结构型设计模式(下)
装饰模式:
1、定义:动态地给一个对象增加一些额外的职责,就增加对象功能来说,装饰模式比生成子类实现更为灵活
2、模型结构:
(1)抽象构件(Component):定义一个抽象接口以规范准备接收附加责任的对象
(2)具体构件(ConcreteComponent):实现抽象构件,通过装饰角色为其添加一些职责
(3)抽象装饰类(Decorator):继承抽象构件,并包含具体构件的实例,可以通过其子类扩展具体构件的功能
(4)具体装饰类(ConcreteDecorator):实现抽象装饰的相关方法,并给具体构件对象添加附加的责任
3、优点:
(1)装饰模式与继承关系的目的都是要扩展对象的功能,但是装饰模式可以提供比继承更多的灵活性
(2)可以通过一种动态的方式来扩展一个对象的功能,通过配置文件可以在运行时选择不同的装饰器实现不同的行为
(3)通过使用不同的具体装饰类以及这些装饰类的排列组合,可以创造出很多不同行为的组合
(4)具体构件类与具体装饰类可以独立变化,用户可以根据需要增加新的具体构件类和具体装饰类,
在使用时再对其进行组合,原有代码无须改变,符合“开闭原则”
4、缺点:
(1)使用装饰模式进行系统设计时将产生很多小对象,这些对象的区别在于它们之间相互连接的方式有所不同,
而不是它们的类或者属性值有所不同,同时还将产生很多具体装饰类。
这些装饰类和小对象的产生将增加系统的复杂度,加大学习与理解的难度
(2)这种比继承更加灵活机动的特性,也同时意味着装饰模式比继承更加易于出错,排错也很困难,
对于多次装饰的对象,调试时寻找错误可能需要逐级排查,较为烦琐
5、适用环境:
(1)在不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责
(2)需要动态地给一个对象增加功能,这些功能也可以动态地被撤销
(3)当不能采用继承的方式对系统进行扩充或者采用继承不利于系统扩展和维护时
注:不能采用继承的情况主要有两类
(1)系统中存在大量独立的扩展,为支持每一种组合将产生大量的子类,使得子类数目呈爆炸性增长
(2)因为类定义不能继承(如final类)
// 抽象构件 Component interface IPerson { getName(): string; } // 具体构件 ConcreteComponent,继承抽象构件 class Tim implements IPerson { private name: string; getName(): string { this.name = "Tim"; return this.name; } } // 抽象装饰类 Decorator,继承抽象构件,并聚合抽象构件 class Cloths implements IPerson { protected person: IPerson; constructor(person: IPerson) { this.person = person; } getName(): string { return this.person.getName(); } } class Trousers implements IPerson { protected person: IPerson; constructor(person: IPerson) { this.person = person; } getName(): string { return this.person.getName(); } } // 具体装饰类 ConcreteDecorator,继承抽象装饰类 class Suit extends Cloths { private info: string; constructor(person: IPerson) { super(person); } getCloths(): string { this.info = this.person.getName() + "上衣: " + "suit..."; return this.info; } } class Jack extends Cloths { private info: string; constructor(person: IPerson) { super(person); } getCloths(): string { this.info = this.person.getName() + "上衣: " + "jacket..."; return this.info; } } class Pants extends Trousers { private info: string; constructor(person: IPerson) { super(person); } getTrousers(): string { this.info = this.person.getName() + "裤子: " + "pants..."; return this.info; } } class Jean extends Trousers { private info: string; constructor(person: IPerson) { super(person); } getTrousers(): string { this.info = this.person.getName() + "裤子: " + "jean..."; return this.info; } } let tim: IPerson = new Tim(); console.log("Get suit"); let suit: Suit = new Suit(tim); console.log(suit.getCloths()); // Tim上衣: suit... console.log("Get jean"); let jean: Jean = new Jean(tim); console.log(jean.getTrousers()); // Tim裤子: jean...
外观模式:
1、定义:外部与一个子系统的通信必须通过一个统一的外观对象进行,为子系统中的一组接口提供一个一致的界面,
外观模式定义了一个高层接口,这个接口使得这一子系统更加容易使用
2、模型结构:
(1)外观角色(Facade):为多个子系统对外提供一个共同的接口
(2)子系统角色(SubSystem):实现系统的部分功能,客户可以通过外观角色访问它
3、优点:
(1)对客户屏蔽子系统组件,减少了客户处理的对象数目并使得子系统使用起来更加容易
(2)降低了子系统与客户端之间的耦合度,使得子系统的变化不会影响调用它的客户类
(3)降低了大型软件系统中的编译依赖性,简化了系统在不同平台之间的移植过程,
因为编译一个子系统不会影响其他的子系统,也不会影响外观对象
(4)只是提供了一个访问子系统的统一入口,并不影响用户直接使用子系统类
4、缺点:
(1)不能很好地限制客户使用子系统类,如果对客户访问子系统类做太多的限制则减少了可变性和灵活性
(2)在不引入抽象外观类的情况下,增加新的子系统可能需要修改外观类或客户端的源代码,违背了“开闭原则”
5、适用环境:
(1)对分层结构系统构建时,使用外观模式定义子系统中每层的入口点可以简化子系统之间的依赖关系
(2)当一个复杂系统的子系统很多时,外观模式可以为系统设计一个简单的接口供外界访问
(3)当客户端与子系统之间存在很大的联系时,引入外观模式可将它们分离,从而提高子系统的独立性和可移植性
// 子系统类 class Light { private name: string; constructor(name) { this.name = name; } open(): void { console.log(this.name + " opened!"); } } class Heater { open(): void { console.log("Heater opened!"); } } // 外观类,统一操作多个子系统 class Facade{ private light1: Light; private light2: Light; private light3: Light; private heater: Heater; constructor() { this.light1 = new Light("light1"); this.light2 = new Light("light2"); this.light3 = new Light("light3"); this.heater = new Heater(); } open(): void { this.light1.open(); this.light2.open(); this.light3.open(); this.heater.open(); } } let facade: Facade = new Facade(); facade.open(); // light1 opened! // light2 opened! // light3 opened! // Heater opened!
享元模式:
1、定义:运用共享技术来有効地支持大量细粒度对象的复用。它通过共享已经存在的对象来
大幅度减少需要创建的对象数量、避免大量相似类的开销,从而提高系统资源的利用率
2、模型结构:
(1)抽象享元类(Flyweight):是所有的具体享元类的基类,为具体享元规范需要实现的公共接口,
非享元的外部状态以参数的形式通过方法传入
(2)具体享元类(ConcreteFlyweight):实现抽象享元角色中所规定的接口
(3)非共享具体享元类(UnsharedConcreteFlyweight):不可共享的外部状态,以参数形式注入具体享元的相关方法中
(4)享元工厂类(FlyweightFactory):负责创建和管理享元角色。当客户对象请求一个享元对象时,
享元工厂检査系统中是否存在符合要求的享元对象,如果存在则提供给客户;
如果不存在的话,则创建一个新的享元对象
3、优点:
(1)享元模式的优点在于它可以极大减少内存中对象的数量,使得相同对象或相似对象在内存中只保存一份
(2)享元模式的外部状态相对独立,而且不会影响其内部状态,从而使得享元对象可以在不同的环境中被共享
4、缺点:
(1)享元模式使得系统更加复杂,需要分离出内部状态和外部状态,这使得程序的逻辑复杂化
(2)读取享元模式的外部状态会使得运行时间稍微变长
注:内部状态是不会随着环境的改变而改变的可共享部分;外部状态是指随环境改变而改变的不可以共享的部分
5、适用环境:
(1)一个系统有大量相同或者相似的对象,由于这类对象的大量使用,造成内存的大量耗费
(2)大部分的对象可以按照内部状态进行分组,且可将不同部分外部化,这样每一个组只需保存一个内部状态
(3)由于享元模式需要额外维护一个保存享元的数据结构,所以应当在有足够多的享元实例时才值得使用享元模式
// 非共享具体享元 class Customer { private name: string; constructor(name: string) { this.name = name; } getName(): string { return this.name; } } // 抽象享元类 Flyweight,书本 interface Book { cell(customer: Customer): void; } // 具体享元类 ConcreteFlyweight,书名 class BookOrder implements Book { private name: string; constructor(name: string) { this.name = name; } cell(customer: Customer): void { console.log(`Cell a book, named ${this.name} and the customer is ${customer.getName()}`); } } // 享元工厂类 FlyweightFactory class BookFactory { // 享元池 private bookPools: Map<string, Book> = new Map<string, Book>(); // 和单例模式结合 private constructor() {} private static factory: BookFactory = new BookFactory(); static getInstance(): BookFactory { return this.factory; } getOrder(bookName: string): Book { let order: Book = null; // 如果存在该书,则从享元池中取出,否则新建一个书对象 if (this.bookPools.has(bookName)) { order = this.bookPools.get(bookName); } else { order = new BookOrder(bookName); this.bookPools.set(bookName, order); } return order; } // 返回创建对象的数目 getTotalObjs(): number { return this.bookPools.size; } } let orders: Book[] = new Array<Book>(); // 确保只有一个工厂对象 let factory: BookFactory = BookFactory.getInstance(); function takeOrders(bookName: string): void { orders.push(factory.getOrder(bookName)); } takeOrders("book1"); takeOrders("book2"); takeOrders("book4"); takeOrders("book2"); takeOrders("book3"); takeOrders("book1"); takeOrders("book5"); takeOrders("book1"); for (let i in orders) { orders[i].cell(new Customer(`customer ${i}`)); } console.log("\n客户一共买了 " + orders.length + " 本书! "); // 客户一共买了 8 本书! console.log("共生成了 " + factory.getTotalObjs() + " 个 Book 对象! "); // 共生成了 5 个 Book 对象!
代理模式:
1、定义:给某一个对象提供一个代理,并由代理对象控制对原对象的引用
2、模型结构:
(1)抽象主题角色(Subject):通过接口或抽象类声明真实主题和代理对象实现的业务方法
(2)代理主题角色(Proxy):提供了与真实主题相同的接口,其内部含有对真实主题的引用,
它可以访问、控制或扩展真实主题的功能
(3)真实主题角色(RealSubject):实现抽象主题中的具体业务,是代理对象所代表的真实对象,是最终要引用的对象
3、优点:
(1)代理模式能够协调调用者和被调用者,在一定程度上降低了系统的耦合度
(2)代理模式在客户端与目标对象之间起到一个中介作用和保护目标对象的作用
(3)代理对象可以扩展目标对象的功能
4、缺点:
(1)在客户端和真实主题之间增加了代理对象,因此有些类型的代理模式可能会造成请求的处理速度变慢
(2)实现代理模式需要额外的工作,有些代理模式的实现非常复杂,增加系统复杂度
5、适用环境:
(1)远程(Remote)代理:为一个位于不同的地址空间的对象提供一个本地的代理对象,
这个不同的地址空间可以是在同一台主机中,也可是在另一台主机中,远程代理又叫做大使(Ambassador)
(2)虚拟(Virtual)代理:如果需要创建一个资源消耗较大的对象,
先创建一个消耗相对较小的对象来表示,真实对象只在需要时才会被真正创建
(3)保护(Protect or Access)代理:控制对一个对象的访问,可以给不同的用户提供不同级别的使用权限
(4)智能引用(Smart Reference)代理:当一个对象被引用时,提供一些额外操作,如记录该对象被调用的次数等
(5)缓冲(Cache)代理:为某一个目标操作的结果提供临时的存储空间,以便多个客户端可以共享这些结果
(6)防火墙(Firewall)代理:保护目标不让恶意用户接近
(7)同步化(Synchronization)代理:使几个用户能够同时使用一个对象而没有冲突
(8)Copy-on-Write代理:它是虚拟代理的一种,把复制(克隆)操作延迟到只有在客户端真正需要时才执行
// 保护代理 // 抽象主题角色 Subject interface Doc { read(): string; } // 真实主题角色 RealSubject class SecretDoc implements Doc { private secret: string = "Secret doc!!!"; private password: string = "123"; read(): string { return this.secret; } checkPsw(password: string): boolean { return this.password === password; } } // 代理主题角色 Proxy class ProtectionSecretDocProxy implements Doc{ private doc: SecretDoc; private inputPsw: string; constructor(password: string) { this.inputPsw = password; this.doc = new SecretDoc(); } // 判断密码是否正确 checkPassword(): boolean { return this.doc.checkPsw(this.inputPsw); } read(): string { if (this.checkPassword()) { return this.doc.read(); } return "Password is wrong!"; } } let userProxy1: ProtectionSecretDocProxy = new ProtectionSecretDocProxy("123"); console.log("User1 get: " + userProxy1.read()); // User1 get: Secret doc!!! let userProxy2: ProtectionSecretDocProxy = new ProtectionSecretDocProxy("234"); console.log("User2 get: " + userProxy2.read()); // User2 get: Password is wrong!