كيف تم نقل البيانات إلى مراقب "عام"؟ كحجال أو كهيكل واحد؟

StackOverflow https://stackoverflow.com/questions/3601886

  •  25-09-2019
  •  | 
  •  

سؤال

أنا مشغول بإضافة آلية مراقب عامة إلى تطبيق C ++ Legacy (باستخدام Visual Studio 2010 ، ولكن لا يستخدم .NET ، لذلك.

في التصميم ، أريد فصل الجزء الخاص بالتطبيق قدر الإمكان عن آلية المراقب العام.

الطريقة الأكثر منطقية لتنفيذ المراقبين تبدو بهذه الطريقة:

class IDoThisObserver
   {
   public:
      void handlDoThis(int arg1, int arg2) = 0;
   };

لكل نوع من أنواع المراقب (idothisobserver ، idothatobserver ، ...) تختلف حجج الأساليب (Handledothis ، Handledothat).

ما تبقى بطريقة عامة لتخزين المراقبين ، مثل هذا:

template<typename T>
class ObserverContainer
   {
   public:
      void addObserver (T &t) {m_observers.push_back(&t);}
   private:
      std::list<T*> m_observers;
   };

لا يمكن تعميم استدعاء المراقب نظرًا لأن الحجج مختلفة لكل نوع مراقب.

هناك طريقة بديلة تتمثل في "حزم" جميع الحجج في حجة واحدة ، مثل هذه:

struct DoThisInfo
   {
   DoThisInfo (int arg1, int arg2) : m_arg1(arg1), m_arg2(arg2) {}
   int m_arg1;
   int m_arg2;
   };

ثم حدد مراقبًا أكثر عامة ، مثل هذا:

template<typename T>
class IObserver
   {
   public:
      void notify(const T &t) = 0;
   };

وستصبح مجموعة من هؤلاء المراقبين بعد ذلك:

template<typename T>
class ObserverContainer
   {
   public:
      void addObserver (IObserver<T> &obs) {m_observers.push_back(&obs);}
   private:
      std::list<IObserver<T>*> m_observers;
   };

الآن ، يمكن إضافة المزيد من المنطق مركزيًا إلى هذا ObserverContainer ، بما في ذلك استدعاء جميع المراقبين. يحتاج "البادئ" للمكالمة إلى إنشاء بنية الإخطار وملءها.

تحتاج الفصول التي تريد أن ترث من أنواع متعددة من المراقبين ، إلى القيام بذلك مثل هذا:

class MyObserver : public IObserver<NotifyThis>, public IObserver<NotifyThat>
   {
   ...
   };

أي من هذه الأساليب (المراقبين مع وسائط واضحة متعددة أو مع حجة بنية واحدة) تبدو الأفضل؟ هل هناك أي مزايا أو عيوب لأي من هذه الأساليب؟

تعديل: نظرت إلى أبعد من ذلك إلى الأساليب البديلة ، ويبدو نهج الفتحة/الإشارة مرشحًا جيدًا آخر. هل هناك أي عيوب مهمة في الفتحة/الإشارة التي يجب أن أعرفها؟

هل كانت مفيدة؟

المحلول

التصميم مع struct الوسيطة هي بالتأكيد أفضل لأنها تتيح كتابة التعليمات البرمجية العامة في ObserverContainer. إنها عمومًا ممارسة تصميم جيدة لاستبدال قوائم الحجة الطويلة بكائنات تغلف الوسيطات وهذا مثال جيد على المكافأة. من خلال إنشاء تجريد أكثر عمومية لك notify الطريقة (مع الهيكل الذي تحدده notify كطريقة تأخذ جزءًا كبيرًا من "البيانات" ، بينما مع قائمة ARG ، فإنك تحدد طريقة تأخذ رقمين) تسمح لنفسك بكتابة رمز عام يستخدم الطريقة ولا يجب أن تهتم بالتكوين الدقيق من تم تمريره في جزء من البيانات.

نصائح أخرى

لماذا لا تفعل فقط:

class IObserver {
    // whatever is in common
};

class IDoThisObserver : public IObserver
{
   public:
      void handlDoThis(int arg1, int arg2) = 0;
};

class IDoThatObserver : public IObserver
{
   public:
      void handlDoThat(double arg1) = 0;
};

?

ثم لديك:

class ObserverContainer
{
   public:
      void addObserver (IObserver* t) {m_observers.push_back(t);}
   private:
      std::list<IObserver*> m_observers;
};

هل بحثت في Boost.Signals؟ أفضل من إعادة تنفيذ العجلة.

أما بالنسبة للمعلمات: يجب أن يكون استدعاء المراقب/الفتحة هو نفسه كما لو كنت ستتصل بوظيفة عادية. تتيح معظم الإشارات المنسقات-معلمات متعددة ، لذا استخدمها. ويرجى استخدام إشارات مختلفة لأنواع المراقب المختلفة ، ثم ليست هناك حاجة لتمرير البيانات في المتغيرات.

اثنين من العيوب من المراقب النمطي/الإشارات التي رأيتها:
1) تدفق البرنامج صعب أو حتى مستحيل فهمه من خلال النظر فقط إلى المصدر.
2) برامج ديناميكية شديدة مع الكثير من المراقبين/الإشارات قد تواجه "حذف هذا"

