【问题标题】:How to subscribe objects without knowing their invoker's class?如何在不知道调用者类的情况下订阅对象?
【发布时间】:2020-03-12 14:58:35
【问题描述】:

我有一个Scenario,它使用A 或多个AsA 是在这个 Scenario 之外创建的,但是在 A 内部创建的 B 不知道Scenario 它的实例。这就是想法,A 和 B 不必知道 Scenario 是如何工作的,他们只需要在引发/调用 UpdateEvent 时工作。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ConsoleApp
{
    class Program
    {
        static void Main(string[] args)
        {

            var sce1 = new Scenario();
            sce1.Add(new A(1));
            sce1.Add(new A(3));
            sce1.Update(2);
            sce1.Update(5);

            var sce2 = new Scenario();
            sce2.Add(new A(6));
            sce2.Update(0.3);

            Console.ReadKey();

        }
    }

    public class BasicEvent : EventArgs
    {
        public double Number { get; set; }
    }

    public abstract class toBeUpdated
    {
        public abstract void OnUpdate(object sender, BasicEvent e);
    }

    public class C : toBeUpdated
    {
        public double Number { get; set; }
        public C(double num)
        {
            Number = num * num;
        }
        public override void OnUpdate(object sender, BasicEvent e)
        {
            Number = Math.Pow(e.Number, 2);
        }
    }

    public class B : toBeUpdated
    {
        public double  Number { get; set; }
        public B(double num)
        {
            Number = num;
        }
        public override void OnUpdate(object sender, BasicEvent e)
        {
            Number *= e.Number;
        }
    }

    public class A : toBeUpdated
    {
        private B B1 { get; set; }
        private B B2 { get; set; }
        private C C1 { get; set; }
        public A(double number)
        {
            B1 = new B(number);
            B2 = new B(number);
            C1 = new C(number);
        }
        public override void OnUpdate(object sender, BasicEvent e)
        {
            Console.WriteLine($"Updating A.. Data = {B1.Number + B2.Number * C1.Number}");
        }
    }

    public class Scenario
    {
        private event EventHandler<BasicEvent> UpdateEvent;
        private List<toBeUpdated> toKeepTrack { get; set; }
        public Scenario()
        {
            toKeepTrack = new List<toBeUpdated>();
        }
        public void Add(toBeUpdated obj)
        {
            toKeepTrack.Add(obj);
            UpdateEvent += obj.OnUpdate;
        }
        public void doSomethingWithTheList()
        {
            ....
        }
        public void Update(double num)
        {
            UpdateEvent?.Invoke(this, new BasicEvent() { Number = num });
            Console.WriteLine();
        }
    }
}

我需要创建几个场景,我不能让它成为静态的。

【问题讨论】:

  • A & B 是只订阅一个Scenario 实例还是同时订阅多个?
  • @Hayden 只有一个
  • 您的问题是“现有的AB 必须订阅不同的Scenario”吗?
  • @LouisGo 不,A 和 A 内部创建的 B 的场景相同
  • 我没有得到你的问题。 ABOnUpdate 必须订阅Scenario 的事件。这样一来,AB 就真的不知道是谁给OnUpdate 打电话了。但是您认为这还不够,因为您想“使它们成为静态”。我不明白您为什么要“使它们成为静态”。一定有一些你没有在这个问题上发表的观点。我可能误解了你的问题。

标签: c# events architecture event-handling structure


【解决方案1】:

对于这个用例,我认为最好的方法是创建一个接口IScenario 并让Scenario 扩展这个接口。这将隐藏Scenario 的实现细节,并允许您将IScenario 直接传递给A 的构造函数。

public interface IScenario
{
    void Add(toBeUpdated obj);
    void Update(int num);
}

您现在可以像这样扩展Scenario

public class Scenario : IScenario
{
    ...
}

相应地更新A

public class A : toBeUpdated
{
    ...
    public A(int number, IScenario scenario)
    {
        B1 = new B(number);
        scenario.Add(this);
        scenario.Add(B1);
    }
    ...
}

如何使用是这样的:

var sce = new Scenario();
A a1 = new A(1, sce);
A a3 = new A(3, sce);
sce.Update(2);
sce.Update(5);

这种方法确保A 不知道Scenario 它正在以最小的努力进行更新,并且非常可测试。另一种方法是在A 上添加一个方法,该方法注册一个Scenario(如果A 需要在创建后添加一个新的Scenario 时很有用):

public void Register(IScenario scenario)
{
    scenario.Add(this);
    scenario.Add(B1);
}

注意:创建IScenario 不是必需,但当您进入unit testing 并想要mock 对象时,创建mock 是很好的。

编辑:您始终可以使用反射从A 获取toBeUpdated 类型的所有属性:

// In Scenario
public void Add(toBeUpdated obj)
{
    UpdateEvent += obj.OnUpdate;

    // Get all properties of type toBeUpdated
    var properties = obj.GetType()
        .GetProperties(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static)
        .Select(x => x.GetValue(obj))
        .OfType<toBeUpdated>();

    foreach (var property in properties)
    {
        UpdateEvent += property.OnUpdate;
    }
}

【讨论】:

  • 我考虑过这个解决方案,但是如果我有几个 B 呢?还是几个C?这个想法是动态的
  • @Noxious 如何实例化几个 B 或几个 C?在A里面像B1?如果是这种情况,您能否更新您的帖子以展示这一点?
  • 抱歉,我花了一段时间才让它变得更复杂一些
  • @Noxious 我已经相应地更新了我的答案,这应该可以满足您的需求。
  • 我得到了 properties.Count() == 0,没有任何反应我会看到更多关于 GetProperties 的代码,非常感谢!只是一个问题,反思很慢还是出于任何原因不推荐?
猜你喜欢
  • 2017-12-04
  • 1970-01-01
  • 1970-01-01
  • 2020-11-24
  • 2018-11-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多