2020-07-28

面向对象23种设计模式系列(三)

本系列将和大家分享面向对象23种设计模式中常用的几种设计模式,本章主要简单介绍下行为型设计模式。

行为型设计模式:关注对象和行为的分离。(共11个)

  甩锅大法:把锅丢出去,只管自己,哪管洪水滔天。把不稳定的地方移出去,自己只写稳定的,能保证自身的稳定。

  没有什么设计模式是完美无缺的,一个设计模式就是解决一类的问题的,通常设计模式在解决一类问题的同时,还会带来别的问题,我们设计者要做的事儿,就是要扬长避短,充分发挥长处!

  很多时候,可能会融合应用多个设计模式,分别解决对应的问题。

下面我们结合几种具体场景来分析几种行为型设计模式。

1、模板方法模式(TemplateMethod Pattern)

模板方法设计模式:在基类父类定义流程,把可变逻辑分离到不同子类实现。

有个复杂的多步骤业务,定义一个父类(模板),模板负责完成流程,把步骤分解,固定不变的当前类--各不相同的子类--有的相同有的不同虚方法。

就是把部分行为做了分离。

好处:就是可以扩展,职责分明。

场景:银行客户端查询余额业务,它通常是包含几个步骤,例如:第一是校验用户名和密码,第二是查询真实余额,第三是计算利息,第四将最终信息展示给客户。对于不同的客户而言,这些步骤流程一般是固定不变的,但每一步的具体实现又有可能是不一样的。比如普通客户和VIP客户计算利息的利率是不一样的、活期和定期的利率也是不一样的,又比如普通客户和VIP客户的信息是存在不同的数据库而导致用户名和密码校验的具体实现可能也是不同的。这时候就可以考虑使用模板方法设计模式了,在基类中定义流程,把可变逻辑分离到不同子类实现。

下面我们来重点看下代码:

 抽象银行客户端:

