设计模式学习(二) 行为型模式
设计模式学习(二) 行为型模式
一、模板方法模式
定义一个操作中的算法的框架,而将一些步骤延迟到子类中。使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。
使用场景:有多个子类共有逻辑相同的方法;重要的、复杂的方法,可以考虑作为模板方法。
(1)角色说明:
-
抽象类AbstractClass : 用来定义算法框架和抽象动作
-
具体实现类ConcreteClass: 用来实现算法框架中的具体步骤,完成对应与特定子类相关的功能
(2)业务需求:
(3)实现代码:
public partial class Program
{
static void Main(string[] args)
{
Child child = new Child();
child.m();
}
}
public abstract class Parent
{
public abstract void op1();
public abstract void op2();
public void m()
{
op1();
op2();
}
}
public class Child : Parent
{
public override void op1()
{
Console.WriteLine("OP1 Child");
}
public override void op2()
{
Console.WriteLine("OP2 Child");
}
}
(4)模式特点:
复用:子类可以复用父类定义好的方法
拓展:后续根据业务需求对子类的方法进行修改。
注意:关键是把握变与不变。变化的东西考虑使用abstract,让子类重写;不变的不需要重写
二、状态模式模式
对有状态的对象,把复杂的“判断逻辑”提取到不同的状态对象中,允许状态对象在其内部状态发生改变时改变其行为。
简单的说,当内部状态改变了,它所提供的方法也改变了。
应用场景:行为随状态改变而改变的场景;条件、分支语句的代替者。
(1)角色说明:
- 环境(Context)角色:也叫环境类, 又称为上下文类,它是拥有多种状态的对象。由于环境类的状态存在多样性且在不同状态下对象的行为有所不同,因此将状态独立出去形成单独的状态类。在环境类中维护一个抽象状态类State的实例,这个实例定义当前状态,在具体实现时,它是一个State子类的对象。
- 环境类实际上是真正拥有状态的对象,我们只是将环境类中与状态有关的代码提取出来封装到专门的状态类中。
- 抽象状态(State)角色: 它用于定义一个接口以封装与环境类的一个特定状态相关的行为,在抽象状态类中声明了各种不同状态对应的方法,而在其子类中实现类这些方法,由于不同状态下对象的行为可能不同,因此在不同子类中方法的实现可能存在不同,相同的方法可以写在抽象状态类中。
- 具体状态(Concrete State)角色: 它是抽象状态类的子类,每一个子类实现一个与环境类的一个状态相关的行为,每一个具体状态类对应环境的一个具体状态,不同的具体状态类其行为有所不同。
案例代码:
public partial class Program
{
static void Main(string[] args)
{
Context context = new Context();
context.Handle();
context.Handle();
context.Handle();
}
}
public class Context
{
private State state;
//定义环境类的初始状态
public Context()
{
this.state = new ConcreteStateA();
}
//设置新状态
public void setState(State state)
{
this.state = state;
}
//对请求做处理
public void Handle()
{
state.Handle(this);
}
}
//抽象状态类
public abstract class State
{
public abstract void Handle(Context context);
}
//具体状态A类
public class ConcreteStateA : State
{
public override void Handle(Context context)
{
Console.WriteLine("当前状态是 A.");
context.setState(new ConcreteStateB());
}
}
//具体状态B类
class ConcreteStateB : State
{
public override void Handle(Context context)
{
Console.WriteLine("当前状态是 B.");
context.setState(new ConcreteStateA());
}
}
状态模式将一个对象在不同状态下的不同行为封装在一个个状态类中,通过设置不同的状态对象可以让环境对象拥有不同的行为,而状态转换的细节对于客户端而言是透明的,方便了客户端的使用。
在实际开发中,状态模式具有较高的使用频率,在工作流和游戏开发中状态模式都得到了广泛的应用,例如公文状态的转换、游戏中角色的升级等。
三、策略模式
策略模式:策略模式是一种行为型模式,它将对象和行为分开,将行为定义为 一个行为接口 和 具体行为的实现。
策略模式最大的特点是行为的变化,行为之间可以相互替换。
本模式使得算法可独立于使用它的用户而变化。
(1)角色
1.策略接口角色IStrategy:用来约束一系列具体的策略算法,策略上下文角色ConcreteStrategy使用此策略接口来调用具体的策略所实现的算法。
2.具体策略实现角色ConcreteStrategy:具体的策略实现,即具体的算法实现。
3.策略上下文角色StrategyContext:策略上下文,负责和具体的策略实现交互,通常策略上下文对象会持有一个真正的策略实现对象,策略上下文还可以让具体的策略实现从其中获取相关数据,回调策略上下文对象的方法。
(2)代码实现略
(3)使用场景:允许系统动态的选择算法。
四、命令模式
命令(Command)模式又叫作动作(Action)模式或事务(Transaction)模式,是一种对象的行为模式。将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可撤消的操作。
角色说明:
实体对象与行为操作之间往往也存在耦合关系。
行为请求者通过“命令”行为实现者去执行一定的行为。
GoF:将请求封装成一个对象,从而使我们可用不同的请求对客户程序进行参数化操作,以及对请求排队或记录讲求日志,以及支持可撤销的操作。
public partial class Program
{
static void Main(string[] args)
{
Receiver receiver = new Receiver();
Command command = new ConcreteCommand(receiver);
Invoker invoker = new Invoker();
invoker.SetCommand(command);
invoker.invoke();
}
}
public interface Command
{
public void Execute();
}
public class ConcreteCommand : Command
{
private Receiver receiver = null;
public ConcreteCommand(Receiver receiver)
{
this.receiver = receiver;
}
public void Execute()
{
receiver.action();
}
}
public class Receiver
{
public void action()
{
Console.WriteLine("接受者开始工作");
}
}
public class Invoker
{
private Command Command = null;
public void SetCommand(Command command)
{
this.Command = command;
}
public void invoke()
{
Command.Execute();
}
}
Invoker:是命令的管理类,这里可以进行设置命令的执行链,设置一些的执行方法(例如撤销等)。
在以下条件下可以考虑使用命令模式:
• 如果需要抽象出需要执行的动作,并参数化这些对象,可以选用命令模式。将这些需要执行的动作抽象成为命令,然后实现命令的参数化配置。
• 如果需要在不同的时刻指定、排列和执行请求,可以选用命令模式。将这些请求封装成为命令对象,然后实现将请求队列化。
• 如果需要支持取消操作,可以选用命令模式,通过管理命令对象,能很容易地实现命令的恢复和重做功能。
• 如果需要支持当系统崩溃时,能将系统的操作功能重新执行一遍,可以选用命令模式。将这些操作功能的请求封装成命令对象,然后实现日志命令,就可以在系统恢复以后,通过日志获取命令列表,从而重新执行一遍功能。
• 在需要事务的系统中,可以选用命令模式。命令模式提供了对事务进行建模的方法。命令模式有一个别名就是Transaction。
五、责任链模式
在责任链模式里,很多对象由每一个对象对其下家的引用而连接起来形成一条链。
请求在这个链上传递,直到链上的某一个对象决定处理此请求。
发出这个请求的客户端并不知道链上的哪一个对象最终处理这个请求,这使得系统可以在不影响客户端的情况下动态地重新组织和分配责任。
使用场景的原理:层级处理(动态指定处理某一组请求时,在不确定接受者的的情况下,向多个对象发送请求时)
小案例的实现代码:
public partial class Program
{
static void Main(string[] args)
{
Handler level1 = new Leader();
Handler level2 = new Boss();
level1.SetNextHandler(level2);
level1.process(8);
level1.process(15);
}
}
public abstract class Handler
{
public Handler NextHandler;
public void SetNextHandler(Handler nextHandler)
{
this.NextHandler = nextHandler;
}
public abstract void process(int info);
}
public class Leader : Handler
{
public override void process(int info)
{
if (info > 0 && info < 11)
{
Console.WriteLine("Leader 处理");
}
else
{
NextHandler.process(info);
}
}
}
public class Boss : Handler
{
public override void process(int info)
{
Console.WriteLine("Boss 处理");
}
}
六、观察者模式
定义对象间的一种一对多依赖关系,使得每当一个对象状态发生改变时,其相关依赖对象得到通知并被自动更新
模式角色说明:
观察者模式主要由这四个角色组成,抽象主题角色(Subject)、具体主题角色(ConcreteSubject)、抽象观察者角色(Observer)和具体观察者角色(ConcreteObserver)。
- 抽象主题角色(Subject):它把所有观察者对象的引用保存到一个聚集里,每个主题都可以有任何数量的观察者。抽象主题提供一个接口,可以增加和删除观察者对象。
- 具体主题角色(ConcreteSubject):将有关状态存入具体观察者对象;在具体主题内部状态改变时,给所有登记过的观察者发出通知。
- 抽象观察者角色(Observer):主要是负责从备忘录对象中恢复对象的状态。
使用场景:
需要关联行为的场景;
事件需要创建一个触发链的场景,比如监控;
跨系统的消息交换场景,比如消息队列、事件总线的处理机制。
小案例描述:张三借了王五的钱。当张三有钱以后通知王五还钱
小案例的实现代码:
Credit 观察者
Debit 主题对象(这里是抽象主题对象),Zhangsan是具体主题对象
通过订阅的方式:主题对象添加观察者。
主题对象通过方法:去通知观察者
public partial class Program
{
static void Main(string[] args)
{
Debit zhansan = new Zhangsan();
zhansan.borrow(new Wangwu());
zhansan.notifCredits();
}
}
interface Debit
{
void borrow(Credit credit);
void notifCredits();
}
/// <summary>
/// 借款人
/// </summary>
class Zhangsan : Debit
{
private List<Credit> allCredits = new List<Credit>();
public void borrow(Credit credit)
{
allCredits.Add(credit);
}
public void notifCredits()
{
Console.WriteLine("张三通知还款了");
foreach (Credit item in allCredits)
{
item.takeMoney();
}
}
}
/// <summary>
/// 被借款人
/// </summary>
interface Credit
{
void takeMoney();
}
class Wangwu: Credit
{
public void takeMoney()
{
Console.WriteLine("王五拿到欠款了");
}
}
七、中介者模式
中介者模式(Mediator Pattern),定义了一个中介对象来封装一系列对象之间的交互关系。中介者使各个对象之间不需要显式地相互引用,从而使耦合性降低,而且可以独立地改变它们之间的交互行为,属于行为型模式。 其主要的目的是用来降低多个对象和类之间的通信复杂性。
角色说明:
中介者模式主要由这四个角色组成, 抽象中介者(Mediator)、具体中介者(ConcreteMediator)、 抽象同事类(Colleague)和具体同事类(ConcreteColleague) 。
- 抽象中介者(Mediator): 定义了同事对象到中介者对象之间的接口。
- 具体中介者(ConcreteMediator): 实现抽象中介者的方法,它需要知道所有的具体同事类,同时需要从具体的同事类那里接收信息,并且向具体的同事类发送信息。
- 抽象同事类(Colleague): 定义了中介者对象的接口,它只知道中介者而不知道其他的同事对象。
- 具体同事类(ConcreteColleague) : 每个具体同事类都只需要知道自己的行为即可,但是他们都需要认识中介者。
说明:
-
中介者模式通过中介者对象来封装一系列的对象交互,将对象间复杂的关系网状结构变成结构简单的以中介者为核心的星形结构,对象间一对多的关联转变为一对一的关联,简化对象间的关系,便于理解;各个对象之间的关系被解耦,每个对象不再和它关联的对象直接发生相互作用,而是通过中介者对象来与关联的对象进行通讯,使得对象可以相对独立地使用,提高了对象的可复用和系统的可扩展性。
-
在中介者模式中,中介者类处于核心地位,它封装了系统中所有对象类之间的关系,除了简化对象间的关系,还可以对对象间的交互进行进一步的控制。
小案例的代码实现:让代理人帮助两个人相亲。
抽象代理人接口提供pair和register方法。具体代理人实现。
Person调用findPartner,实际上是调用Agency的方法。
public partial class Program
{
static void Main(string[] args)
{
Agency agency = new AgencyImpl();
Person person1 = new Person("张三", 12, 15, agency);
Person person2 = new Person("李四", 15, 15, agency);
agency.register(person1);
agency.register(person2);
agency.pair(person1);
}
}
public interface Agency
{
public void pair(Person person);
public void register(Person person);
}
public class Person
{
public string name;
public int age;
public int requsterAge;
public Agency agency;
public Person(string name, int age, int requsterAge, Agency agency)
{
this.name = name;
this.age = age;
this.requsterAge = requsterAge;
this.agency = agency;
}
public void findPartner()
{
agency.pair(this);
}
}
public class AgencyImpl : Agency
{
List<Person> listPerson = new List<Person>();
public void register(Person person)
{
listPerson.Add(person);
}
public void pair(Person person)
{
foreach (Person p in listPerson)
{
if (person.requsterAge == p.age)
{
Console.WriteLine("让 "+person.name + " 与 " + p.name + " 相亲");
}
}
}
八、迭代器模式
迭代器模式用于顺序访问集合对象的元素,不需要知道集合对象的底层表示,属于行为型模式。 它提供一种方法顺序访问一个聚合对象中各个元素, 而又无须暴露该对象的内部表示。
角色说明:
◊ Iterator:迭代器定义访问和遍历元素的接口
◊ ConcreteIterator
° 具体迭代器实现迭代器接口
° 对该聚合遍历时跟踪当前位置
◊ Aggregate:聚合定义创建Iterator对象的接口
◊ ConcreteAggregate:具体聚合,实现相应迭代器的接口,返回具体迭代器的一个适当的实例。
在迭代器模式中,ConcreteAggregate通过Aggregate定义的接口得到Iterator,并且这是一个ConcreteIterator,该ConcreteIterator具体实现了对ConcreteAggregate的访问与遍历的方法。通过ConcreteIterator可以访问并使用集合中的元素。
/// <summary>
/// 抽象迭代器,提供接口
/// </summary>
public abstract class Iterator
{
public abstract object First();
public abstract object Next();
public abstract bool IsDone();
public abstract object CurrentItem();
}
/// <summary>
/// 具体迭代器
/// </summary>
public class ConcreteIterator : Iterator
{
private ConcreteAggregate _aggregate;
private int _current = 0;
public ConcreteIterator(ConcreteAggregate aggregate)
{
this._aggregate = aggregate;
}
public override object First()
{
return _aggregate[0];
}
public override object Next()
{
object ret = null;
if (_current < _aggregate.Count - 1)
{
ret = _aggregate[++_current];
}
return ret;
}
public override object CurrentItem()
{
return _aggregate[_current];
}
public override bool IsDone()
{
return _current >= _aggregate.Count;
}
}
/// <summary>
/// 抽象聚合
/// </summary>
public abstract class Aggregate
{
public abstract Iterator CreateIterator();
}
/// <summary>
/// 具体聚合
/// </summary>
public class ConcreteAggregate : Aggregate
{
private ArrayList _items = new ArrayList();
public override Iterator CreateIterator()
{
return new ConcreteIterator(this);
}
public int Count
{
get { return _items.Count; }
}
public object this[int index]
{
get { return _items[index]; }
set { _items.Insert(index, value); }
}
}
public partial class Program
{
static void Main(string[] args)
{
ConcreteAggregate a = new ConcreteAggregate();
a[0] = "Item A";
a[1] = "Item B";
a[2] = "Item C";
a[3] = "Item D";
ConcreteIterator i = new ConcreteIterator(a);
Console.WriteLine("Iterating over collection:");
object item = i.First();
while (item != null)
{
Console.WriteLine(item);
item = i.Next();
}
}
}
九、访问者模式
访问者模式(VisitorPattern),顾名思义使用了这个模式后就可以在不修改已有程序结构的前提下,通过添加额外的访问者来完成对已有代码功能的提升,它属于行为模式。访问者模式的目的是封装一些施加于某种数据结构元素之上的操作。一旦这些操作需要修改的话,接受这个操作的数据结构则可以保持不变。
其主要目的是将数据结构与数据操作分离。
角色:
- 抽象访问者(Visitor)角色:声明了一个或者多个方法操作,形成所有的具体访问者角色必须实现的接口。
- 具体访问者(ConcreteVisitor)角色:实现抽象访问者所声明的接口,也就是抽象访问者所声明的各个访问操作。
- 用户(Client)角色:声明一个接受操作,接受一个访问者对象作为一个参数。
- 具体节点(ConcreteNode)角色:实现了抽象节点所规定的接受操作。
- 结构对象(ObjectStructure)角色:有如下的责任,可以遍历结构中的所有元素。
使用场景:
对象结构中对象对应的类很少改变,但经常需要在此对象结构上定义新的操作;
需要对一个对象结构中的对象进行很多不同的并且不相关的操作,而需要避免让这些操作"污染"这些对象的类,也不希望在增加新操作时修改这些类。
小案例说明:
有一台小机器人,它因为某些问题,计算计算1+1=2时,只能显示"1+1",而无法显示完全。所以工程人员使用补丁包的方式让机器人升级。
小案例代码实现:
Vistor 是接口,可以定义具体的实现类
元素接收访问器,又把自己传递给访问器
UpdateVisitor 是具体访问器
Hardware 是抽象元素
CPU 是具体元素
Robot作为客户,有元素对象。可以使用Accept命令来使用具体访问器
public partial class Program
{
static void Main(string[] args)
{
Robot robot = new Robot();
robot.calc();
Vistor updatePack = new UpdateVisitor();
robot.accept(updatePack);
robot.calc();
}
}
public class Robot
{
private CPU cpu;
public Robot()
{
cpu = new CPU("1+1");
}
public void calc()
{
cpu.run();
}
public void accept(Vistor vistor)
{
cpu.accept(vistor);
}
}
public abstract class Hardware
{
public string command;
public Hardware(string command)
{
this.command = command;
}
public void run()
{
Console.WriteLine(command);
}
public abstract void accept(Vistor vistor);
}
public interface Vistor
{
public void vistorCPU(CPU cpu);
}
public class UpdateVisitor : Vistor
{
public void vistorCPU(CPU cpu)
{
cpu.command += "=2";
}
}
public class CPU : Hardware
{
public CPU(string command) : base(command)
{
}
public override void accept(Vistor vistor)
{
vistor.vistorCPU(this);
}
}
十、备忘录模式
备忘录模式(Memento Pattern)用于保存一个对象的某个状态,以便在适当的时候恢复对象,该模式属于行为型模式。
其主要目的是在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。
简单来说:备份
角色说明:
- 备忘录(Memento):主要的功能是包含要被恢复的对象的状态。
- 发起人(Originator):在创建的时候,会在备忘录对象中存储状态。
- 负责人(Caretaker):主要是负责从备忘录对象中恢复对象的状态。
代码实现:
public partial class Program
{
static void Main(string[] args)
{
History history = new History();
Document document = new Document();
document.change("abc");
history.push(document.save());
document.change("bcd");
history.push(document.save());
document.change("fgh");
document.resume(history.getLastVersion());
document.print();
document.resume(history.getLastVersion());
document.print();
}
}
public class Document
{
private string content;
public BackUp save()
{
return new BackUp(content);
}
public void resume(BackUp backUp)
{
content = backUp.content;
}
public void change(string content)
{
this.content = content;
}
public void print()
{
Console.WriteLine(content);
}
}
public interface Memonto
{ }
public class BackUp: Memonto
{
public string content;
public BackUp(string content)
{
this.content = content;
}
}
public class History
{
Stack<BackUp> backUpstack = new Stack<BackUp>();
public void push(BackUp backUp)
{
backUpstack.Push(backUp);
}
public BackUp getLastVersion()
{
return backUpstack.Pop();
}
}
十一、解释器模式
解释器模式顾名思义,就是对某事物进行解释。给定一个语言之后,解释器模式可以定义出其文法的一种表示,并同时提供一个解释器。客户端可以使用这个解释器来解释这个语言中的句子。
解释器模式其实就是对某事物进行解释。比如生活中经常用到的计算器,将我们用的语言转换成计算器预言,还有我们编写代码时用到的正则表达式等等。
角色说明:
- 抽象解释器:声明一个所有具体表达式都要实现的抽象接口(或者抽象类),接口中主要是一个interpret()方法,称为解释操作。具体解释任务由它的各个实现类来完成,具体的解释器分别由终结符解释器TerminalExpression和非终结符解释器NonterminalExpression完成。
- 终结符表达式:实现与文法中的元素相关联的解释操作,通常一个解释器模式中只有一个终结符表达式,但有多个实例,对应不同的终结符。终结符一半是文法中的运算单元,比如有一个简单的公式R=R1+R2,在里面R1和R2就是终结符,对应的解析R1和R2的解释器就是终结符表达式。
- 非终结符表达式:文法中的每条规则对应于一个非终结符表达式,非终结符表达式一般是文法中的运算符或者其他关键字,比如公式R=R1+R2中,+就是非终结符,解析+的解释器就是一个非终结符表达式。非终结符表达式根据逻辑的复杂程度而增加,原则上每个文法规则都对应一个非终结符表达式。
- 环境角色:这个角色的任务一般是用来存放文法中各个终结符所对应的具体值,比如R=R1+R2,我们给R1赋值100,给R2赋值200。
几个应用场景:
- 进行人民币大写与对应数字的转换
一个小的实现案例:
public partial class Program
{
static void Main(string[] args)
{
string sentence = "美丽的古老的北京";
new Context().Interpreter(sentence);
Console.ReadLine();
}
}
public abstract class AbstractExpression
{
public abstract void Interpreter(string context);
}
public class TerminalExpression : AbstractExpression
{
public override void Interpreter(string context)
{
Console.WriteLine($"Interpreter in TerminalExpression: {context}");
}
}
public class NonterminalExpression : AbstractExpression
{
public override void Interpreter(string context)
{
if (context.Contains("的"))
{
var index = context.IndexOf("的");
Console.WriteLine($"Interpreter in NonterminalExpression: {context.Substring(0, index + 1)}");
new NonterminalExpression().Interpreter(context.Substring(index + 1));
}
else
{
new TerminalExpression().Interpreter(context);
}
}
}
public class Context
{
public void Interpreter(string sentence)
{
if (sentence.Contains("的"))
{
new NonterminalExpression().Interpreter(sentence);
}
else
{
new TerminalExpression().Interpreter(sentence);
}
}
}