0

我正在设计一种模式,其中将有多个主题和多个观察者。每个观察者都会在其构造函数中将自己注册到它需要的主题,因为它知道它需要知道什么来产生它的输出。我的第一个想法是让主题成为单例类,但我发现这要么需要将每个主题单独设为单例,要么需要abstract staticJava 不支持的类。底线是,我试图找出最好的解决方案到底是什么。只是为了确保它真的很清楚,这就是我想要做的:

  1. 会有多个科目。每个观察者将有能力观察多个主题。为了完成这项工作,我将为每个主题设置一个资源代码,以便它向观察者标识自己(`update(SUBJECT_1),其中 SUBJECT_1 是对应于主题 1 的 int)
  2. 每个观察者都会知道它想在构建时观察哪些主题。
  3. 我只想要创建每个主题的单个实例。
  4. 一些观察者本身可能反过来成为主题。
  5. 一般来说,有一个流动模式。最终有一些输入来源,它们创造了更抽象的概念。我的观察者/主题流程图如下所示:

在此处输入图像描述

我认为这可能完全需要另一种设计模式,但由于我对设计模式还是相当陌生,我想在我加入之前我会要求更多的经验。谢谢!

为了清楚起见,单例的作用如下,即我想要一个abstractSubject,其中每个主题都是一个单例。虽然我可以自己使每个单例,但我宁愿被迫让所有人都使用 getInstance,就像这个伪代码一样。

abstract class abstractSubject{

AbstractSubject instance=null;

public static AbstractSubject getInstance()
{
    if (instance==null)
        instance=new AbstractInstance();
    return instance;
}
abstract void attach();
//More stuff will go here to make it a subject
}

//Define Subject1, Subject2 classes fully

class Observer1 extends AbstractObserver{

    public Observer1()
    {
        Subject1.getInstance().attach(this);
        Subject2.getInstance().attach(this);
    }
    //Other stuff goes here to make it an observer
}
4

4 回答 4

1

经过一番思考之后,最好的事情似乎是有一个看起来像这样的模式,我认为它与 Builder 或 Factory 类有关,但我仍在尝试弄清楚它是如何工作的。

  1. 有一个第三方类,其工作是跟踪所有创建的主题,并在询问时提供对它们的引用。由于没有更好的术语,我将其称为经纪人。
  2. 每个观察者都可以访问此代理。他们向 Broker 询问他们想要什么,如果尚未实例化,它会创建对象。

如果所有观察者都被构造为使用这个 Broker,那么他们只需要知道让 Broker 返回他们想要的东西的调用,这应该足够简单。每次创建新主题时都需要更新代理,但否则它似乎是我能想出的最干净的方法来使这一切正常工作。

此外,Broker 类包含一个包含所有主题的 Enum,因此,它可以作为跟踪与给定类对应的 Enum 值的有效点。

这看起来像:

public class Broker {
    public enum Subject {Subj1, Subj2};

    static private Broker instance=null;

    HashMap<Subject,AbstractSubject> mSubjectDict;

    public static Broker getInstance()
    {
        return instance;
    }

    private Broker()
    {
        mSubjectDict=new HashMap<Subject,AbstractSubject>();
        mSubjectDict.put(Subject.Subj1,new Subject1());
        mSubjectDict.put(Subject.Subj2,new Subject2());
    }

    public AbstractLocationSubject getSubject(Subject subj)
    {
        return mSubjectDict.get(subj);
    }
}

添加一个新的可能的主题需要向枚举添加一个值,并在 mSubjectDict 中创建主题。总的来说,我对这个解决方案很满意。

于 2013-10-16T01:44:34.050 回答
0

我不会谈论 Java,但我在 C++ 中遇到过类似类型的问题。

这是我提出的解决方案的粗略草图。它是用 C++ 编写的,但基本思想应该很好地转化为 Java(或其他 OO 语言):

  1. Observer 是一个带有键(枚举值,而不是字符串;这是一个速度折衷,因为键没有经过散列搜索,但这意味着没有简单的“字符串”名称,您必须提前定义它们)以便主题注册兴趣。因为是单例,所以一直存在。
  2. 每个主题都派生自一个公共基类。基类有一个抽象虚函数 Notify(...) 必须在派生类中实现,以及一个析构函数,当它被删除时,它会从 Observer 中删除(它总是可以到达)。
  3. 在观察者本身内部,如果在 Notify(...) 正在进行时调用 Detach(...),则任何分离的 Subjects 最终都会出现在列表中。
  4. 当在 Observer 上调用 Notify(...) 时,它会创建主题列表的临时副本。当它迭代它时,它将它与最近分离的进行比较。如果目标不在其上,则在目标上调用 Notify(...)。否则,将被跳过。
  5. Observer 中的 Notify(...) 还跟踪处理级联调用的深度(A 通知 B、C、D,并且 D.Notify(...) 触发对 E 的 Notify(...) 调用, ETC。)

这是界面最终的样子:

/* 
 The Notifier is a singleton implementation of the Subject/Observer design
 pattern.  Any class/instance which wishes to participate as an observer
 of an event can derive from the Notified base class and register itself
 with the Notiifer for enumerated events.

 Notifier derived classes MUST implement the notify function, which has 
 a prototype of:

 void Notify(const NOTIFIED_EVENT_TYPE_T& event)

 This is a data object passed from the Notifier class.  The structure 
 passed has a void* in it.  There is no illusion of type safety here 
 and it is the responsibility of the user to ensure it is cast properly.
 In most cases, it will be "NULL".

 Classes derived from Notified do not need to deregister (though it may 
 be a good idea to do so) as the base class destructor will attempt to
 remove itself from the Notifier system automatically.

 The event type is an enumeration and not a string as it is in many 
 "generic" notification systems.  In practical use, this is for a closed
 application where the messages will be known at compile time.  This allows
 us to increase the speed of the delivery by NOT having a 
 dictionary keyed lookup mechanism.  Some loss of generality is implied 
 by this.

 This class/system is NOT thread safe, but could be made so with some
 mutex wrappers.  It is safe to call Attach/Detach as a consequence 
 of calling Notify(...).  

 */


class Notified;

class Notifier : public SingletonDynamic<Notifier>
{
public:
   typedef enum
   {
      NE_MIN = 0,
      NE_DEBUG_BUTTON_PRESSED = NE_MIN,
      NE_DEBUG_LINE_DRAW_ADD_LINE_PIXELS,
      NE_DEBUG_TOGGLE_VISIBILITY,
      NE_DEBUG_MESSAGE,
      NE_RESET_DRAW_CYCLE,
      NE_VIEWPORT_CHANGED,
      NE_MAX,
   } NOTIFIED_EVENT_TYPE_T;

private:
   typedef vector<NOTIFIED_EVENT_TYPE_T> NOTIFIED_EVENT_TYPE_VECTOR_T;

   typedef map<Notified*,NOTIFIED_EVENT_TYPE_VECTOR_T> NOTIFIED_MAP_T;
   typedef map<Notified*,NOTIFIED_EVENT_TYPE_VECTOR_T>::iterator NOTIFIED_MAP_ITER_T;

   typedef vector<Notified*> NOTIFIED_VECTOR_T;
   typedef vector<NOTIFIED_VECTOR_T> NOTIFIED_VECTOR_VECTOR_T;

   NOTIFIED_MAP_T _notifiedMap;
   NOTIFIED_VECTOR_VECTOR_T _notifiedVector;
   NOTIFIED_MAP_ITER_T _mapIter;

   // This vector keeps a temporary list of observers that have completely
   // detached since the current "Notify(...)" operation began.  This is
   // to handle the problem where a Notified instance has called Detach(...)
   // because of a Notify(...) call.  The removed instance could be a dead
   // pointer, so don't try to talk to it.
   vector<Notified*> _detached;
   int32 _notifyDepth;

   void RemoveEvent(NOTIFIED_EVENT_TYPE_VECTOR_T& orgEventTypes, NOTIFIED_EVENT_TYPE_T eventType);
   void RemoveNotified(NOTIFIED_VECTOR_T& orgNotified, Notified* observer);

public:

   virtual void Reset();
   virtual bool Init() { Reset(); return true; }
   virtual void Shutdown() { Reset(); }

   void Attach(Notified* observer, NOTIFIED_EVENT_TYPE_T eventType);
   // Detach for a specific event
   void Detach(Notified* observer, NOTIFIED_EVENT_TYPE_T eventType);
   // Detach for ALL events
   void Detach(Notified* observer);

   /* The design of this interface is very specific.  I could 
    * create a class to hold all the event data and then the
    * method would just have take that object.  But then I would
    * have to search for every place in the code that created an
    * object to be used and make sure it updated the passed in
    * object when a member is added to it.  This way, a break
    * occurs at compile time that must be addressed.
    */
   void Notify(NOTIFIED_EVENT_TYPE_T, const void* eventData = NULL);

   /* Used for CPPUnit.  Could create a Mock...maybe...but this seems
    * like it will get the job done with minimal fuss.  For now.
    */
   // Return all events that this object is registered for.
   vector<NOTIFIED_EVENT_TYPE_T> GetEvents(Notified* observer);
   // Return all objects registered for this event.
   vector<Notified*> GetNotified(NOTIFIED_EVENT_TYPE_T event);
};

/* This is the base class for anything that can receive notifications.
 */
class Notified
{
public:
   virtual void Notify(Notifier::NOTIFIED_EVENT_TYPE_T eventType, const void* eventData) = 0;
   virtual ~Notified();

};

typedef Notifier::NOTIFIED_EVENT_TYPE_T NOTIFIED_EVENT_TYPE_T;

注意:Notified 类有一个函数,Notify(...) 这里。因为 void* 不是类型安全的,所以我创建了其他版本,其中 notify 如下所示:

virtual void Notify(Notifier::NOTIFIED_EVENT_TYPE_T eventType, int value); 
virtual void Notify(Notifier::NOTIFIED_EVENT_TYPE_T eventType, const string& str);

相应的 Notify(...) 方法已添加到通知程序本身。所有这些都使用一个函数来获取“目标列表”,然后在目标上调用适当的函数。这很好用,并且使接收器不必进行难看的演员表。

这似乎运作良好。该解决方案与源代码一起 发布在网络上

于 2013-11-16T00:46:38.780 回答
0

我真的不明白,你为什么还要考虑单身人士。它对观察者模式中的任何事情都没有帮助。不惜一切代价避免 Singleton,直到您是非常优秀的程序员,或者您真的看不到任何其他合理的选择。

通知一些观察者和一些不基于硬编码数字的方法实际上是非 OOP。

例如,您可以创建名为“Quests”的枚举,并且每个观察者都会从该枚举中拥有自己的值列表。当您从一个或多个主题通知观察者时,您只需查看已注册观察者的整个列表并根据该枚举通知他们(或者您可以通知所有人,将此枚举作为通知的参数作为消息,您可以让观察者决定,如果他们做任何事情)。

编辑(基于在您的帖子中添加的 5.):看起来您并不真的需要观察者模式,它通常用作事件处理程序。如果您的数据从一个实例流向另一个实例,并且您知道它将如何流动等,那么实际上并不需要 Observer。我不知道您到底在用数据做什么,您可以考虑一下责任链。

但是,如果您的实例只是接收消息,对它们进行计数,然后将下一条消息发送到其他实例,那么至少观察者的想法 - 注册和通知 - 是一个好方法。

于 2013-10-16T01:21:34.607 回答
0

您是否正在尝试构建工作流引擎?

已经有很多这样的: 我应该使用哪个基于 Java 的工作流引擎?

我还将把 Spring Integration 和 Spring Batch 添加到列表中。

于 2013-10-16T02:56:48.857 回答