using System;namespace TemplateMethodPattern{ /// <summary> /// 抽象银行客户端 /// </summary> public abstract class AbstractClient {  /// <summary>  /// 查询(模板方法)  /// </summary>  public void Query(int id, string name, string password)  {   if (this.CheckUser(id, password))   {    double balance = this.QueryBalance(id);    double interest = this.CalculateInterest(balance);    this.Show(name, balance, interest);   }   else   {    Console.WriteLine("账户密码错误");   }  }  /// <summary>  /// 用户名和密码校验  /// </summary>  public virtual bool CheckUser(int id, string password)  {   return DateTime.Now < DateTime.Now.AddDays(1);  }  /// <summary>  /// 查询真实余额  /// </summary>  public virtual double QueryBalance(int id)  {   return new Random().Next(10000, 1000000);  }  /// <summary>  /// 计算利息  /// 活期/定期/VIP 利率不同  /// </summary>  public abstract double CalculateInterest(double balance);  /// <summary>  /// 信息展示  /// 有些一样,有些不一样  /// </summary>  public virtual void Show(string name, double balance, double interest)  {   Console.WriteLine("尊敬的{0}客户,你的账户余额为:{1},利息为{2}",    name, balance, interest);  } }}

活期用户:

using System;namespace TemplateMethodPattern{ /// <summary> /// 银行客户端(活期用户) /// </summary> public class ClientCurrent: AbstractClient {  /// <summary>  /// 活期利率不同  /// </summary>  public override double CalculateInterest(double balance)  {   return balance * 0.001;  } }}

定期用户:

using System;namespace TemplateMethodPattern{ /// <summary> /// 银行客户端(定期用户) /// </summary> public class ClientRegular: AbstractClient {  /// <summary>  /// 定期利率不同  /// </summary>  public override double CalculateInterest(double balance)  {   return balance * 0.003;  } }}

VIP用户:

using System;namespace TemplateMethodPattern{ /// <summary> /// 银行客户端(VIP) /// </summary> public class ClientVip : AbstractClient {  /// <summary>  /// 计算利息  /// VIP利率不同  /// </summary>  public override double CalculateInterest(double balance)  {   return balance * 0.005;  }  /// <summary>  /// VIP的Show不一样  /// </summary>  public override void Show(string name, double balance, double interest)  {   Console.WriteLine("尊贵的{0} vip客户,您的账户余额为:{1},利息为{2}",    name, balance, interest);  } }}

使用如下:

using System;namespace TemplateMethodPattern{ /// <summary> /// 模板方法设计模式 ///  /// 有个复杂的多步骤业务,定义一个父类(模板),模板负责完成流程,把步骤分解,固定不变的当前类--各不相同的子类--有的相同有的不同虚方法。 /// 就是把部分行为做了分离。 /// 好处:就是可以扩展,职责分明。 /// 设计模式没那么神奇,只不过是把常用的东西跟场景结合,沉淀下来起个名字。 /// </summary> class Program {  static void Main(string[] args)  {   try   {    {     AbstractClient client = new ClientCurrent();     client.Query(101, "张三", "123456");    }    {     AbstractClient client = new ClientRegular();     client.Query(102, "李四", "000000");    }    {     AbstractClient client = new ClientVip();     client.Query(103, "王五", "251146");    }   }   catch (Exception ex)   {    Console.WriteLine(ex.Message);   }   Console.ReadKey();  } }}

运行结果:

2、观察者模式(Observer Pattern)

观察者模式:一个对象动作触发多个对象的行为(动作),通过观察者可以去掉对象的依赖,支持各种自定义和扩展。

主题(Subject)发生变化是导致观察者(Observer)触发动作的根本原因。观察者是观察主题的。

场景:猫(主题)叫了一声,触发了观察者的一系列动作,例如:老鼠跑、狗吼、鸡叫、孩子哭等等。

下面我们来重点看下代码:

首先我们来看下不是使用观察者模式实现的普通方法

using System;using System.Collections.Generic;using ObserverPattern.Observer;namespace ObserverPattern.Subject{ /// <summary> /// 一只神奇的猫 ///  /// 猫叫一声之后触发一系列动作 /// 老鼠跑、狗吼、鸡叫、孩子哭 /// </summary> public class Cat {  /// <summary>  /// 猫Miao了一声  ///   /// 依赖太多,任何一个对象改动,都会导致Cat变化。  /// 违背了单一职责,不仅自己miao,还要触发各种动作,不稳定。  /// 加一个/减一个/调整顺序 Cat都得改  /// Cat职责:  ///  1、Miao 2、触发一系列动作 这是需求  ///  实现上,这里多了一个,3、指定动作  /// </summary>  public void Miao()  {   Console.WriteLine("{0} Miao.....", this.GetType().Name);   //依赖了   new Mouse().Run(); //老鼠跑   new Dog().Wang(); //狗吼   new Chicken().Woo(); //鸡叫   new Baby().Cry(); //孩子哭  } }}

上面的这段代码就很不稳定,依赖太多,任何一个对象改动,都会导致Cat变化。违背了单一职责,不仅自己miao,还要触发各种动作,不稳定。加一个/减一个/调整顺序  Cat都得改。

竟然Cat不稳定,那我们就可以考虑将这一堆对象甩出去,自己不写让别人传递。

接下来我们使用观察者模式实现下:

首先定义一个接口,这个接口只是为了能够使多个对象产生关系,方便保存和调用

using System;namespace ObserverPattern{ /// <summary> /// 只是为了使多个对象产生关系,方便保存和调用 /// 方法本身其实没用 /// </summary> public interface IObserver {  void Action(); }}

让我们的观察者都去实现这个接口

using System;namespace ObserverPattern.Observer{ /// <summary> /// 老鼠 /// </summary> public class Mouse : IObserver {  public void Action()  {   this.Run();  }  public void Run()  {   Console.WriteLine("{0} Run", this.GetType().Name);  } }}
using System;namespace ObserverPattern.Observer{ /// <summary> /// 狗 /// </summary> public class Dog : IObserver {  public void Action()  {   this.Wang();  }  public void Wang()  {   Console.WriteLine("{0} Wang", this.GetType().Name);  } }}
using System;namespace ObserverPattern.Observer{ /// <summary> /// 鸡 /// </summary> public class Chicken : IObserver {  public void Action()  {   this.Woo();  }  public void Woo()  {   Console.WriteLine("{0} Woo", this.GetType().Name);  } }}
using System;namespace ObserverPattern.Observer{ /// <summary> /// 小孩 /// </summary> public class Baby : IObserver {  public void Action()  {   this.Cry();  }  public void Cry()  {   Console.WriteLine("{0} Cry", this.GetType().Name);  } }}

我们的猫(主题)添加一个观察者对象容器,让外部去添加对象

using System;using System.Collections.Generic;namespace ObserverPattern.Subject{ /// <summary> /// 一只神奇的猫 ///  /// 猫叫一声之后触发一系列动作 /// 老鼠跑、狗吼、鸡叫、孩子哭 /// </summary> public class Cat {  /// <summary>  /// 存放观察者的容器  /// </summary>  private List<IObserver> _observerList = new List<IObserver>();  /// <summary>  /// 添加观察者  /// </summary>  public void AddObserver(IObserver observer)  {   this._observerList.Add(observer);  }  /// <summary>  /// 观察者模式Miao  /// </summary>  public void MiaoObserver()  {   Console.WriteLine("{0} MiaoObserver.....", this.GetType().Name);   if (this._observerList != null && this._observerList.Count > 0)   {    foreach (var item in this._observerList)    {     item.Action();    }   }  } }}

使用如下:

using System;using ObserverPattern.Observer;using ObserverPattern.Subject;namespace ObserverPattern{ /// <summary> /// 观察者模式 /// 对象和行为的分离 /// </summary> class Program {  static void Main(string[] args)  {   try   {    {     Console.WriteLine("***************Observer***************");     Cat cat = new Cat();     cat.AddObserver(new Mouse());     cat.AddObserver(new Chicken());     cat.AddObserver(new Baby());     cat.AddObserver(new Dog());     cat.AddObserver(new Mouse());     cat.MiaoObserver();    }   }   catch (Exception ex)   {    Console.WriteLine(ex.Message);   }   Console.ReadKey();  } }}

运行结果:

上面的这种是标准的观察者模式,是使用面向对象的方式实现的。其实观察者模式还有一种更优雅的写法,就是使用事件来实现的。如下所示:

using System;namespace ObserverPattern.Subject{ /// <summary> /// 一只神奇的猫 ///  /// 猫叫一声之后触发一系列动作 /// 老鼠跑、狗吼、鸡叫、孩子哭 /// </summary> public class Cat {  private event Action MiaoHandler;  public void MiaoEvent()  {   Console.WriteLine("{0} MiaoEvent.....", this.GetType().Name);   if (this.MiaoHandler != null)   {    foreach (Action item in this.MiaoHandler.GetInvocationList())    {     item.Invoke();    }   }  } }}

3、责任链模式(ResponsibilityChain Pattern)

责任链模式:请求的处理流程,沿着链子顺序执行,还允许链子扩展和订制。被称为行为型设计模式的巅峰之作。

Context(上下文环境):用来保存业务处理中的参数--中间结果--最终结果。是行为型设计模式常用的标配。

为什么说Context是行为型设计模式常用的标配呢?因为行为型设计模式关注的是对象和行为的分离,而方法(行为)在处理过程中就需要一些参数并且有返回值,这些信息就存放在Context上下文环境中。

场景:就以我们的请假审批流程为例,我们都知道不同的角色能够审批的假期时长是不一样的,例如:直接上级(SM)有权批1天的假期,主管(Charge)有权批3天的假期,经理(Manager)有权批7天的假期,总监(Chief)有权批30天假期,而我们的董事长(CEO)有权批300天的假期。下面我们通过代码来看下如何实现这个审批的功能。

首先我们来看下可能会想到的解决思路:

using System;namespace ResponsibilityChainPattern{ class Program {  static void Main(string[] args)  {   //请假申请   ApplyContext context = new ApplyContext()   {    Id = 1001,    Name = "隔壁老王",    Hour = 60,    Description = "有急事需要请假几天",    AuditResult = false,    AuditRemark = ""   };   {    AbstractAuditor sm = new SM()    {     Name = "直接上级"    };    sm.Audit(context);    if (!context.AuditResult)    {     AbstractAuditor charge = new Charge()     {      Name = "主管"     };     charge.Audit(context);     if (!context.AuditResult)     {      AbstractAuditor manager = new Manager()      {       Name = "经理"      };      manager.Audit(context);      if (!context.AuditResult)      {       //找下一环节      }     }    }    if (context.AuditResult)    {     Console.WriteLine(context.AuditRemark);    }    else    {     Console.WriteLine("不干了!");    }   }  } }}

其实上面的这段代码我们只是按照面向对象的思路翻译了一遍,完全没有设计、没有加工、没有思考。这里就有一个很大的弊端,比如:此处隔壁老王请假60小时,首先他去找直接上级请假,发现直接上级没有批假权限。然后他又去找主管请假,发现主管也没有批假权限。接着他又要去找经理、找总监请假,如此找下去直到找到有批假权限人员为止,这就很不符合常理。正确的思路应该是这样的,隔壁老王直接将请假申请交给直接上级审批,对于直接上级来说,如果是权限范围内则审批通过,如果权限范围外则转交下一环节审批。下一环节的审批人员也是如此。最终我们就可以将请求的处理流程,沿着链子顺序执行。

接下来我们使用责任链模式来实现下这个功能:

请假申请上下文:

using System;namespace ResponsibilityChainPattern{ /// <summary> /// 请假申请 /// Context(上下文环境):用来保存业务处理中的参数--中间结果--最终结果。是行为型设计模式常用的标配。 /// 把行为转移 /// </summary> public class ApplyContext {  public int Id { get; set; }  /// <summary>  /// 姓名  /// </summary>  public string Name { get; set; }  /// <summary>  /// 请假时长  /// </summary>  public int Hour { get; set; }  /// <summary>  /// 请假理由  /// </summary>  public string Description { get; set; }  /// <summary>  /// 审核结果  /// </summary>  public bool AuditResult { get; set; }  /// <summary>  /// 审核备注  /// </summary>  public string AuditRemark { get; set; } }}

抽象审核人员:

using System;namespace ResponsibilityChainPattern{ /// <summary> /// 抽象审核人员 /// </summary> public abstract class AbstractAuditor {  public string Name { get; set; }  /// <summary>  /// 审核  /// </summary>  public abstract void Audit(ApplyContext context);  /// <summary>  /// 下一个审核者  /// </summary>  private AbstractAuditor _nextAuditor = null;  /// <summary>  /// 指定下一个审核者  /// </summary>  public void SetNext(AbstractAuditor auditor)  {   this._nextAuditor = auditor;  }  /// <summary>  /// 交给下一个审核人员审核  /// </summary>  protected void AuditNext(ApplyContext context)  {   if (this._nextAuditor != null)   {    this._nextAuditor.Audit(context);   }   else   {    context.AuditResult = false;    context.AuditRemark = "不允许请假!";   }  } }}

下面我们不同角色的审核人员都去继承抽象的审核人员,审核职责是:权限范围内则审批通过,权限范围外则转交下一环节审批。

using System;namespace ResponsibilityChainPattern{ /// <summary> /// 直接上级 ///  /// 职责问题: ///  1、权限范围内,审批通过。 ///  2、权限范围外,转交下一环节审批。 /// </summary> public class SM : AbstractAuditor {  public override void Audit(ApplyContext context)  {   Console.WriteLine($"This is {this.GetType().Name} {this.Name} Audit");   if (context.Hour <= 8)   {    context.AuditResult = true;    context.AuditRemark = "允许请假!";   }   else   {    base.AuditNext(context); //转交下一环节审批   }  } }}
using System;namespace ResponsibilityChainPattern{ /// <summary> /// 主管 /// </summary> public class Charge : AbstractAuditor {  public override void Audit(ApplyContext context)  {   Console.WriteLine($"This is {this.GetType().Name} {this.Name} Audit");   if (context.Hour <= 24)   {    context.AuditResult = true;    context.AuditRemark = "允许请假!";   }   else   {    base.AuditNext(context); //转交下一环节审批   }  } }}
using System;namespace ResponsibilityChainPattern{ /// <summary> /// 经理 /// </summary> public class Manager : AbstractAuditor {  public override void Audit(ApplyContext context)  {   Console.WriteLine($"This is {this.GetType().Name} {this.Name} Audit");   if (context.Hour <= 56)   {    context.AuditResult = true;    context.AuditRemark = "允许请假!";   }   else   {    base.AuditNext(context); //转交下一环节审批   }  } }}
using System;namespace ResponsibilityChainPattern{ /// <summary> /// 总监 /// </summary> public class Chief : AbstractAuditor {  public override void Audit(ApplyContext context)  {   Console.WriteLine($"This is {this.GetType().Name} {this.Name} Audit");   if (context.Hour <= 240)   {    context.AuditResult = true;    context.AuditRemark = "允许请假!";   }   else   {    base.AuditNext(context); //转交下一环节审批   }  } }}
using System;namespace ResponsibilityChainPattern{ /// <summary> /// 董事长 /// </summary> public class CEO : AbstractAuditor {  public override void Audit(ApplyContext context)  {   Console.WriteLine($"This is {this.GetType().Name} {this.Name} Audit");   if (context.Hour <= 2400)   {    context.AuditResult = true;    context.AuditRemark = "允许请假!";   }   else   {    base.AuditNext(context); //转交下一环节审批   }  } }}

最后来看下如何使用:

using System;namespace ResponsibilityChainPattern{ /// <summary> /// 建造者 /// </summary> public class AuditorBuilder {  public static AbstractAuditor Build()  {   AbstractAuditor sm = new SM()   {    Name = "直接上级"   };   AbstractAuditor charge = new Charge()   {    Name = "主管"   };   AbstractAuditor manager = new Manager()   {    Name = "经理"   };   AbstractAuditor chief = new Chief()   {    Name = "总监"   };   AbstractAuditor ceo = new CEO()   {    Name = "董事长"   };   //转交下一环节,允许链子扩展和订制   sm.SetNext(charge);   charge.SetNext(manager);   manager.SetNext(chief);   chief.SetNext(ceo);   return sm;  } }}
using System;namespace ResponsibilityChainPattern{ /// <summary> /// 责任链模式:请求的处理流程,沿着链子顺序执行,还允许链子扩展和订制。被称为行为型设计模式的巅峰之作。 /// </summary> class Program {  static void Main(string[] args)  {   //请假申请   ApplyContext context = new ApplyContext()   {    Id = 1001,    Name = "隔壁老王",    Hour = 60,    Description = "有急事需要请假几天",    AuditResult = false,    AuditRemark = ""   };   {    AbstractAuditor auditor = AuditorBuilder.Build();    auditor.Audit(context);    if (!context.AuditResult)    {     Console.WriteLine("不干了!");    }    else    {     Console.WriteLine(context.AuditRemark);    }   }  } }}

运行结果:

 

Demo源码:

链接:https://pan.baidu.com/s/1OSjocBaO_m7FWw25RW4jRg 提取码:bq9q

此文由博主精心撰写转载请保留此原文链接:https://www.cnblogs.com/xyh9039/p/13340315.html

版权声明:如有雷同纯属巧合,如有侵权请及时联系本人修改,谢谢!!!

面向对象23种设计模式系列(三)prime dayfocalprice海鹰数据跨境电子商务政策再迎利好 政策升级加码重拳打击违规跨境支付交易并举亚马逊FBA发货为何会分仓?卖家如何选择分仓与合仓?为什么Shopee单价比1688低一半?因为没有中间商赚差价 尼泊尔尼瓦尔人:探秘没有寡妇的民族(全文)12月份去哪里旅游好 体会西安的前世今生去巴马当"候鸟人" 寻找长生秘诀

No comments:

Post a Comment