• 关于生活中的设计模式(二)


    上篇文章中讨论了以“每个过程”为观察点来处理订单流程。我们定义了一个接口IAction

       1:  public interface IAction
       2:  {
       3:        void DoA();
       4:        void DoB();
       5:        void DoC();
       6:        ....
       7:  }

    还定义了每个过程“拍下商品”、“付钱到支付宝”…

       1:  public class 拍下商品:IAction
       2:  {
       3:      void DoA();
       4:      void DoB();
       5:      void DoC();
       6:  }
       7:   
       8:  public class 付款到支付宝:IAction
       9:  {
      10:      void DoA();
      11:      void DoB();
      12:      void DoC();
      13:  }

    以及还有一个负责将每个“过程”进行串联的管理类ActionManager

       1:  public class ActionManager
       2:  {
       3:      private IAction _action =null;
       4:      
       5:      public ActionManager(IAction action)
       6:      {
       7:           _action =action;
       8:      }
       9:      
      10:      public void DoSomething()
      11:      {
      12:          _action.DoA();
      13:          _action.DoB();
      14:          _action.DoC();
      15:          ...
      16:      }
      17:  }

    最后展示了调用方式:

       1:  var actionMgr = new ActionManager(拍下商品);
       2:  actionMgr.DoSomething();

    由于实际项目中呢?我们对于IAction每个“动作”(即DoA、DoB、DoC),都会做一些其他的与业务不怎么相关(如日志)或者说在不影响原先的业务逻辑前提下,我要增加一些业务逻辑(比如业务要求在4点之后客户不能确认收货)

    一开始我们的想法都是比较简单:直接在“确认收货”该过程里面修改逻辑代码!!以及在“每个过程”都添加日志的模块。然后将代码编译成dll文件,做个发布包就OK了。

    这样的做法有点违反设计模式的“开闭原则”:“说软件实体(类,模块,函数等)应该可以扩展,但是不可以修改”。

    那么有没有比较优美的做法来改变“直接修改类”的问题呢?

    优化版做法

    在23种设计模式中就有一个符合解决上述问题的解决方案——“装饰者模式”。我的理解就是一个“包装类”,生活中有很多这种例子比如中秋节买的月饼,里面的东西都是差不多的可是在经过盒子的“包装”后让人顿时感觉就“高端 大气 上档次”了。

    装饰者模式:自己内部拥有一个业务接口(即IAction),而且自己也实现这个业务接口(IAction)。

    代码如下:

       1:  public void ProcedureWrapper:IAction
       2:  {
       3:      private IAction _innerAction=null;
       4:      
       5:      public ProcedureWrapper(IAction action)
       6:      {
       7:          _innerAction=action;
       8:      }
       9:      
      10:      public void DoA()
      11:      {
      12:        //做一些其他的事情;比如4点之后不执行改动作,直接return;
      13:          
      14:        try
      15:        {
      16:          _innerAction.DoA();
      17:        }catch
      18:        {
      19:          //记录些日志
      20:        }
      21:        
      22:        //改过程完成之后;做一些事情。
      23:      }
      24:      
      25:      public void DoB()
      26:      {
      27:        //做一些其他的事情;比如4点之后不执行改动作,直接return;
      28:          
      29:        try
      30:        {
      31:          _innerAction.DoB();
      32:        }catch
      33:        {
      34:          //记录些日志
      35:        }
      36:        
      37:        //改过程完成之后;做一些事情。
      38:      }
      39:      
      40:      public void DoC()
      41:      {
      42:        //做一些其他的事情;比如4点之后不执行改动作,直接return;
      43:          
      44:        try
      45:        {
      46:          _innerAction.DoC();
      47:        }catch
      48:        {
      49:          //记录些日志
      50:        }
      51:        
      52:        //改过程完成之后;做一些事情。
      53:      }
      54:  }

    然后我们修改下ActionManager

       1:  public class ActionManager
       2:  {
       3:      private IAction _action = null;
       4:      
       5:      public ActionManager(IAction action)
       6:      {
       7:          if(action!=null)
       8:          {
       9:              _action=ProcedureWrapper(action);
      10:          }
      11:      }
      12:      
      13:      public void DoSomething()
      14:      {
      15:          if(action!=null)
      16:          {
      17:              _action.DoA();
      18:              _action.DoB();
      19:              _action.DoC();
      20:          }
      21:      }
      22:  }

    这样的做法虽然满足了上述的要求,但是有个弊端就是每个方法几乎都被定死了即“DoC前”—>“DoC中”—>“DoC后”。在“DoC前和后”都已经规定死了要做什么,要是有些“过程”我们比较特殊(即我不要记日志,或者记日志的方式不一样了)。

    面对这个问题我能想到的有两种做法:一个用接口IMethodBefore,IMethodAfter;另一种做法就是定义两个事件:OnActionBefore、OnActionAfter

    事件的做法大家应该都会做的,也是比较用到的毕竟.Net就是以事件为驱动的。这里我就讲下接口的做法

    接口定义如下:

       1:  public interface IMethodBefore
       2:  {
       3:      void DoBefore();
       4:  }
       5:   
       6:  public interface IMethodAfter
       7:  {
       8:      void DoAfter();
       9:  }

    然后呢我定义一个默认的做法类DefaultMethodAction

       1:  public class DefaultMethodAction:IMethodBefore,IMethodAfter
       2:  {
       3:      public DoBefore()
       4:      {
       5:          //做一些通用的事情;如上述的“4点之后不执行该动作”
       6:      }
       7:      
       8:      public DoAfter()
       9:      {
      10:          //做一些通用的事情;如上述的“4点之后不执行该动作”
      11:      }
      12:  }

    DefaultMethodAction用于如果没有其他的业务要求我们就做默认的的动作。

    自定义的IMethodBefore,IMethodAfter该如何实现呢?这种实现有很多种方式,这里提供一种我的方式—反射、特性。

    特性定义如下:

       1:  public class BeforeAttribute : Attribute
       2:  {
       3:      public IMethodBefore MethodBefore { get; private set; }
       4:      public BeforeAttribute(IMethodBefore before)
       5:      {
       6:          MethodBefore = before;
       7:      }
       8:  }
       9:   
      10:  public class AfterAttribute : Attribute
      11:  {
      12:      public IMethodAfter MethodAfter { get; private set; }
      13:      public BeforeAttribute(IMethodAfter before)
      14:      {
      15:          MethodAfter = before;
      16:      }
      17:  }

    修改下ProcedureWrapper类代码如下:

       1:  public class ProcedureWrapper : IAction
       2:  {
       3:      private IMethodBefore _defaultBefore = null;
       4:      private IMethodAfter _defaultAfter = null;
       5:      private IAction _action = null;
       6:      /// <summary>
       7:      /// key:方法名称 
       8:      /// </summary>
       9:      private Dictionary<string, IMethodBefore> methodBefore = new Dictionary<string, IMethodBefore>();
      10:   
      11:      /// <summary>
      12:      ///  key:方法名称 
      13:      /// </summary>
      14:      private Dictionary<string, IMethodAfter> methodAfter = new Dictionary<string, IMethodAfter>();
      15:      public ProcedureWrapper(IAction action)
      16:      {
      17:          _defaultBefore = new DefaultMethodAction();
      18:          _defaultAfter = new DefaultMethodAction();
      19:          _action = action;
      20:   
      21:          var methods = _action.GetType().GetMethods();
      22:          foreach (var methodInfo in methods)
      23:          {
      24:              var methodName = methodInfo.Name.ToLower();
      25:              var attrBefore =methodInfo.GetCustomAttributes(typeof(BeforeAttribute),true);
      26:              if (attrBefore.Any())
      27:              {
      28:                  var before = attrBefore[0] as BeforeAttribute;
      29:                  if (before != null) methodBefore.Add(methodName,before.MethodBefore);
      30:              }
      31:   
      32:              var attrAfter =methodInfo.GetCustomAttributes(typeof(AfterAttribute),true);
      33:              if (attrAfter.Any())
      34:              {
      35:                    var after = attrBefore[0] as AfterAttribute;
      36:                  if (after != null) methodAfter.Add(methodName,after.MethodAfter);
      37:              }
      38:          }
      39:      }
      40:      public void DoA()
      41:      {
      42:          IMethodBefore before = _defaultBefore;
      43:          IMethodAfter after = _defaultAfter;
      44:          const string key = "doa";
      45:          if (methodBefore.ContainsKey(key))
      46:          {
      47:              before = methodBefore[key];
      48:          }
      49:   
      50:          before.DoBefore();
      51:   
      52:          try
      53:          {
      54:              _action.DoA();
      55:          }
      56:          catch (Exception)
      57:          {
      58:              
      59:              throw;
      60:          }
      61:   
      62:           if (methodAfter.ContainsKey(key))
      63:          {
      64:              after = methodAfter[key];
      65:          }
      66:          after.DoAfter();
      67:      }
      68:   
      69:      public void DoB()
      70:      {
      71:           IMethodBefore before = _defaultBefore;
      72:          IMethodAfter after = _defaultAfter;
      73:          const string key = "dob";
      74:          if (methodBefore.ContainsKey(key))
      75:          {
      76:              before = methodBefore[key];
      77:          }
      78:   
      79:          before.DoBefore();
      80:   
      81:           try
      82:          {
      83:              _action.DoB();
      84:          }
      85:          catch (Exception)
      86:          {
      87:              
      88:              throw;
      89:          }
      90:   
      91:           if (methodAfter.ContainsKey(key))
      92:          {
      93:              after = methodAfter[key];
      94:          }
      95:          after.DoAfter();
      96:      }
      97:   
      98:      public void DoC()
      99:      {
     100:           IMethodBefore before = _defaultBefore;
     101:          IMethodAfter after = _defaultAfter;
     102:          const string key = "doc";
     103:          if (methodBefore.ContainsKey(key))
     104:          {
     105:              before = methodBefore[key];
     106:          }
     107:   
     108:          before.DoBefore();
     109:   
     110:           try
     111:          {
     112:              _action.DoC();
     113:          }
     114:          catch (Exception)
     115:          {
     116:              
     117:              throw;
     118:          }
     119:   
     120:           if (methodAfter.ContainsKey(key))
     121:          {
     122:              after = methodAfter[key];
     123:          }
     124:          after.DoAfter();
     125:      }
     126:  }

    然后就是我们的每个过程“用法”

       1:  public class 拍下商品:IAction
       2:  {
       3:      //在这里你可以打上BeforeAttribute或者AfterAttribute,也可以不打上面的两个标签,让其走默认的做法
       4:      public void DoA()
       5:      {
       6:      
       7:      }
       8:      
       9:      public void DoB()
      10:      {
      11:      
      12:      }
      13:      
      14:      public void DoC()
      15:      {
      16:      
      17:      }
      18:  }

    我们最终的调用方式还是和原来一样,代码如下:

       1:  var actionMgr = new ActionManager(拍下商品);
       2:  actionMgr.DoSomething();
  • 相关阅读:
    04 链表(上):如何实现LRU缓存淘汰算法?
    03 数组:为什么很多编程语言中数组都从0开始编号?
    02 复杂度分析(下):浅析最好、最坏、平均、均摊时间复杂度
    01 复杂度分析(上):如何分析、统计算法的执行效率和资源消耗?
    Winform PictureBox图片旋转
    我的第一篇博客
    redis分布式锁实现与思考
    java 时间字符串中毫秒值时有时无,怎么解析
    spring 接收处理 json 类型的请求(spring 默认使用jackson 处理接收的数据), json 字段的中的Date 类型会自动 转换为 Long 类型
    java 中的正则使用
  • 原文地址:https://www.cnblogs.com/hankskfc/p/3408148.html
Copyright © 2020-2023  润新知