كل شيء جانبا ، أحب المراقبين/الإشارات أكثر من الفئات الفرعية وبالتالي اقتران مرتفع.

لا أعتقد أن أيًا من مقارباتك يناسب متطلباتك كما هو. ومع ذلك ، هناك القليل من التعديل باستخدام حامل بيانات يحتوي على مجموعة البيانات التي تم تمريرها عبر جميع المراقبين حيث يعرف كل مراقب ما الذي يجب قراءته سيفعل الخدعة. قد يقوم رمز العينة أدناه بمسحه (ملاحظة لم أجمعها)

 enum Type {
    NOTIFY_THIS,
    NOTIFY_THAT
 };

 struct Data {
 virtual Type getType() = 0;
 };

 struct NotifyThisData: public Data {
    NotifyThisData(int _a, int _b):a(_a), b(_b) { }
    int a,b;
    Type getType() { return NOTIFY_THIS; }
 };

 struct NotifyThatData: public Data {
    NotifyThatData(std::string _str):str(_str) { }
    std::string str;
    Type getType() { return NOTIFY_THAT; }
 };

 struct DataCarrier {
    std::vector<Data*> m_TypeData;  
 };

 class IObserver {
 public:
     virtual void handle(DataCarrier& data) = 0;
 };

 class NotifyThis: public virtual IObserver {
 public:
         virtual void handle(DataCarrier& data) {
                 vector<Data*>::iterator iter = find_if(data.m_TypeData.begin(), data.m_TypeData.end(), bind2nd(functor(), NOTIFY_THIS);
                 if (iter == data.m_TypeData.end())
                         return;
                 NotifyThisData* d = dynamic_cast<NotifyThisData*>(*iter);
                 std::cout << "NotifyThis a: " << d->a << " b: " << d->b << "\n";
         }
 };

 class NotifyThat: public virtual IObserver {
 public:
         virtual void handle(DataCarrier& data) {
                 vector<Data*>::iterator iter = find_if(data.m_TypeData.begin(), data.m_TypeData.end(), bind2nd(functor(),NOTIFY_THAT);
                 if (iter == data.m_TypeData.end())
                         return;            
                 NotifyThatData* d = dynamic_cast<NotifyThatData*>(*iter);
                 std::cout << "NotifyThat str: " << d->str << "\n";
         }
 };

 class ObserverContainer
    {
    public:
       void addObserver (IObserver* obs) {m_observers.push_back(obs);}
       void notify(DataCarrier& d) {
                 for (unsigned i=0; i < m_observers.size(); ++i) {
                         m_observers[i]->handle(d);
                 }
         }
    private:
       std::vector<IObserver*> m_observers;
    };

 class MyObserver: public NotifyThis, public NotifyThat {
 public:
         virtual void handle(DataCarrier& data) { std::cout << "In MyObserver Handle data\n"; }
 };

 int main() {
         ObserverContainer container;
         container.addObserver(new NotifyThis());
         container.addObserver(new NotifyThat());
         container.addObserver(new MyObserver());

         DataCarrier d;
         d.m_TypeData.push_back(new NotifyThisData(10, 20));
         d.m_TypeData.push_back(new NotifyThatData("test"));

    container.notify(d);
    return 0;
 }

بهذه الطريقة تحتاج إلى تعديل التعداد فقط إذا قمت بإضافة بنية جديدة. يمكنك أيضًا استخدام Boost :: shared_ptr للتعامل مع فوضى المؤشرات.

لن أحصل على بناء الجملة بشكل صحيح ، لذا سأقوم فقط بإدراج الإعلانات لتوضيح الهياكل. يمكن صنع مراقب عام لتوقع معلمة إما فئة فرعية إلى أشكال محددة من المعلمات المطلوبة أو هي بنية بما في ذلك رسم الخرائط الأفقية لجميع المعلمات البدائية التي سيتطلبها المراقبين. ثم يمكن أن يعمل ObserverContainer ك AbstractFactory ويمكن أن يكون كل فئة فرعية من ObserverContainer dothatobserverfactory و dothisobserverfactory. سيقوم المصنع ببناء مراقب ويعين تكوين إلى المراقب لإخباره بالمعلمة التي يمكن توقعها.

class AbstractObserverFactory {...};
class DoThatObserverFactory : AbstractObserverFactory {...};
class DoThisObserverFactory : AbstractObserverFactory {...};
class ObserverParam {...};
class DoThatObserverParam : ObserverParam {...};
class DoThisObserverParam : ObserverParam {...};
class Observer;
class DoThisObserver : public Observer
{
   public:
      void handlDoThis(DoThisObserverParam);
};
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top