今天带给大家的是大话设计模式中的行为型模式,行为型总共分为了两组,即观察者组和策略模式组。总共是十一个模式。
用来识别对象之间常用的交流模式并加以实现,即不仅表达了对象和类还表达了他们之间的交互,涉及到了对象和算法的分配
状态:主要解决控制一个对象状态转换条件表达式过于复杂的情况。可以把复杂的判断逻辑化。如果状态判断变化太简单,就没必要用此模式.
何时用 | 当一个对象的行为取决于它的状态,并且它必须在运行时刻根据状态改变它的行为时 |
---|---|
优点 | 通过讲各种状态转移逻辑分布到state子类之间,消除庞大分支,较少相互间的依赖 |
一天的工作状态带入
工作类 :没有过长的分支判断,因为封装到了各自的子状态里
class Context
{
private State state;
public Context(State state) //定义context的初始状态
{
this.state = state;
}
public State State //用于当前状态和设置状态
{
get { return state; }
set { state = value;
Console.WriteLine("当前状态:"+state.GetType().Name);
}
}
public void Request() //对请求做处理并设置下一状态
{
state.Handle(this);
}
}
抽象状态
abstract class State
{
public abstract void Handle(Context context);
}
具体状态
class ConcreteStateA : State
{
public override void Handle(Context context)
{
context.State = new ConcreteStateB();//设置A的下一状态是B
}
}
具体状态
class ConcreteStateB : State
{
public override void Handle(Context context)
{
context.State = new ConcreteStateA();//设置B的下一状态是A
}
}
客户端
Context c = new Context(new ConcreteStateA());
c.Request();
c.Request();
c.Request(); //不断的请求,同时更改状态
职责链:使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。客户端并不知道哪一个对象最终处理这个请求,这样系统的更改可以在不影响客户端的情况下动态地重新组织和分配责任。
优点 | 请求者不用管哪个对象来处理,反正该请求会被处理。由客户端来定义链的结构,可以随时地增加或修改处理一个请求地结构。增强了给对象指派职责地灵活性 |
---|---|
缺点 | 一个请求极有可能到了链地末端都得不到处理,或者因为没有正确配置而得不到处理。 |
管理者,请示地接口
abstract class Handler
{
protected Handler successor;
public void SetSuccessor(Handler successor) //设置继任者
{
this.successor = successor;
}
public abstract void HandleRequest(int request); //处理请求地抽象方法
}
经理
class ConcreteHandler1 : Handler
{
public override void HandleRequest(int request)
{
if (request>=0 && request<10)
{
Console.WriteLine("{0}处理请求{1}", this.GetType().Name, request);
}
else if (successor !=null)
{
successor.HandleRequest(request); //转移到下一位
}
}
}
总监
class ConcreteHandler2 : Handler
{
public override void HandleRequest(int request)
{
if (request >= 10 && request < 20)
{
Console.WriteLine("{0}处理请求{1}", this.GetType().Name, request);
}
else if (successor != null)
{
successor.HandleRequest(request); //转移到下一位
}
}
}
总经理
class ConcreteHandler3 : Handler
{
public override void HandleRequest(int request)
{
if (request >= 20 && request < 30)
{
Console.WriteLine("{0}处理请求{1}", this.GetType().Name, request);
}
else if (successor != null)
{
successor.HandleRequest(request); //转移到下一位
}
}
}
客户端
Handler h1 = new ConcreteHandler1();
Handler h2 = new ConcreteHandler2();
Handler h3 = new ConcreteHandler3();
h1.SetSuccessor(h2); //设置职责链上家与下家
h2.SetSuccessor(h3);
int[] requests = { 2, 5, 14, 22 };
foreach (int request in requests) //循环给最小处理者提交请求,不同地数额,由不同权限处理者处理
{
h1.HandleRequest(request);
}
命令:将一个请求封装为一个对象,从而使你可用不同地请求对客户进行参数化;对请求排队或记录请求日志,以及支持可撤销地操作。
优点 | 能较容易的设计一个命令队列,能允许接受请求的一方决定是否要否决请求,增加新的具体命令类很容易,可以容易的实现对请求的撤销和重做,最后命令模式把请求一个操作的对象与指导怎么执行一个操作的对象分隔开。 |
---|---|
注意点 | 根据敏捷开发原则,不要为代码添加基于猜测的、实际不需要的功能。如命令模式虽然支持撤销、恢复操作功能,但还不清楚是否需要这个功能时,不要使用命令模式。这也是敏捷开发原则所提倡的。 |
烤羊肉串、鸡翅例子带入
抽象命令类
abstract class Command
{
protected Receiver receiver;
public Command(Receiver receiver)
{
this.receiver = receiver;
}
abstract public void Execute();
}
接受者,即烤羊肉串者
class Receiver
{
public void Action()
{
Console.WriteLine("执行请求");
}
}
服务员,即要求该命令执行这个请求
class Invoker
{
private Command command;
public void SetCommand(Command command)
{
this.command = command;
}
public void ExecuteCommand()
{
command.Execute();
}
}
具体命令,如烤羊肉串,烤鸡翅
class ConcreteCommand : Command
{
public ConcreteCommand(Receiver receiver) : base(receiver) { }
public override void Execute()
{
receiver.Action();
}
}
观察者:一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态发生变化时,会通知所有观察者对象,使它们能够自动更新自己。具体的观察者是谁,主题对象可以不用知道。任何一个观察者也不需要知道其他观察者的存在。
如:换手机号码了,通知全班同学更新我的手机号,好及时联系
何时用 | 当一个对象的改变同时改变其他对象时,并且它不知道具体有多少对象有待改变时。 |
---|---|
特点 | 观察者模式实际上就是在解除耦合,让耦合的双方都依赖于抽象而不是依赖于具体,从而使得各自的变化都不会影响另一边的变化。 |
不足 | 抽象通知者与观察者之间互相不知道,尽管已经用依赖倒转原则,没有抽象观察者这样的接口,还是不能完成通知 |
由上面的不足延伸出了,委托,委托就是一种引用方法的类型,一旦为委托分配了方法,委托将与该方法具有完全相同的行为。
声明一个委托,名称叫“EventHandler(事件处理程序)” ,代码:delegate void EventHandler();
用委托的前提,委托对象所搭载的所有方法必须具有相同的原形和形式,也就是拥有相同的参数列表和返回值类型。
老板来了前台通知后台看NAB和看股票的同事为例子带入
//抽象通知类
abstract class Subject
{
private IList<Observer> observers = new List<Observer>();
//增加观察者
public void Attach(Observer observer)
{
observers.Add(observer);
}
public void Detach(Observer observer)
{
observers.Remove(observer);
}
public void Notify()
{
foreach (Observer o in observers)
{
o.Update();
}
}
}
//抽象观察者
abstract class Observer
{
public abstract void Update();
}
//具体通知者,如前台小姐,这里可用事件委托
class ConcreteSubject : Subject
{
public event EventHandler Update;//声明一"EventHandler(事件处理程序)"的委托事件,名称为update(更新)
public void Notify()
{
Update(); //访问通知方法时,调用“更新”
}
private string subjectState;
public string SubjectState
{
get { return subjectState; }
set { subjectState = value; }
}
}
//具体观察者,如看NBA的同事
class ConcreteObserver : Observer
{
private string name;
private string observerState;
private ConcreteSubject subject;
public ConcreteObserver(ConcreteSubject subject,string name)
{
this.subject = subject;
this.name = name;
}
public override void Update()
{
observerState = subject.SubjectState;
Console.WriteLine("观察者{0}的状态是{1}",name,observerState);
}
public ConcreteSubject Subject
{
get { return subject; }
set { subject = value; }
}
}
//客户端
ConcreteSubject s = new ConcreteSubject();
s.Attach(new ConcreteObserver(s, "X"));
s.Attach(new ConcreteObserver(s, "Y"));
s.Attach(new ConcreteObserver(s, "Z"));
s.SubjectState = "ABC";
s.Notify();
模板方法:定义一个操作中算法的骨架,将一些步骤延迟到子类中。模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。
个人理解,其实也就是定义了一个模板方法,其特点就是提供了一个很好的代码复用平台
结构图
//试题
abstract class AbstactClass
{
public abstract void PrimitveOperation1(); //一些抽象行为放到子类去实现,就像试卷一样让继承的子类去重写,因为大家的答案都不一样
public abstract void PrimitiveOperation2();
public void TemplateMethod() //给出了逻辑的骨架,逻辑的组成是一些响应的抽象操作,它们都推出到了子类实现
{
PrimitveOperation1();
PrimitiveOperation2();
Console.WriteLine("");
}
}
//学生A的抄的试卷
class ConcreteClassA : AbstactClass
{
public override void PrimitiveOperation2()
{
Console.WriteLine("具体类A方法2实现");
}
public override void PrimitveOperation1()
{
Console.WriteLine("具体类A方法1实现");
}
}
//学生B抄的试卷
class ConcreteClassB : AbstactClass
{
public override void PrimitiveOperation2()
{
Console.WriteLine("具体类B方法2实现");
}
public override void PrimitveOperation1()
{
Console.WriteLine("具体类B方法1实现");
}
}
//客户端
AbstactClass c;
c = new ConcreteClassA();
c.TemplateMethod();
c = new ConcreteClassB();
c.TemplateMethod();
解释器:给定一个语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子 。
个人理解,说白了就是解释的意思;其中正则表达式就是解释器的一种应用,解释器为正则表达式定义了一个文法,如何表示一个特定的正则表达式,以及如何解释这个正则表达式。(这里还不太明白,正则表达式是怎么运用的,相信以后会用到)
优点:以很容易地改变和扩展文法,因为该模式使用类来表示文法规则,可使用继承来改变或扩展该文法。也比较容易实现文法,因为定义抽象语法树中各个节点的类的实现大体类似。这些类都易于直接编写。(个人理解就是较灵活,可以有很多方式去理解和解释它)
缺点:解释器为文法中的每一条规则至少定义了一个类,因此会有许多规则的文法可能难以管理和维护。建议当文法非常复杂时,使用其他的技术如语法分享程序或编译器生成器来出理。
结构图
音乐解释器实现
//演奏内容(context)
class Context
{
private string input;
public string Input
{
get { return input; }
set { input = value; }
}
private string output;
public string Output
{
get { return output; }
set { output = value; }
}
}
//表达式类
abstract class AbstractExpression
{
public abstract void Interpret(Context context);
}
//音符类、音阶类
class TerminalExpression : AbstractExpression
{
public override void Interpret(Context context)
{
Console.WriteLine("终端解释器");
}
}
//客户端
Context context = new Context();
IList<AbstractExpression> List = new List<AbstractExpression>();
List.Add(new TerminalExpression());
List.Add(new NonterminalExpression());
List.Add(new TerminalExpression());
List.Add(new TerminalExpression());
foreach (AbstractExpression exp in List)
{
exp.Interpret(context);
}
中介者:用一个中介对象来封装一系列的对象交互。中介者使各个对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变他们之间的交互。
个人理解,通过第三方
用法:中介者模式很容易在系统中应用,但是当系统出现“多对多”的交互复制的对象群是,不要急于使用中介者模式。
优点:Mediator的出现减少了各个Colleague的耦合,使得可以独立地改变和复用各个Colleague类和Mediator。比如任何国家的改变不会影响到其他国家,而只是与安理会发生变化。
结构图
访问者:表示一个作用于某对象结构中的各元素的操作。它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作。
个人理解,其目的是要把处理从数据结构分离出来。如果一个系统有比较稳定的数据结构,又有易于变化的算法的话,使用该模式,该模式可使算法的增加变得容易。
优点:增加新的操作很容易,
缺点;使增加新的数据结构有困难了。数据结构稳定的很少,所以很少会用到。一旦用到就是真用到了。
策略:定义了算法家族,分别封装起来,让他们之间可以互相替换,此模式让算法的变化,不会影响到使用算法的客户。
优点:封装了变化
备忘录:不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到原先保存的状态。(游戏进度)
应用:Memento模式比较适用于功能比较复杂的,但需要维护或记录属性历史的类,或者需要保存的属性只是众多属性中的一小部分时,Origination可以根据保存的Memento还原到前一状态。
与命令模式的区别:如果在某个系统中使用命令模式时,需要实现命令的撤销功能,那么命令模式可以使用备忘录模式来存储可撤销操作的状态。有时一些对象的内部信息必须保存在对象以外的地方,但是必须要由对象自己读取。这时,使用备忘录可以把复杂的对象内部信息对其他的对象屏蔽起来。从而可以恰当的保持封装的边界。
个人理解,也就是说当角色的状态改变时候,有可能这个状态无效,这时候就可以使用暂时存储起来的备忘录将状态复原了。
结构图
迭代器:提供一种方法的顺序访问一个聚合对象中各个元素,而又不暴露该对象的内部表示。(售票员的例子)
个人理解,当需要访问一个聚集对象,而且不管这些对象是什么都需要遍历的时候,就应该考虑用迭代器模式。无论什么样的客户,都可以用同样的接口。它分离出了集合对象的遍历行为,抽象出一个迭代器类出来,这样的既可以做到不暴露内部结构,又可以让外部透明的访问内部。
.NET的迭代器实现:IEnmerator支持对非泛型集合的简单迭代接口
结构图