用于构建基于功能的插件架构的模式

时间:2015-06-01 16:12:21

标签: .net design-patterns functional-programming system.reactive

我正在研究如何为项目开发插件框架,而Rx似乎非常适合我想要实现的目标。最终,该项目将是一组插件(模块化功能),可以通过xml配置来执行不同的操作。要求如下

  1. 即使在插件中也可以实施模块化架构。这促使松散耦合并可能最小化复杂性。这有望使单个插件功能更容易建模和测试
  2. 强制执行数据不变性以降低复杂性并确保模块内的状态管理保持在最低水平
  3. 通过提供线程池线程以尽可能在模块内工作来阻止手动创建线程
  4. 在我看来,插件本质上是一个数据转换实体(我试图在这里思考功能)。这意味着插件

    • 获取一些数据并以某种方式对其进行转换以生成新数据(此处未显示)
    • 自行生成数据并将其推送给观察者
    • 接收一些数据,并在不通知外人的情况下对数据进行一些处理

    如果你进一步采用这个概念,插件可以包含上述所有三种类型的数量。例如,在插件中你可以有一个IntGenerator模块,它可以为ConsoleWorkUnit模块生成一些数据等等。所以我想要的是main函数中的模型是插件必须执行其工作的连接。

    为此,我使用Microsoft的Immutable nuget创建了以下基类。我想要实现的是抽象出Rx调用,以便它们可以在模块中使用,因此最终的目标是在抽象类中包含对缓冲区等的调用,可以用来组成复杂的查询和模块。通过这种方式,代码比实际读取模块中的所有代码以查找订阅x等类型的缓冲区或窗口更加自我记录。

    public abstract class OutputBase<TOutput> : SendOutputBase<TOutput>
    {
        public abstract void Work();
    }
    
    public interface IBufferedBase<TOutput>
    {
        void Work(IList<ImmutableList<Data<TOutput>>> list);
    }
    
    public abstract class BufferedWorkBase<TInput> : IBufferedBase<TInput>
    {
        public abstract void Work(IList<ImmutableList<Data<TInput>>> input);
    }
    public abstract class SendOutputBase<TOutput>
    {
        private readonly ReplaySubject<ImmutableList<Data<TOutput>>> _outputNotifier;
        private readonly IObservable<ImmutableList<Data<TOutput>>> _observable;
    
        protected SendOutputBase()
        {
            _outputNotifier = new ReplaySubject<ImmutableList<Data<TOutput>>>(10);
            _observable  =  _outputNotifier.SubscribeOn(ThreadPoolScheduler.Instance);
            _observable = _outputNotifier.ObserveOn(ThreadPoolScheduler.Instance);
        }
    
        protected void SetOutputTo(ImmutableList<Data<TOutput>> output)
        {
            _outputNotifier.OnNext(output);
        }
    
        public void ConnectOutputTo(IWorkBase<TOutput> unit)
        {
            _observable.Subscribe(unit.Work);
        }
    
        public void BufferOutputTo(int count, IBufferedBase<TOutput> unit)
        {
            _observable.Buffer(count).Subscribe(unit.Work);
        }
    }
    
    public abstract class WorkBase<TInput> : IWorkBase<TInput>
    {
        public abstract void Work(ImmutableList<Data<TInput>> input);
    }
    
    public interface IWorkBase<TInput>
    {
        void Work(ImmutableList<Data<TInput>> input);
    }
    
    public class Data<T>
    {
        private readonly T _value;
    
        private Data(T value)
        {
            _value = value;
        }
    
        public static Data<TData> Create<TData>(TData value)
        {
            return new Data<TData>(value);
        }
    
        public T Value { get { return _value; } }
    
    }
    

    这些基类用于创建三个类;一个用于生成一些int数据,一个用于在数据发生时打印出数据,最后一个用于在数据进入时缓冲数据并将数据加总为三个。

    public class IntGenerator : OutputBase<int>
    {
        public override void Work()
        {
            var list = ImmutableList<Data<int>>.Empty;
            var builder = list.ToBuilder();
            for (var i = 0; i < 1000; i++)
            {
                builder.Add(Data<int>.Create(i));
            }
    
            SetOutputTo(builder.ToImmutable());
        }
    }
    
    public class ConsoleWorkUnit : WorkBase<int>
    {
        public override void Work(ImmutableList<Data<int>> input)
        {
            foreach (var data in input)
            {
                Console.WriteLine("ConsoleWorkUnit printing {0}", data.Value);
            }
        }
    }
    
    public class SumPrinter : WorkBase<int>
    {
    
        public override void Work(ImmutableList<Data<int>> input)
        {
            input.ToObservable().Buffer(2).Subscribe(PrintSum);
        }
    
        private void PrintSum(IList<Data<int>> obj)
        {
          Console.WriteLine("Sum of {0}, {1} is {2} ", obj.First().Value,obj.Last().Value ,obj.Sum(x=>x.Value) );
        }
    }
    

    这些是在像这样的主要运行

            var intgen = new IntGenerator();
            var cons = new ConsoleWorkUnit();
            var sumPrinter = new SumPrinter();
    
            intgen.ConnectOutputTo(cons);
            intgen.BufferOutputTo(3,sumPrinter);
    
            Task.Factory.StartNew(intgen.Work);
    
            Console.ReadLine();
    

    这个架构听起来好吗?

1 个答案:

答案 0 :(得分:1)

我认为你最好不要在模块中使用Rx而不是将它隐藏在基类中。该路由导致您自己重新实现Rx API的子集。然后,如果插件在内部使用Rx,那么当您刚刚直接公开API时,该插件实际上会将您的非Rx API转换为Rx API,最终会浪费精力。

这样做的“Rx方式”:

生成器(案例1)应该公开(很可能IObservable<T>

interface IGenerator<T>
{
    // each subscription to this will create a new stream of data
    IObservable<T> Data { get; }
}

public class LongGenerator : IGenerator<long>
{
    IntGenerator()
    {
        // example that produces a new integer every second
        Data = Observable.Interval(TimeSpan.FromSeconds(1));

        // something more complex...
        Data = Observable.Create(async (observer, token) =>
        {
            // infinitely poll some web service for the number
            while (!token.CancellationRequested())
            {
                var result = await _client.WebServiceCall(...);
                observer.OnNext(result.Value);
            }
        });
    }
    public IObservable<T> Data { get; private set; }
}

消费者(案例3)应公开IObserver<T>

interface IConsumer<T>
{
    // Use IObserver<ImmutableList<T>> if you want to *force* them to receive buffered input
    // but why not let them do their own buffering if they need it?
    IObserver<T> Observer { get; }
}

public class ConsoleWork : IConsumer<int>, IObserver<int>
{
    public IObserver<T> Observer { get { return this; } }


    public void OnNext(int value) ...
    public void OnError(Exception e) ...
    public void OnComplete() ...
}

变形金刚(案例2)应该有一个方法接受IObservable<T>并返回IObservable<U>

interface ITransform<T, U>
{
    IObservable<U> Transform(IObservable<T>);
}

public class StringTransform : ITransform<int, string>
{
    IObservable<string> Transform(IObservable<int> source)
    {
        return source.Select(i => "Hello " + i.ToString());
    }
}