博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
[原译]类型安全的黑板模式(属性包)
阅读量:6177 次
发布时间:2019-06-21

本文共 6376 字,大约阅读时间需要 21 分钟。

  有时候对于对象来说。在一个软件中,不直接通过互相引用而做到共享信息是非常有用的。比如像带有插件的软件。可以互相进行通信。假设我们有了很多对象。其中一些包含一些数据。而另一些对象需要消费这些数据 不同的子集,我们不通过对数据生产者和消费者的直接引用来实现,而是通过更低耦合的方式。叫做创建一个“BlackBoard”(黑板)对象。该对象允许其他对象自由对其进行读取/写入数据。这种解耦方式使得消费者不知道也不必知道数据来自哪里。如果想要了解更多关于黑板模式的信息。我们常说的。Google是你最好的朋友。

  一个最简单的黑板对象应该是 Dictionary一些简单的命名值的字典。所有的对象共享同一个字典引用。使得他们可以交换这些命名数据。这种方法有两个问题。一个是名字。一个是类型安全—数据生产者和消费者对每一个数据值都必须共享一个字符串标识。消费者也没有对字典中的值进行编译时的类型检查,比如,可能期望一个小数,结果运行时读到了字符串。本文对这两个问题演示了一种解决方案。

背景

  最近我在开发一个通用任务的异步执行的引擎。我的通用任务通常有Do/Undo方法。原则上是相互独立的,但是有一些任务需要从已经执行的任务重请求数据。比如。一个任务可以

为一个硬件设备建立一个API,随后的任务就可以使用创建好的API来操作硬件设备。但是。我不想我的执行引擎知道关于这个执行任务的任何信息。而且。我也不想直接手工的就在一个任务里引用另一个任务。

黑板类

  黑板类本质上是一个Dictionary的包装类,对外暴露Get和Set方法。黑板类允许其他对象存储并且取回数据。但是要求这些数据使用一个

BlackboardProperty 类型的标识符来表示这些数据是可存取的。BlackboardProperty 对象应该在那些准备读写黑板类的对象之间共享,因此,他应该在那些类中作为一个静态成员。(很像WPF的依赖属性。是他们所属控件的静态成员)

注意:命名安全应该可以通过同样的方式实现。但是但是依然没有解决类型安全的问题。那么。到了主要的部分了。那就是黑板类的代码了

public class Blackboard : INotifyPropertyChanged, INotifyPropertyChanging{    Dictionary
_dict = new Dictionary
(); public T Get
(BlackboardProperty
property) { if (!_dict.ContainsKey(property.Name)) _dict[property.Name] = property.GetDefault(); return (T)_dict[property.Name]; } public void Set
(BlackboardProperty
property, T value) { OnPropertyChanging(property.Name); _dict[property.Name] = value; OnPropertyChanged(property.Name); } #region property change notification public event PropertyChangingEventHandler PropertyChanging; public event PropertyChangedEventHandler PropertyChanged; protected virtual void OnPropertyChanging(string propertyName) { if (PropertyChanging != null) PropertyChanging(this, new PropertyChangingEventArgs(propertyName)); } protected virtual void OnPropertyChanged(string propertyName) { if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); } #endregion}

 

黑板属性(BlackBoardProperty)类

