行为型设计模式(上)
责任链模式:
下图为责任链
1、定义:为了避免请求发送者与多个请求处理者耦合在一起,将所有请求的处理者通过前一对象
记住其下一个对象的引用而连成一条链;当有请求发生时,可将请求沿着这条链传递,直到有对象处理它为止
2、模型结构:
(1)抽象处理者(Handler):定义一个处理请求的接口,包含抽象处理方法和一个后继连接
(2)具体处理者(Concrete Handler):实现抽象处理者的处理方法,判断能否处理本次请求,
如果可以处理请求则处理,否则将该请求转给它的后继者
3、优点:
(1)降低了对象之间的耦合度。该模式使得一个对象无须知道到底是哪一个对象处理其请求以及链的结构,
发送者和接收者也无须拥有对方的明确信息
(2)增强了系统的可扩展性。可以根据需要增加新的请求处理类,满足“开闭原则”
(3)增强了给对象指派职责的灵活性。当工作流程发生变化,可以动态地改变链内的成员或者调动它们的次序,
也可动态地新增或者删除责任
(4)简化了对象之间的连接。每个对象只需保持一个指向其后继者的引用,
不需保持其他所有处理者的引用,这避免了使用众多的分支语句
(5)责任分担。每个类只需要处理自己该处理的工作,不该处理的传递给下一个对象完成,
明确各类的责任范围,符合类的单一职责原则
注:单一职责原则规定一个类应该有且仅有一个引起它变化的原因,否则类应该被拆分
4、缺点:
(1)不能保证每个请求一定被处理。由于一个请求没有明确的接收者,
所以不能保证它一定会被处理,该请求可能一直传到链的末端都得不到处理
(2)对比较长的职责链,请求的处理可能涉及多个处理对象,系统性能将受到一定影响
(3)职责链建立的合理性要靠客户端来保证,增加了客户端的复杂性,
可能会由于职责链的错误设置而导致系统出错,如可能会造成循环调用
5、适用环境:
(1)有多个对象可以处理一个请求,哪个对象处理该请求由运行时刻自动确定
(2)可动态指定一组对象处理请求,或添加新的处理者
(3)在不明确指定请求处理者的情况下,向多个处理者中的一个提交请求
// 抽象处理者 Handler abstract class ATeacher { // 链式结构,需要包含一个指向下一个 Handler 的对象 protected aTeacher: ATeacher; setTeacher(teacher: ATeacher): void { this.aTeacher = teacher; } getTeacher(): ATeacher { return this.aTeacher; } abstract handler(level: number): void; } // 具体处理者 Concrete Handler class Teacher extends ATeacher { private name: string; private something: string; constructor(name: string, something: string) { super(); // Constructors for derived classes must contain a 'super' call this.name = name; this.something = something } handler(level: number): void { if (level >= 3) { console.log("授课老师同意..."); } else { console.log("授课老师无权受理..."); this.getTeacher().handler(level); } } } class Moniteur extends ATeacher { private name: string; private something: string; constructor(name: string, something: string) { super(); // Constructors for derived classes must contain a 'super' call this.name = name; this.something = something } handler(level: number): void { let flag: boolean = true; // 设置为同意 if (level >= 2) { if (flag) console.log("辅导员同意..."); else console.log("辅导员不同意..."); } else { console.log("辅导员无权受理..."); this.getTeacher().handler(level); } } } class Directeur extends ATeacher { private name: string; private something: string; constructor(name: string, something: string) { super(); // Constructors for derived classes must contain a 'super' call this.name = name; this.something = something } handler(level: number): void { let flag: boolean = false; // 设置为不同意 if (level >= 1) { if (flag) console.log("主任同意..."); else console.log("主任不同意..."); } else { console.log("主任无权受理..."); this.getTeacher().handler(level); } } } let stuName1: string = "Lemon"; let thing: string = "Go home"; let teacher1: ATeacher = new Teacher(stuName1, thing); let teacher2: ATeacher = new Moniteur(stuName1, thing); let teacher3: ATeacher = new Directeur(stuName1, thing); teacher1.setTeacher(teacher2); teacher2.setTeacher(teacher3); // 授课老师无权受理... // 辅导员同意... teacher1.handler(2);
命令模式:
1、定义:将一个请求封装为一个对象,使发出请求的责任和执行请求的责任分割开。
这样两者之间通过命令对象进行沟通,这样方便将命令对象进行储存、传递、调用、增加与管理
2、模型结构:
(1)抽象命令类(Command):声明执行命令的接口,拥有执行命令的抽象方法
(2)具体命令类(ConcreteCommand):是抽象命令类的具体实现类,它拥有接收者对象,
并通过调用接收者的功能来完成命令要执行的操作
(3)调用者(Invoker):请求的发送者,它通常拥有很多的命令对象,
并通过访问命令对象来执行相关请求,不直接访问接收者
(4)接收者(Receiver):执行命令功能的相关操作,是具体命令对象业务的真正实现者
3、优点:
(1)降低系统的耦合度:命令模式能将调用操作的对象与实现该操作的对象解耦
(2)增加或删除命令非常方便:采用命令模式增加与删除命令不会影响其他类,满足“开闭原则”,扩展比较灵活
(3)可以实现宏命令:命令模式可以与组合模式结合,将多个命令装配成一个组合命令,即宏命令
(4)方便实现 Undo 和 Redo 操作:命令模式可以与后面介绍的备忘录模式结合,实现命令的撤销与恢复
4、缺点:可能产生大量具体命令类。因为计对每一个具体操作都需要设计一个具体命令类,这将增加系统的复杂性
5、适用环境:
(1)系统需要将请求调用者和请求接收者解耦,使得调用者和接收者不直接交互
(2)系统需要在不同的时间指定请求、将请求排队和执行请求
(3)系统需要支持命令的撤销(Undo)操作和恢复(Redo)操作
(4)系统需要将一组操作组合在一起,即支持宏命令
// 接收者 Receiver class RStudent { clean(name: string): void { console.log(name + " begin cleaning..."); } doHomwork(name: string) { console.log(name + " begin doing homework..."); } } // 抽象命令类 Command abstract class Command { protected student: RStudent; constructor(student: RStudent) { this.student = student; } abstract execute(name: string): void; } // 具体命令类 ConcreteCommand class LiTeacher extends Command { constructor(student: RStudent) { super(student); } execute(name: string): void { this.student.clean(name); } } class WangTeacher extends Command { constructor(student: RStudent) { super(student); } execute(name: string): void { this.student.doHomwork(name); } } // 调用者 Invoker class Invoker { protected command: Command; commands: Command[] = new Array<Command>(); executeCommand(name: string): void { this.command = this.commands.shift(); this.command.execute(name); console.log("Executing one command..."); } setCommand(command: Command): void { console.log(`Here are ${this.commands.length} things must be executed before execute this`); this.commands.push(command); } undoCommand(): void { if (this.commands.length) { this.commands.pop(); console.log("Undo command is successful!"); } else { console.log("You shouldn't execute any command after you set it if you want to undo it..."); } } } let stuName: string = "Tim"; let stu: RStudent = new RStudent(); let command1: LiTeacher = new LiTeacher(stu); let command2: WangTeacher = new WangTeacher(stu); let invoker: Invoker = new Invoker(); invoker.setCommand(command1); // Here are 0 things must be executed before execute this invoker.setCommand(command2); // Here are 1 things must be executed before execute this invoker.executeCommand(stuName); // Tim begin cleaning... \n Executing one command... invoker.setCommand(command1); // Here are 1 things must be executed before execute this invoker.undoCommand(); // Undo command is successful! invoker.executeCommand(stuName); // Tim begin doing homework... \n Executing one command...
解释器模式:
1、定义:给分析对象定义一个语言,并定义该语言的文法表示,再设计一个解析器来解释语言中的句子
注:文法是用于描述语言的语法结构的形式规则
2、模型结构:
(1)抽象表达式(Abstract Expression):定义解释器的接口,约定解释器的解释操作
(2)终结符表达式(Terminal Expression):抽象表达式的子类,用来实现文法中与终结符相关的操作,
文法中的每一个终结符都有一个具体终结表达式与之相对应
(3)非终结符表达式(Nonterminal Expression):抽象表达式的子类,用来实现文法中与非终结符相关的操作,
文法中的每条规则都对应于一个非终结符表达式
(4)环境(Context):通常包含各个解释器需要的数据或是公共的功能,一般用来传递被所有解释器共享的数据,
后面的解释器可以从这里获取这些值
3、优点:
(1)扩展性好:由于在解释器模式中使用类来表示语言的文法规则,因此可以通过继承等机制来改变或扩展文法
(2)容易实现:在语法树中的每个表达式节点类都是相似的,所以实现其文法较为容易
注:语法树是句子结构的一种树型表示,它代表了句子的推导结果,它有利于理解句子语法结构的层次
4、缺点:
(1)执行效率较低:解释器模式中通常使用大量的循环和递归调用,当要解释的句子较复杂时,
其运行速度很慢,且代码的调试过程也比较麻烦
(2)引起类膨胀:解释器模式中的每条规则至少需要定义一个类,当包含的文法规则很多时,
类的个数将急剧增加,导致系统难以管理与维护
(3)可应用的场景比较少:在软件开发中,需要定义语言文法的应用实例非常少,所以这种模式很少被使用到
5、适用环境:
(1)当语言的文法较为简单,且执行效率不是关键问题时
(2)当问题重复出现,且可以用一种简单的语言来进行表达时
(3)当一个语言需要解释执行,并且语言中的句子可以表示为一个抽象语法树的时候,如 XML 文档解释
// 抽象表达式 interface AbstractExpression { interpret(age: string, occupation: string): boolean; } // 终结符表达式 class TerminalExpression implements AbstractExpression { private set: Set<string> = new Set<string>(); constructor(data: string[]) { for (let i in data) { this.set.add(data[i]); } } interpret(age: string, occupation: string): boolean { if (Number(age) <= 12 || this.set.has(occupation)) { return true; } return false; } } // 非终结符表达式 class AndExpression implements AbstractExpression { private people: AbstractExpression = null; constructor(people: AbstractExpression) { this.people = people; } interpret(age: string, occupation: string): boolean { return this.people.interpret(age, occupation); } } // 环境类 class Context { private occupations: string[] = ["学生", "老人", "孕妇"]; private people: AbstractExpression; constructor() { let occupation: AbstractExpression = new TerminalExpression(this.occupations); this.people = new AndExpression(occupation); } halfPrice(info: string) { let info_arr: string[] = info.split("岁"); let ok: boolean = this.people.interpret(info_arr[0], info_arr[1]); if (ok) { console.log(`您是${info},本次费用半价...`); } else { console.log(`${info},您不是优惠人群,本次费用全票...`); } } } let sp: Context = new Context(); sp.halfPrice("12岁学生"); // 您是12岁学生,本次费用半价... sp.halfPrice("8岁儿童"); // 您是8岁儿童,本次费用半价... sp.halfPrice("20岁学生"); // 您是20岁学生,本次费用半价... sp.halfPrice("68岁老人"); // 您是68岁老人,本次费用半价... sp.halfPrice("28岁孕妇"); // 您是28岁孕妇,本次费用半价... sp.halfPrice("18岁无业游民"); // 18岁无业游民,您不是优惠人群,本次费用全票...
迭代器模式:
1、定义:提供一个对象来顺序访问聚合对象中的一系列数据,而不暴露聚合对象的内部表示
2、模型结构:
(1)抽象聚合(Aggregate):定义存储、添加、删除聚合对象以及创建迭代器对象的接口
(2)具体聚合(ConcreteAggregate):实现抽象聚合类,返回一个具体迭代器的实例
(3)抽象迭代器Iterator:定义访问和遍历聚合元素的接口,通常包含 hasNext()、first()、next() 等方法
(4)具体迭代器(Concretelterator):实现抽象迭代器接口中所定义的方法,完成对聚合对象的遍历,
记录遍历的当前位置
3、优点:
(1)访问一个聚合对象的内容而无须暴露它的内部表示
(2)遍历任务交由迭代器完成,这简化了聚合类
(3)它支持以不同方式遍历一个聚合,甚至可以自定义迭代器的子类以支持新的遍历
(4)增加新的聚合类和迭代器类都很方便,无须修改原有代码
(5)封装性良好,为遍历不同的聚合结构提供一个统一的接口
4、缺点:增加了类的个数,这在一定程度上增加了系统的复杂性
5、适用环境:
(1)当需要为聚合对象提供多种遍历方式时
(2)当需要为遍历不同的聚合结构提供一个统一的接口时
(3)当访问一个聚合对象的内容而无须暴露其内部细节的表示时
// 学校类,用于聚合 class School { private schoolName: string; constructor(schoolName: string) { this.schoolName = schoolName; } printName(): void { console.log(this.schoolName); } } // 抽象聚合 interface Aggregate { add(school: School): void; remove(): School; getIterator(): MyIterator; } // 具体聚合 class ConcreteAggregate implements Aggregate { private schoolList: School[] = new Array<School>(); add(school: School): void { this.schoolList.push(school); } remove(): School { let temp: School = this.schoolList.pop(); return temp; } getIterator(): MyIterator { return new ConcreteIterator(this.schoolList); } } // 抽象迭代器 interface MyIterator { first(): School; next(): School; hasNext(): boolean; reset(): void; } // 具体迭代器 class ConcreteIterator implements MyIterator { private schoolList: School[]; private index: number = -1; constructor(schoolList: School[]) { this.schoolList = schoolList; } first(): School { let first: School = null; if (this.schoolList.length > 0) first = this.schoolList[0]; return first; } next(): School { let next: School = null; if (this.hasNext()) // index 初始值为 -1,所以需要先加 next = this.schoolList[++this.index]; return next; } hasNext(): boolean { if (this.index < this.schoolList.length-1) return true; return false; } reset(): void { this.index = -1; } } let school1: School = new School("华南师范大学"); let school2: School = new School("北京邮电大学"); let school3: School = new School("东南大学"); let schools: Aggregate = new ConcreteAggregate(); schools.add(school1); schools.add(school2); schools.add(school3); let it: MyIterator = schools.getIterator(); while (it.hasNext()) { it.next().printName(); // 依次输出学校名字 } it.first().printName(); // 华南师范大学 console.log(it.hasNext()); // false,因为 index 已经到末尾了 it.reset(); // 重置 index console.log(it.hasNext()); // true schools.remove().printName(); // 东南大学