C# 类型特定的观察者

C# type specific observers

在此应用程序中,观察者处理对象网络中的状态变化。所有对象都是基于相同的 BaseObject class 派生的 classes。 BaseObject 提供基本的识别和导航功能。

从 BaseObject 派生的

类 是由代码生成器创建的。这些 classes 应具有最小的足迹,并专注于特定的状态和行为。

在应用程序级别,观察者处理 BaseObject 派生的 classes 中的状态变化,通常不止一个 class,而且主要是针对大量 BaseObject 派生的实例。

在当前解决方案中,BaseObject 管理观察者并以 BaseObject 实例作为发送者通知观察者。

using System;
using System.Collections.Generic;

namespace Observer {
  #region underlying framework foundation
  interface IObserver {
    void ObjectChanged (BaseObject obj);
  }

  abstract class BaseObject {
    HashSet<IObserver> observers = new HashSet<IObserver> ();

    public void RegisterObserver (IObserver observer) {
      observers.Add (observer);
    }

    public void FireObjectChanged () {
      foreach (var obs in observers)
        obs.ObjectChanged (this);
    }
  }
  #endregion underlying framework foundation

  #region code generator
  class DerivedObject1 : BaseObject {
  }

  class DerivedObject2 : BaseObject {
  }
  #endregion code generator

  #region application code
  class Observer : IObserver {

    public void ObjectChanged (BaseObject obj) {
      Console.WriteLine (obj.GetType ().Name);

      if (obj is DerivedObject1) {

      } else if (obj is DerivedObject2) {

      }
    }
  }
  #endregion application code

  #region sample
  class Program {
    static void Main (string[] args) {
      Observer observer = new Observer ();
      List<BaseObject> objects = new List<BaseObject> ();

      DerivedObject1 obj1 = new DerivedObject1 ();
      objects.Add (obj1);
      obj1.RegisterObserver (observer);

      DerivedObject2 obj2 = new DerivedObject2 ();
      objects.Add (obj2);
      obj2.RegisterObserver (observer);

      foreach (var bo in objects)
        bo.FireObjectChanged ();
    }
  }
  #endregion sample
}

我不喜欢这种方法的地方是观察者必须在 运行 时识别发送者类型。相反,我想让它在编译时是安全的。

因此我提出了一种引入泛型和第二个 BaseObject 层的新方法,BaseObjectT 包含类型安全的观察者。

using System;
using System.Collections.Generic;

namespace Observer {
    #region underlying framework foundation
    interface IObserver<T> where T : BaseObjectT<T> {
        void ObjectChanged (T obj);
    }

    abstract class BaseObject {
        public abstract void FireObjectChanged ();
    }

    abstract class BaseObjectT<T> : BaseObject where T : BaseObjectT<T> {
        HashSet<IObserver<T>> observers = new HashSet<IObserver<T>> ();

        public void RegisterObserver (IObserver<T> observer) {
            observers.Add (observer);
        }

        public override void FireObjectChanged () {
            foreach (var obs in observers)
                obs.ObjectChanged ((T)this);
        }
    }
    #endregion underlying framework foundation

    #region code generator
    class DerivedObject1 : BaseObjectT<DerivedObject1> {
    }

    class DerivedObject2 : BaseObjectT<DerivedObject2> {
    }
    #endregion code generator

    #region application code
    class Observer :
      IObserver<DerivedObject1>,
      IObserver<DerivedObject2> {

        public void ObjectChanged (DerivedObject1 obj) {
            Console.WriteLine (obj.GetType ().Name);
        }

        public void ObjectChanged (DerivedObject2 obj) {
            Console.WriteLine (obj.GetType ().Name);
        }
    }
    #endregion application code

    #region sample
    class Program {
        static void Main (string[] args) {
            Observer observer = new Observer ();
            List<BaseObject> objects = new List<BaseObject> ();

            DerivedObject1 obj1 = new DerivedObject1 ();
            objects.Add (obj1);
            obj1.RegisterObserver (observer);

            DerivedObject2 obj2 = new DerivedObject2 ();
            objects.Add (obj2);
            obj2.RegisterObserver (observer);

            foreach (var bo in objects)
                bo.FireObjectChanged ();
        }
    }
    #endregion sample
}

虽然这种方法有效并且在应用程序级别完全符合我的要求 - 将派生的实例 class 传递给观察者而不需要在那里进行类型转换,并允许重载不同的 IObserver方法实现 - 在底层对我来说似乎有点难看。

我现在的问题是,是否有更好、更优雅的方法来完成此操作,特别是有没有办法避免在 FireObjectChanged() 中进行强制转换

obs.ObjectChanged ((T)this);      

或者将 BaseObject 和 BaseObjectT 组合成一个基 class?

正如@Falanwe 评论的那样,IObserver<T> 是一个系统class,你应该给你的观察者起别的名字。我为下面的示例代码选择了 ICustomObserver<T>

我想到的唯一选择就是这个。我们创建了一个扩展方法来将委托存储在 ConditionalWeakTable 中。这些委托 (Action<BaseObject>) 可以在每个对象的基础上从另一个扩展方法中调用 ObjectChanged。

这种方法的优点是您的 BaseObject 中不需要任何花哨的东西,并且您可以以类型安全的方式为 BaseObject 或 DerivedObject1 调用 RegisterObserver。

缺点是如果您还不了解扩展方法、委托或 ConditionalWeakTable,则可能会有一个学习曲线。

public static class ObjectChangedExtension
{
    internal static ConditionalWeakTable<object, List<Action<BaseObject>>> observers
        = new ConditionalWeakTable<object, List<Action<BaseObject>>>();

    public static void RegisterObserver<T>(this T obj, ICustomObserver<T> observer)
        where T : BaseObject
    {
        Action<BaseObject> objChangedDelegate = v => observer.ObjectChanged((T)v);

        observers
            .GetOrCreateValue(obj)
            .Add(objChangedDelegate);
    }

    public static void FireObjectChanged(this BaseObject obj)
    {
        observers
            .GetOrCreateValue(obj)
            .ForEach(v => v(obj));
    }
}

#region code generator
class DerivedObject1 : BaseObject
{
}

class DerivedObject2 : BaseObject
{
}
#endregion code generator

#region application code
class Observer : ICustomObserver<DerivedObject1>, ICustomObserver<DerivedObject2>
{
    public void ObjectChanged(DerivedObject1 obj)
    {
        Console.WriteLine("DerivedObject1 Observer");
    }

    public void ObjectChanged(DerivedObject2 obj)
    {
        Console.WriteLine("DerivedObject2 Observer");
    }
}

class ObserverOfBase : ICustomObserver<BaseObject>
{
    public void ObjectChanged(BaseObject obj)
    {
        Console.WriteLine("BaseObject Observer");
    }
}
#endregion application code

#region sample
class Program
{
    internal static void Main(string[] args)
    {
        Observer observer = new Observer();
        List<BaseObject> objects = new List<BaseObject>();

        DerivedObject1 obj1 = new DerivedObject1();
        objects.Add(obj1);
        obj1.RegisterObserver(observer);

        DerivedObject2 obj2 = new DerivedObject2();
        objects.Add(obj2);
        obj2.RegisterObserver(observer);

        var baseObjectObserver = new ObserverOfBase();
        obj1.RegisterObserver(baseObjectObserver);
        obj2.RegisterObserver(baseObjectObserver);

        foreach (var bo in objects)
            bo.FireObjectChanged();
    }
}
#endregion sample

public class BaseObject
{
}

public interface ICustomObserver<T>
{
    void ObjectChanged(T obj);
}