跟着项目学设计模式(十) 观察者模式

接上文,既然一个服务有多个消费者,在具体的功能实现上,会遇到许多有细微差别的地方,比如:

对商品的修改有些服务需要短信通知后台管理员,有些服务需要通过邮件+站内消息的形式通知相关用户,有些服务要求无需任何通知操作。

为了能兼容这些矛盾,我们尝试去添加相应的接口如下:

public interface IOrder  
{  
    //编辑
    bool OrderEdit(Order entity);  
    //编辑附带短信
    bool OrderEditWithSMS(Order entity);
    //编辑附带站内消息和邮件
    bool OrderEditWithMESSAGE_MAIL(Order entity);
}

一旦需求有了新的变化,比如,有的服务要求编辑商品后发送站内消息+邮件,有的服务要求发送短信+站内消息+邮件等等,就需要添加新的接口。这就好像是求解一道功能之间排列组合的问题,n个功能的结果就是n的阶乘。

我们现在选择在接口设计时就把结果一一列出,声明n的阶乘个方法。一不小心就违反了单一职责原则、接口隔离原则和开闭原则。

造成这种尴尬局面的根本原因是:除了最基本的编辑之外,其他的诸如短信、邮件、站内消息等附加功能是动态的,只有在调用时,才确定具体需要哪些附加功能。

是的,这里并不需要保证数据强一致性,什么短信、邮件、站内消息都是事件驱动模型里的东西,是异步调用的,并不会阻塞订单编辑操作,正是这些并不需要事务包裹的附加功能降低了模块的内聚。

基本思路:

方案1:不改变接口,而是创建一个包装对象,也就是装饰来包裹真实的对象,动态的扩展一个对象的功能=》装饰器模式

方案2:业务逻辑层添加一个消息接口,直接把消息发送功能暴露给调用者,调用者自行决定何时何地调用。

zhe

下面来看方案2大体是如何实现的:

1)定义的消息接口


public interface IMessage
{
   void send(Message entity);
}

public class SMSBLL:IMessage
{
   ...
}

public class EmailBLL:IMessage
{
   ...
}

 

2)服务项目订单编辑:


public bool Edit(string id,Message entity)
{
   if(IOrder.Edit(id))
   {
       //发送短信
       SMS.Send(entity);
       //发送邮件
       Email.Send(entity);
       return true;
   }
   return false;
}

 

观察者模式

观察者模式(有时又被称为模型-视图(View)模式、源-收听者(Listener)模式或从属者模式)是软件设计模式的一种。在此种模式中,一个目标物件管理所有相依于它的观察者物件,并且在它本身的状态改变时主动发出通知。这通常透过呼叫各观察者所提供的方法来实现。此种模式通常被用来实现事件处理系统。

 观察者设计模式定义了对象间的一种一对多的组合关系,以便一个对象的状态发生变化时,所有依赖于它的对象都得到通知并执行。观察者模式属于行为型模式。

目标对象(ISubject)订单

观察者(IObserver):短信、站内消息、邮件

1)封装观察者

//封装消息体
public class Message
{
    public string id{get;set;}
    public string message{get;set;}
}

//观察者
public interface IObserver {
    void send(Message entity);
}

//短信观察者
public class SMSObserver:IObserver
{
   public void send(Message entity)
   {
       //调用短信服务
   }
}

//站内消息观察者
public class MessageObserver:IObserver
{
   public void send(Message entity)
   {
       //调用站内消息服务
   }
}

。。。

2)创建目标对象接口,包括一个观察者队列属性和注册观察者、删除观察者、通知所有观察者三个方法,并在订单中实现目标对象接口:

public interface ISubject
{
    IList<IObser> Observers{get;set;} 
    //注册
    void RegisterObserver(IObserver o);
    //删除
    void RemoveObserver(IObserver o);
    //通知
    void NotifyObserver();
}

public interface IOrder:ISubject  
{ 
    IList<IObser> Observers{get;set;} 
    //编辑
    bool OrderEdit(Order entity);  
}

public class OrderBLL:IOrder
{
    public IList<IObserver> Observers{get;set;}

    public void RegisterObserver(IObserver o)
    {
      if(!Observers.Contains(O))
      {
          Observers.Add(o);
      }
    }
    //删除
    public void RemoveObserver(IObserver o)
    {
      if(!Observers.Contains(O))
      {
          Observers.Remove(o);
      }
    }
    //通知所有观察者
    public void NotifyObserver(Message entity)
    {
        foreach(var item in Observers)
        {
            item.send(entity);
        }
    }
}

类中实现了注册、删除和通知方法,用以维护观察者列表,基本满足了我们的需求。

结构型设计模式是从程序的结构上解决模块之间的耦合问题,如装饰器模式,在不改变原接口的情况下,通过新创建的类对原来的对象的进行扩展。而行为型设计模式是在不同对象之间划分责任和算法的抽象化,如观察者模式,通过对象关联的方式来分配多个职责。

我们在IOberserve这个接口中声明了Observers这个属性,目的是保证主题实现类中的消息列表属性必须统一使用IList类型,防止在不同的实现类里出现诸如数组、链表、队列、二叉树等等五花八门的类型。

那就是说主题实现类中的观察者列表是一个属性,是public类型,直接将这个列表暴露给了调用者,调用者可以直接操作订单对象的Observers属性,例如可以通过操作list类型自带的Add方法来注册,而不必通过类内部的实现的注册方法,在订单编辑之前,就可以通过遍历该属性就可以发送消息 。这破坏了类的封装,减弱代码的安全性。

我们需要对这个属性字段的进一步封装,来约束调用者只能通过主题类内部实现的注册、删除和通知方法来操作Observers属性。

事件包含一个私有的委托对象只能在类内部触发,封装性和易用性特别好,这里我们采用事件优化之前的代码。

        //消息委托
        public delegate void SendEventHandler(object sender, EventArgs e);

        //订单
        public interface IOrder:ISubject
        {
            //事件
            event SendEventHandler SendEvent;
            //编辑
            bool OrderEdit(Order entity);
        }

        public class OrderBLL:IOrder
        {
            public event SendEventHandler SendEvent;


            public bool OrderEdit(Order entity)
            {
                var val=new OrderDal().OrderEdit(entity);
                //事件广播
                _SendEvent(message,null);
                return val;
            }
        }

事件限制了委托对象只能通过类内部的事件访问器Add和Remove方法注册和删除,事件的广播只能通过调用类内部的OrderEdit方法。

同一般的属性,事件是也是通过事件访问器来封装的,非简写情况,代码如下:

        public class OrderBLL:IOrder
        {
            private SendEventHandler _SendEvent;
            public event SendEventHandler SendEvent
            {
                //注册
                add
                {
                    if(value!=null)
                    {
                        _SendEvent += value;
                    }
                }
                //删除
                remove
                {
                    if (value==_SendEvent)
                    {
                        _SendEvent -= value;
                    }
                }
            }

            public bool OrderEdit(Order entity)
            {
                var val=new OrderDal().OrderEdit(entity);
                //事件广播
                _SendEvent(message,null);
                return val;
            }
        }

可以发现,事件访问器的add和remove方法都是void类型的。而属性访问器里只有set方法是void类型,get方法有返回值,类型同字段类型,这才是事件封装性好的关键。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值