  BlackBoardProperty 类 提供了一个标识符来存取黑板对象中的数据。定义了名称和值的类型。也定义了一个默认的返回值。以防黑板类中对应属性没有值。

/// /// 对应黑板类中的属性的强类型标识符/// /// 
该类能识别的属性值的类型
public class BlackboardProperty
{ ///
/// 属性的名称 ///
/// 黑板类的属性通过名称来存储。请注意不要让相同的名字有不同的属性值。因为如果被用在同样的黑板类上。他们会互相覆盖值 ///
///
public string Name { get; set; }//当黑板对象没有包含对应属性的时候。该工厂方法被用来提供一个默认的值 // Func
_createDefaultValueFunc; public BlackboardProperty(string name) : this(name, default(T)) { } ///
/// /// ///
///
/// 当黑板类不包括该属性的时候。该值会被返回。 ///
/// 如果缺省的值是一个常量或是一个值类型的时候,使用该构造方法。 ///
/// public BlackboardProperty(string name, T defaultValue) { Name = name; _createDefaultValueFunc = () => defaultValue; } ///
/// ///
/// 如果缺省值是一个引用类型,并且,你不想要共享该实例给多个黑板对象的时候。请使用该/// 构造函数 ///
///
///
public BlackboardProperty(string name, Func
createDefaultValueFunc) { Name = name; _createDefaultValueFunc = createDefaultValueFunc; } public BlackboardProperty() { Name = Guid.NewGuid().ToString(); } public T GetDefault() { return _createDefaultValueFunc(); }}

 

我承认不是非常有用的代码。但是。能够模拟两个类的使用。

下一个例子会更和现实情况接近。但是肯定是被简化过了的。在下面的例子里。我定义了集中不同的任务。我用这些任务来启动对硬件设备的连接。操作设备。关闭连接。这些任务通过一个执行引擎依次执行,这些任务通过一个公用的黑板类来共享数据。至于这个任务类的和执行引擎(ExecutionEngine)类还是留到另一篇文章中把。

//一个设备的接口例子interface IDevice{    void Connect();    void Reset();    decimal Read(string obis);    void Close();}//任务实例化了设备api,设置并且启动连接class InitiateDeviceTask : Task{    //这个用来定义黑板类里的DeviceAPI 变量    public static BlackboardProperty
DeviceAPIProperty = new BlackboardProperty
(); protected override void Execute(Blackboard context) { IDevice deviceAPI = null; //deviceAPI = ...创建并设置设备 API, 连接到设备 deviceAPI.Connect(); //这是最重要的部分 – 我把deviceAPI 放在了黑板类里 //并使用 DeviceProperty作为该对象的标识符 context.Set(DeviceAPIProperty, deviceAPI); }}//t该任务重置设备class ResetDeviceTask : Task{ protected override void Execute(Blackboard context) { //从黑板类里取出 deviceAPI // InitiateDeviceTask这个任务必然要在本任务之前执行 IDevice deviceAPI = context.Get(InitiateDeviceTask.DeviceAPIProperty);//注意不需要进行转换 deviceAPI.Reset(); }}//该任务读取设备的一个寄存器class ReadRegisterTask : Task{ public string RegisterName { get; set; } //工厂方法为该属性提供一个默认的值 public static BlackboardProperty
> ReadingsProperty = new BlackboardProperty
>("Readings", () => new Dictionary
()); protected override void DoExecute(Blackboard context) { IDevice deviceAPI = context.Get(InitiateDeviceTask.DeviceAPIProperty); decimal value = deviceAPI.Read(RegisterName);//我们可以把值放在一个仓库里 //可以把寄存器的名字和值存储到字典里。这样其他的任务也可以使用该值了 //如果 readings属性不在黑板类里。那么一个空的字典对象将会被 createDefaultValueFunc 工厂方法创建ReadingsProperty来完成。 context.Get(ReadingsProperty)[RegisterName] = value; }}//该任务关闭对设备的连接class CloseConnectionTask : Task{ protected override void Execute(Blackboard context) { IDevice deviceAPI = context.Get(InitiateDeviceTask.DeviceAPIProperty); deviceAPI.Close(); }}class Demo{ public void StartDemo() { ExecutionEngine engine = new ExecutionEngine(); engine.Enque(new InitiateDeviceTask()); //创建 deviceAPI 对象, 连接, 放在黑板类里 engine.Enque(new ReadRegisterTask() { RegisterName = "1.8.0" }); //使用设备api来读取一个值 engine.Enque(new ReadRegisterTask() { RegisterName = "3.8.0" }); //使用设备api来读取另一个值 engine.Enque(new ResetDeviceTask()); //使用设备api来重置设备 engine.Enque(new CloseConnectionTask()); //使用设备api来关闭设备连接 //引擎创建了一个黑板, /开启一个新线程并且一个接一个将任务传递给黑板对象来执行 // engine.Start(); }}

 

   黑板类另一种可能的使用情况就是一个支持插件的软件。如果需要的话允许插件进行通信,这种情况下属性改变的时候能够通知是很有用的。

   还有一件重要的事情是注意 BlackboardProperty 实例一般应该作为逻辑上拥有该属性的类的一个静态成员。那么既然那是静态的。同样的BlackboardProperty 实例就可以出现在多个黑板对象里。当某一个给定的属性。黑板对象里没有值的时候。他会请求BlackboardProperty 实例提供一个默认的值。缺省的值可能是一个引用类型,因此,如果你不想在多个黑板对象间共享同一个引用。在创建BlackboardProperty 对象的时候务必使用下面的构造函数。

public BlackboardProperty(string name, Func createDefaultValueFunc)
这就会使得默认的值不会在多个黑板对象间共享。

有意思的地方

    我应该说过了。这个方案一部分是受微软WPF中依赖属性的影响。还参考了我前段时间读到的一篇关于枚举类的文章

许可

本文包括源代码和文件在CPOL下授权。

原文地址: 

著作权声明:本文由 翻译,欢迎转载分享。请尊重作者劳动,转载时保留该声明和作者博客链接,谢谢!

转载于:https://www.cnblogs.com/lazycoding/archive/2012/10/16/2725743.html

你可能感兴趣的文章
高等代数葵花宝典—白皮书
查看>>
一种简单的图像修复方法
查看>>
基于DobboX的SOA服务集群搭建
查看>>
C#设计模式之装饰者
查看>>
[noip模拟20170921]模版题
查看>>
获取ip
查看>>
Spring Shell简单应用
查看>>
移动app可开发的意见于分析
查看>>
周总结7
查看>>
类似OutLook布局的开源控件XPanderControls
查看>>
Web前端工程师成长之路——知识汇总
查看>>
[2018-9-4T2]探索黑暗dark
查看>>
【学术信息】中科院2019年学术期刊分区-综合性期刊
查看>>
ShareObject离线存储相关
查看>>
C++ XML
查看>>
windows批处理 打开exe后关闭cmd
查看>>
Linux 安装中文包
查看>>
谷物大脑
查看>>
访问控制-禁止php解析、user_agent,PHP相关配置
查看>>
AgileEAS.NET之系统架构
查看>>