策略模式:
1、定义:定义了一系列算法,并将每个算法封装起来,使它们可以相互替换,且算法的变化不会影响使用算法的客户
2、模型结构:
(1)抽象策略(Strategy)类:定义了一个公共接口,各种不同的算法以不同的方式实现这个接口,
环境角色使用这个接口调用不同的算法,一般使用接口或抽象类实现
(2)具体策略(Concrete Strategy)类:实现了抽象策略定义的接口,提供具体的算法实现
(3)环境(Context)类:持有一个策略类的引用,最终给客户端调用
3、优点:
(1)使用策略模式可以避免使用多重条件转移语句
(2)提供一系列可供重用的算法族,恰当使用继承可以把算法族的公共代码转移到父类里面,从而避免重复的代码
(3)提供了对“开闭原则”的完美支持,在不修改原有系统的基础上选择算法或行为,也可灵活地增加新算法或行为
(4)策略模式可以提供相同行为的不同实现,客户可根据不同的要求选择不同的实现
(5)策略模式把算法的使用放到环境类中,而算法的实现移到具体策略类中,实现了二者的分离
4、缺点:
(1)客户端必须理解所有策略算法的区别,以便适时选择恰当的算法类
(2)策略模式将造成产生很多策略类,可以通过使用享元模式在一定程度上减少对象的数量
5、适用环境:
(1)一个系统需要动态地在几种算法中选择一种
(2)一个类定义了多种行为,并且这些行为在这个类的操作中以多个条件语句的形式出现,
可将每个条件分支移入它们各自的策略类中以代替这些条件语句
(3)不希望客户端知道复杂的、与算法相关的数据结构,在具体策略类中封装算法和相关的数据结构,
提高算法的保密性与安全性
(4)多个类只区别在表现行为不同,可以使用策略模式,在运行时动态选择具体要执行的行为
(5)系统中各算法彼此完全独立,且要求对客户隐藏具体算法的实现细节时
// 购买门票策略(门票每张10元): 1、超过25张的打九折 2、15张票送一张 // 抽象策略类 abstract class Strategy { protected price: number = 10; // 门票价格 protected total: number; // 门票总花费 // 根据方案不同计算总花费 abstract strategyMethod(num: number): void; } // 具体策略类,超过25张的打九折 class ConcreteStrategyA extends Strategy { strategyMethod(num: number): void { this.total = num > 25 ? 25*this.price+(num-25)*this.price*0.9 : num*this.price; console.log(`方法 1 购买 ${num} 张票花费 ${this.total} 元`); } } // 15张票送一张 class ConcreteStrategyB extends Strategy { strategyMethod(num: number): void { this.total = 15*this.price*((num-num%16)/16) + (num%16)*this.price; console.log(`方法 2 购买 ${num} 张票花费 ${this.total} 元`); } } // 环境类 class StrategyContext { private strategy: Strategy; getStrategy(): Strategy { return this.strategy; } setStrategy(strategy: Strategy) { this.strategy = strategy; } strategyMethod(num: number): void { this.strategy.strategyMethod(num); } } let fares: number = 100; let strategyContext: StrategyContext = new StrategyContext(); let strategy1: Strategy = new ConcreteStrategyA(); strategyContext.setStrategy(strategy1); strategyContext.strategyMethod(fares); // 方法 1 购买 100 张票花费 925 元 let strategy2: Strategy = new ConcreteStrategyB(); strategyContext.setStrategy(strategy2); strategyContext.strategyMethod(fares); // 方法 2 购买 100 张票花费 940 元 // 目前环境里的策略为 strategy2,所以输出:方法 2 购买 100 张票花费 940 元 strategyContext.getStrategy().strategyMethod(fares);
模板方法模式:
1、定义:定义一个操作中的算法骨架,而将算法的一些步骤延迟到子类中,
使得子类可以不改变该算法结构的情况下重定义该算法的某些特定步骤
2、模型结构:
(1)抽象类(Abstract Class):负责给出一个算法的轮廓和骨架,它由一个模板方法和若干个基本方法构成
(2)具体子类(Concrete Class):实现抽象类中所定义的抽象方法和钩子方法,它们是一个顶级逻辑的一个组成步骤
注:
(1)模板方法:定义了算法的骨架,按某种顺序调用其包含的基本方法
(2)基本方法:是整个算法中的一个步骤,包含以下几种类型
A、抽象方法:在抽象类中申明,由具体子类实现
B、具体方法:在抽象类中已经实现,在具体子类中可以继承或重写它
C、钩子方法:在抽象类中已经实现,包括用于判断的逻辑方法和需要子类重写的空方法两种
3、优点:
(1)封装了不变部分,扩展可变部分:它把认为是不变部分的算法封装到父类中实现,
而把可变部分算法由子类继承实现,便于子类继续扩展
(2)在父类中提取公共部分的代码,便于代码复用
(3)部分方法是由子类实现的,因此子类可以通过扩展方式增加相应的功能,符合“开闭原则”
4、缺点:
(1)对每个不同的实现都需要定义一个子类,这会导致类的个数增加,系统更加庞大,设计也更加抽象
(2)父类中的抽象方法由子类实现,子类执行的结果会影响父类的结果,
这导致一种反向的控制结构,它提高了代码阅读的难度
5、适用环境:
(1)算法的整体步骤很固定,但其中个别部分易变时,这时候可以使用模板方法模式,
将容易变的部分抽象出来,供子类实现
(2)当多个子类存在公共的行为时,可以将其提取出来并集中到一个公共父类中以避免代码重复。
首先,要识别现有代码中的不同之处,并且将不同之处分离为新的操作。
最后,用一个调用这些新的操作的模板方法来替换这些不同的代码
(3)当需要控制子类的扩展时,模板方法只在特定点调用钩子操作,这样就只允许在这些点进行扩展
// 假设早上上班步骤:1、起床 2、刷牙洗脸 3、吃早饭 4、坐车去公司 5、到达时间 // 抽象类 abstract class AbstractClass { protected food: string; protected vehicle: string; protected time: string; TemplateMethod(): void { this.awake(); this.clean(); this.eat(); this.transportation(); this.arrive(); } awake(): void { console.log("Awaking on time..."); } clean(): void { console.log("Washing face and brushing teeth..."); } abstract eat():void; abstract transportation(): void; abstract arrive(): void; } // 具体子类 class ConcreteClass extends AbstractClass { constructor(food: string, vehicle: string, time: string) { super(); this.food = food; this.vehicle = vehicle; this.time = time; } eat(): void { console.log(`Eating ${this.food}`); } transportation(): void { console.log(`Going to workplace by ${this.vehicle}`); } arrive(): void { console.log(`Arriving workplace at ${this.time}`); } } let food: string = "bread and milk"; let vehicle: string = "bus"; let time: string = "9:00 am"; let tm: AbstractClass = new ConcreteClass(food, vehicle, time); tm.TemplateMethod();
访问者模式:
1、定义:将作用于某种数据结构中的各元素的操作分离出来封装成独立的类,
使其在不改变数据结构的前提下可以添加作用于这些元素的新的操作,
为数据结构中的每个元素提供多种访问方式
2、模型结构:
(1)抽象访问者(Visitor):定义一个访问具体元素的接口,为每个具体元素类对应一个访问操作 visit(),
该操作中的参数类型标识了被访问的具体元素
(2)具体访问者(ConcreteVisitor):实现抽象访问者中声明的各个访问操作,确定访问者访问一个元素时该做什么
(3)抽象元素(Element):声明一个包含接受操作 accept() 的接口,被接受的访问者对象作为 accept() 方法的参数
(4)具体元素(ConcreteElement):实现抽象元素角色提供的 accept() 操作,其方法体通常是 visitor.visit(this),
另外具体元素中可能还包含本身业务逻辑的相关操作
(5)对象结构(Object Structure):是一个包含元素角色的容器,提供让访问者对象遍历容器中的所有元素的方法
3、优点:
(1)扩展性好:能够在不修改对象结构中的元素的情况下,为对象结构中的元素添加新的功能
(2)复用性好:可以通过访问者来定义整个对象结构通用的功能,从而提高系统的复用程度
(3)灵活性好:将数据结构与作用于结构上的操作解耦,使操作集合可相对自由地演化而不影响系统的数据结构
(4)符合单一职责原则:把相关的行为封装在一起,构成一个访问者,使每一个访问者的功能都比较单一
4、缺点:
(1)增加新的元素类很困难:在访问者模式中,每增加一个新的元素类,
都要在每一个具体访问者类中增加相应的具体操作,这违背了“开闭原则”
(2)破坏封装:访问者模式中具体元素对访问者公布细节,这破坏了对象的封装性
(3)违反了依赖倒置原则:访问者模式依赖了具体类,而没有依赖抽象类
5、适用环境:
(1)对象结构相对稳定,但其操作算法经常变化的程序
(2)对象结构中的对象需要提供多种不同且不相关的操作,而且要避免让这些操作的变化影响对象的结构
(3)对象结构包含很多类型的对象,希望对这些对象实施一些依赖于其具体类型的操作
// 抽象访问者 interface Visitor { visit(element: IElement): void; } // 具体访问者,篮球运动员和羽毛球运动员 class ConcreteVisitorA implements Visitor { visit(element: IElement): void { console.log(`篮球运动员:${element.operationA()}`); } } class ConcreteVisitorB implements Visitor { visit(element: IElement): void { console.log(`羽毛球运动员:${element.operationB()}`); } } // 抽象元素 interface IElement { accept(visitor: Visitor): void; operationA(): string; operationB(): string; } // 具体元素,球类和工具类 class ConcreteElementA implements IElement { accept(visitor: Visitor): void { visitor.visit(this); } operationA(): string { return "打篮球"; } operationB(): string { return "打羽毛球"; } } class ConcreteElementB { accept(visitor: Visitor): void { visitor.visit(this); } operationA(): string { return "需要篮球鞋"; } operationB(): string { return "需要羽毛球拍"; } } // 对象结构 class ObjectStructure { private elems: IElement[] = new Array<IElement>(); accept(visitor: Visitor): void { for (let elem of this.elems) { elem.accept(visitor); } } add(elem: IElement): void { this.elems.push(elem); } remove(elem: IElement): void { let idx: number = this.elems.indexOf(elem); this.elems.splice(idx, 1); } } let eleA: IElement = new ConcreteElementA(); let eleB: IElement = new ConcreteElementB(); let obs: ObjectStructure = new ObjectStructure(); obs.add(eleA); obs.add(eleB); let visitor: Visitor = new ConcreteVisitorA(); obs.accept(visitor); visitor = new ConcreteVisitorB(); obs.remove(eleA); obs.accept(visitor);