23种设计模式
2024-04-15 16:40:03 206 举报
AI智能生成
设计模式梳理,持续更新中。。。
作者其他创作
大纲/内容
单一职责原则
定义:一个类应该只有一个引起它变化的原因,即一个类只负责一项职责。
优点:
提高代码的可读性和可维护性。
降低类之间的耦合度。
提高系统的可扩展性。
实现方式:
将不同的职责分离到不同的类中。
遵循高内聚、低耦合的设计原则。
应用场景:
当一个类存在多个职责时,可以考虑使用单一职责原则进行重构。
当类的职责过于复杂时,可以考虑将其拆分成多个类。
注意事项:
单一职责原则并不是要求每个类只有一个方法,而是要求每个类只负责一项职责。
总结:
单一职责原则是面向对象设计中的重要原则,能够提高代码的可读性、可维护性和可扩展性。
通过将不同的职责分离到不同的类中,可以降低类之间的耦合度,提高系统的灵活性。
在实际开发中,应该根据具体的业务需求来判断是否需要遵循单一职责原则进行重构。
开放封闭原则
开放封闭原则
定义:软件实体(类、模块、函数等)应该对扩展开放,对修改关闭。
目的:通过扩展实体的行为来满足新的需求,而无需修改已有的代码。
优点:提高代码的可维护性、可扩展性和可复用性。
核心思想:抽象和多态。
关键要素:抽象类、接口、继承、多态、依赖倒置原则。
开放封闭原则的实现方法
使用抽象类或接口定义可扩展的行为。
使用继承来实现新的行为。
使用多态来实现运行时绑定。
使用依赖倒置原则来减少类之间的耦合。
开放封闭原则的应用场景
插件化开发:通过定义接口和抽象类,实现插件的扩展,而不需要修改主程序的代码。
框架设计:通过定义抽象类和接口,让框架的核心部分保持稳定,而让具体实现可以自由扩展。
事件驱动编程:通过定义事件和事件处理器的接口,实现事件的扩展,而不需要修改事件的触发代码。
开放封闭原则的实践指导
尽量使用抽象类或接口来定义可扩展的行为。
遵循单一职责原则,每个类只负责一项职责。
多用组合,少用继承,通过组合关系来扩展行为。
使用依赖倒置原则,依赖抽象而不依赖具体实现。
使用工厂模式、策略模式等设计模式来实现开放封闭原则。<br>
里氏替换原则
里氏替换原则
定义:如果一个类型是子类型,那么它可以被它的父类型所替代,而程序的行为不变。
目的:实现代码的可扩展性、可维护性和可复用性。
关键点:
子类必须完全实现父类的方法,但可以通过重写方法来改变行为。
子类可以增加自己特有的方法。
子类重载父类的方法时,方法的前置条件(即方法的输入参数)要比父类方法的输入参数更宽松。
子类重载父类的方法时,方法的后置条件(即方法的输出结果)要比父类方法的输出结果更严格。
优点:
提高代码的可扩展性,通过增加子类实现新的功能,而不需要修改原有代码。
提高代码的可维护性,通过重写父类方法来改变行为,而不需要修改父类的源代码。
提高代码的可复用性,通过将子类对象赋值给父类变量,可以在不修改代码的情况下,使用子类的特殊功能。
应用场景:
父类和子类之间的关系满足"is-a"关系。
需要使用多态的场景。
需要使用继承的场景。
总结:
里氏替换原则是面向对象设计的重要原则之一,它能够提高代码的可扩展性、可维护性和可复用性。
遵循里氏替换原则可以使代码更加灵活,减少代码的耦合度,提高代码的可测试性。
在使用继承时,要注意父类和子类之间的关系,确保子类能够完全替换父类,而不引起意外的行为变化。
接口分离原则
概念
接口应该尽量细化,不应该包含不需要的方法
一个类对另一个类的依赖应该建立在最小的接口上
原则解释
接口应该具有高内聚性
接口应该与客户端的需求保持一致
接口应该尽量细化,不应该包含不需要的方法
接口应该尽量稳定,避免频繁修改
优点
提高代码的可读性和可维护性
降低类之间的耦合度
提高系统的灵活性和可扩展性
实现方法
将庞大的接口拆分为多个小接口
根据不同的角色和需求定义不同的接口
使用适配器模式将多个接口整合为一个接口
适用场景
当一个接口中的方法过多,功能过于复杂时
当一个接口的实现类需要根据不同的角色进行定制时
当一个接口的实现类需要根据不同的需求进行定制时
注意事项
遵循单一职责原则和接口隔离原则
避免在接口中定义太多的方法
根据实际需求进行合理拆分和整合接口
依赖倒置原则
定义:高层模块不应该依赖低层模块,二者都应该依赖其抽象;抽象不应该依赖细节,细节应该依赖抽象。
目的:降低模块间的耦合性,提高系统的稳定性和可维护性。
优点:
1. 提高代码的可读性和可维护性。
2. 提高系统的扩展性和灵活性。
3.降低了模块间的耦合度。
实现方法:
1. 面向接口编程。
2.依赖注入。
3. 接口隔离原则。
应用场景:
1. 在框架设计中广泛使用。
2. 在类与类之间的关系较为复杂的场景中使用。
3. 在需要扩展性较高的系统中使用。
注意事项:
1. 遵循单一职责原则。
2. 避免循环依赖。
3. 合理划分模块的职责和功能。
4. 避免滥用抽象。依赖倒置原则
迪米特法则
定义
迪米特法则(LoD)又叫最少知识原则(LKP),指的是一个类/模块对其他的类/模块有越少的了解越好。简言之:只跟你最亲密的朋友交谈,不跟陌生人说话。<br>
简单来说迪米特法则想要表达的思想就是: 不该有直接依赖关系的类之间,不要有依赖;有依赖关系的类之间,尽量只依赖必要的接口。<br>
原则
一个对象应该对其他对象有尽可能少的了解。
一个对象只与其直接朋友通信,不与非直接朋友通信。
尽量降低类之间的耦合度。
优点
减少了对象之间的依赖关系,提高了系统的灵活性和可维护性。
降低了代码的复杂度,提高了代码的可读性和可理解性。
增强了代码的可扩展性,便于进行系统的修改和升级。
注意事项
遵循迪米特法则不代表完全不与其他对象通信,而是尽量减少通信的次数和范围。
在设计类之间的关系时,要考虑对象之间的职责和依赖关系,避免不必要的耦合。
合理划分类的职责,确保每个类只负责自己的事情,不涉及其他类的内部细节。
以上是迪米特法则的相关资料的思维导图。
创建型
单例模式Singleton
定义单例模式是一种创建型设计模式,它保证一个类只有一个实例,并提供一个全局访问点。
特点- 类只有一个实例对象- 全局访问点
应用场景- 需要频繁创建和销毁对象的场景- 需要节省系统资源的场景- 需要全局唯一性的场景
实现方式
饿汉式
定义
饿汉式是一种常见的单例模式实现方式,其特点是在类加载时就创建实例对象,因此也被称为静态初始化方式。<br>
特点:
1. 线程安全:由于在类加载时就创建实例对象,所以不存在多线程并发访问的问题。
2. 简单直观:实现简单,不需要考虑线程同步的问题。
实现方式:
1. 将构造函数私有化,禁止外部直接创建对象。
2. 在类内部定义一个静态私有的成员变量,用于保存单例对象。
3. 提供一个公共的静态方法,用于获取单例对象。
代码示例:
优缺点:
优点:
1. 线程安全:由于在类加载时就创建实例对象,所以不存在多线程并发访问的问题。
2. 简单直观:实现简单,不需要考虑线程同步的问题。
缺点:
1. 提前创建:在类加载时就创建实例对象,如果该对象在使用前没有被用到,就会造成内存浪费。
2.无法懒加载:无法实现延迟加载,即只有在使用时才创建对象。
总结:
饿汉式是一种简单直观的单例模式实现方式,适用于在类加载时就创建实例对象的场景。但由于无法实现延迟加载,可能会造成内存浪费。
懒汉式
定义
懒汉式是一种常见的单例模式实现方式之一,它的特点是在需要获取实例时才进行实例化,而不是在类加载时就进行实例化。<br>
特点:
需要获取实例时才进行实例化,而不是在类加载时就进<br>
实现方式:
1.私有化构造方法,防止外部实例化。<br>
2.提供一个静态私有变量作为实例对象。<br>
3. 提供一个公有的静态方法获取实例,如果实例为null,则进行实例化。<br><br>
代码示例:
优缺点:
优点:
1. 延迟加载:只有在需要使用实例时才进行实例化,节省了系统资源。<br>
2. 线程安全:通过加锁保证了多线程环境下的唯一实例。<br>
缺点:
1. 性能较低:由于加锁会导致线程阻塞,降低了系统的性能。<br>
2.实现复杂:需要考虑线程安全性,可能需要使用双重检查锁定等机制。<br>
注意事项
1. 在多线程环境下需要考虑线程安全性,可以使用双重检查锁定等机制来提高性能。
2. 懒汉式单例模式在某些情况下可能存在线程安全问题,可以考虑使用静态内部类实现单例模式来解决这个问题。
应用场景
1. 需要延迟加载的情况,例如数据库连接、日志记录等。
2. 需要保证唯一实例的情况,例如配置文件读取、线程池等。<br>
静态内部类
优点:<br>
1. 懒加载:静态内部类在使用时才会被加载,实现了延迟加载的效果。
2. 线程安全:JVM保证了静态内部类的加载过程是线程安全的,保证了单例的线程安全性。
3. 高效性:静态内部类只会被加载一次,保证了单例的高效性。
实现方式<br>
1. 私有化构造方法,防止外部实例化。
2. 定义一个静态内部类,其中包含一个静态的、final的单例对象。
3. 提供一个公共的静态方法,返回静态内部类中的单例对象。
代码示例
注意事项:<br>
1. 静态内部类中的单例对象是final的,不可修改。
2. 静态内部类中的单例对象是static的,只会被加载一次。
3. 静态内部类的加载是在使用时才会进行,实现了延迟加载的效果。
4. 静态内部类实现的单例模式是线程安全的。
应用场景:<br>
1. 多线程环境下的单例模式:静态内部类实现的单例模式能够在多线程环境下保证单例的线程安全性。
2. 延迟加载的单例模式:静态内部类的方式实现了延迟加载的效果,只有在使用时才会加载单例对象。
枚举方式实
特点
枚举是一种特殊的类,它只能有限个实例
枚举类型的实例在使用时是常量,不可修改
枚举类型的构造函数是私有的,只能在枚举类型内部使用
枚举类型的实例是线程安全的,可以保证在多线程环境下也只会有一个实例
实现步骤
定义一个枚举类型,其中包含一个枚举实例
在枚举类型中定义一个私有的构造函数,用于初始化枚举实例
在枚举类型中定义一个公共的获取实例的方法,用于获取枚举实例
使用枚举类型的方式获取单例实例
代码示例
优点
实现简单,代码量少
线程安全,不需要考虑多线程环境下的同步问题
防止反射攻击和反序列化攻击
缺点
无法懒加载,即在使用时才进行实例化,会在程序启动时就创建实例
无法继承,枚举类型无法被继承,因此无法通过继承来扩展功能
应用场景
需要确保在多线程环境下只有一个实例的情况
需要防止反射攻击和反序列化攻击的情况
不需要懒加载的情况,可以在程序启动时就创建实例
工厂模式Factory
简单工厂
概念
简单工厂是一种创建对象的设计模式,它提供了一个通用的接口来实例化对象,而不需要暴露对象的创建逻辑<br>
优点
1. 隐藏对象的创建细节,客户端只需关心接口和参数即可。
2. 简化了客户端代码,减少了对具体产品的依赖。
3. 可以通过配置文件等方式动态改变产品类<br>
缺点
1. 当新增产品时,需要修改工厂类的代码,违反了开闭原则。
2. 工厂类职责过重,一旦出现问题,整个系统都会受到影响<br>
实现方案
适用场景
1. 需要创建的对象较少。
2. 客户端无需关心对象的创建细节。
3. 需要通过工厂类来动态获取不同类型的对象。
相关角色
1. 简单工厂(SimpleFactory):负责创建对象的工厂类,包含一个创建产品的方法。
2. 抽象产品(Product):定义产品的接口,所有产品都实现这个接口。
3. 具体产品(ConcreteProduct):实现抽象产品接口,具体的产品类。
总结
简单工厂模式通过一个工厂类来创建不同类型的对象,将对象的创建和使用分离,降低了客户端的耦合度。但是它违反了开闭原则,每次新增产品都需要修改工厂类的代码。因此,在实际应用中,我们可以考虑使用工厂方法模式或抽象工厂模式来替代简单工厂模式,以提高代码的可维护性和扩展性。
工厂方法
概念
定义:工厂方法模式是一种创建型设计模式,它提供了一种将对象的创建委托给子类的方式,通过子类来决定实例化哪个具体类。
目的:将对象的创建和使用分离,降低耦合性,增强可扩展性和可维护性。
角色
抽象工厂(Abstract Factory):定义了创建产品的接口,声明了工厂方法。
具体工厂(Concrete Factory):实现抽象工厂接口,具体实现工厂方法,创建具体产品对象。
抽象产品(Abstract Product):定义了产品的接口,声明了产品的公共方法。
具体产品(Concrete Product):实现抽象产品接口,具体实现产品的方法。
优点
符合开闭原则:增加新的具体产品和具体工厂时,不需要修改已有代码,只需要添加新的产品和工厂类。
符合单一职责原则:每个具体工厂类只负责创建一个具体产品对象。
符合依赖倒置原则:客户端通过抽象工厂和抽象产品来创建和使用具体产品,依赖于抽象而不依赖于具体。
缺点
增加了系统的抽象性和理解难度。
每增加一个具体产品类,就需要增加一个对应的具体工厂类,导致类的个数增加,增加了代码的复杂度。
适用场景
当一个类不知道它所需要的对象的类时。
当一个类希望由子类来指定创建对象时。
当一个类将创建对象的职责委托给多个子类中的某一个,并且希望将具体实例化的工作与业务逻辑分离开时。
实现方案
总结
工厂方法模式通过抽象工厂和抽象产品来实现对象的创建和使用分离,降低了耦合性,增强了可扩展性和可维护性。但也增加了系统的抽象性和理解难度,需要根据具体情况选择使用。
抽象工厂模式
定义
抽象工厂模式是一种创建型设计模式,它提供了一种封装一组具有相同主题的单个工厂的方式。它将工厂的实现细节隐藏在一个抽象的接口后面,并允许客户端使用这个接口来创建一系列相关的对象,而不需要关心实际实现细节<br>
适用场景
1. 当一个系统需要独立于它的产品的创建、组合和表示时。
2. 当一个系统需要多个系列的产品,而这些产品之间有一定的约束关系时。
3. 当希望提供一组相关的产品对象,而不需要指定它们的具体类时。
优点
1. 客户端与具体产品的创建代码分离,使得客户端代码更加独立于具体产品的实现细节。
2. 增加新的具体工厂和产品族很方便,符合开闭原则。
缺点
1. 增加新的产品等级结构麻烦,需要修改抽象工厂接口及其所有子类。
2. 增加新的产品族时,需要修改所有的具体工厂类。
结构
抽象工厂(AbstractFactory):声明创建一系列产品对象的操作接口。
具体工厂(ConcreteFactory):实现抽象工厂接口,返回一个具体产品对象。
抽象产品(AbstractProduct):声明一类产品对象的接口。
具体产品(ConcreteProduct):定义一个将被具体工厂创建的产品对象。
实现方案
<br>
总结
抽象工厂模式提供了一种封装一组具有相同主题的单个工厂的方式,使得客户端代码更加独立于具体产品的实现细节。它适用于需要独立于产品的创建、组合和表示的系统,以及需要多个系列的产品,而这些产品之间有一定的约束关系的系统。抽象工厂模式的结构包括抽象工厂、具体工厂、抽象产品和具体产品。它的优点是客户端与具体产品的创建代码分离,增加新的具体工厂和产品族方便;缺点是增加新的产品等级结构和产品族时需要修改抽象工厂和具体工厂类。
建造者模式Builder
介绍
建造者模式是一种创建型设计模式,用于将对象的构建过程与其表示分离。它允许同样的构建过程可以创建不同的表示。
建造者模式的核心思想是将一个复杂对象的构建过程分解为多个简单对象的构建步骤,并通过一个指导者来控制这些步骤的顺序和组合,从而构建出一个完整的对象<br>
角色
产品(Product):表示被构建的复杂对象。包含多个组成部分。
抽象建造者(Builder):定义了构建产品各个部件的抽象接口。包含创建产品和返回产品的方法。
具体建造者(ConcreteBuilder):实现了抽象建造者接口,负责构建和装配各个部件。
指导者(Director):调用具体建造者来创建产品对象。指导者知道具体建造者的具体细节,但不知道产品的具体细节<br>
优缺点
优点
1. 可以将复杂对象的创建过程与其表示分离,使得同样的创建过程可以创建不同的表示。
2. 可以更加精细地控制对象的构建过程。
3. 可以避免重复的构建代码,提高代码复用性<br>
缺点
建造者模式所创建的产品一般具有较多的共同点,其组成部分相似,如果产品之间的差异性很大,则不适合使用建造者模式,因此其使用范围受到一定的限制。
实现方案
与工厂模式的区别
工厂模式是用来创建不同但是相关类型的对象(继承同一父类或者接口的一组子类),由给定的参数来决定创建哪种类型的对象。
建造者模式是用来创建一种类型的复杂对象,通过设置不同的可选参数,“定制化”地创建不同的对象
适用场景
1. 需要创建复杂对象的场景,该对象由多个部分组成,且构建过程稳定。
2. 需要精细控制对象的构建过程,以满足特定需求<br>
原型模式Prototype
概述
原型模式是一种创建型设计模式,它允许通过复制现有对象来创建新对象,而无需显式地使用构造函数。它通过克隆已有对象的属性和方法,来创建新的对象实例。
原型模式适用于创建复杂的对象,或者创建成本较大的对象,通过复制已有对象可以提高性能和效率。
角色
抽象原型类(Prototype):它是声明克隆方法的接口,是所有具体原型类的公共父类,它可以是抽象类也可以是接口.
具体原型类(ConcretePrototype):实现在抽象原型类中声明的克隆方法,在克隆方法中返回自己的一个克隆对象.
客户类(Client):在客户类中,让一个原型对象克隆自身从而创建一个新的对象.由于客户类针对抽象原型类Prototype编程.因此用户可以根据需要选择具体原型类,系统具有较好的扩展性,增加或者替换具体原型类都比较方便.
实现方案
实现方式
浅克隆:只复制对象的基本数据类型的属性,而不复制引用类型的属性。新对象和原型对象共享引用类型属性的地址。
深克隆:不仅复制对象的基本数据类型的属性,还要复制引用类型的属性。新对象和原型对象拥有各自独立的引用类型属性。
优点
可以动态添加或删除原型对象的属性和方法。
可以克隆对象,而无需与它们所属的具体类相耦合。
可以通过改变原型对象,来改变所有克隆对象的属性和方法。
缺点
深克隆的实现较为复杂,需要对引用类型的属性进行递归复制。
克隆对象时,需要注意深克隆和浅克隆的选择,以避免共享引用类型属性时的问题。
适用场景
需要创建大量相似对象的场景,可以通过复制原型对象来提高效率。
需要动态地添加或删除对象的属性和方法的场景,可以通过修改原型对象来实现。
需要避免使用构造函数的场景,可以使用原型模式来创建对象。
与其他模式的关系
原型模式与工厂模式:工厂模式关注对象的创建过程,而原型模式关注对象的复制过程。
原型模式与单例模式:单例模式只允许创建一个对象实例,而原型模式可以创建多个相似的对象实例。
原型模式与建造者模式:建造者模式关注对象的创建细节和顺序,而原型模式关注对象的复制和修改。
结构型
代理模式Proxy
概述
代理模式是一种结构型设计模式,它允许通过提供代理对象来控制对其他对象的访问<br>
角色
1. 抽象主题(Subject):定义了真实主题和代理主题的共同接口,这样在任何使用真实主题的地方都可以使用代理主题。
2. 真实主题(Real Subject):定义了代理所代表的真实对象。
3.代理主题(Proxy Subject):持有对真实主题的引用,在其所实现的接口方法中调用真实主题的方法,并可以在调用前后添加额外的逻辑。
优点
1. 职责分离:代理模式将代理对象和真实对象分离,使得代理对象可以独立于真实对象进行扩展和修改。
2. 控制对真实对象的访问:代理对象可以控制对真实对象的访问权限,可以在访问前后进行一些额外的逻辑操作。
3. 提高性能:代理模式可以通过延迟加载(懒加载)来提高系统的性能,只有在真正需要时才创建真实对象。
缺点
1. 增加复杂性:引入代理对象会增加系统的复杂性,需要额外的代码来处理代理和真实对象之间的关系。
2. 增加请求处理时间:由于代理对象需要在调用真实对象之前进行一些额外的逻辑操作,可能会增加请求的处理时间。
适用场景
1. 远程代理:为一个对象在不同的地址空间提供局部代表,隐藏对象存在于不同地址空间的事实。
2. 虚拟代理:根据需要创建开销很大的对象,通过代理对象来存储真实对象的引用,真实对象在需要的时候才会被真正创建。
3. 安全代理:控制真实对象的访问权限。
4. 智能指引:当调用真实对象的方法时,代理对象可以在调用前后进行额外的操作,如记录日志、缓存等。
JDK代理和CGLIB代理比较
JDK代理
JDK动态代理是Java提供的一种代理技术,通过反射机制在运行时创建代理类和代理对象。主要使用Proxy和InvocationHandler两个类实现。
Proxy类是JDK提供的代理类,通过调用Proxy类的静态方法newProxyInstance()来创建代理对象。
InvocationHandler是一个接口,实现该接口的类需要重写invoke()方法,该方法在代理对象的方法被调用时被调用。
JDK代理只能代理实现了接口的类,不能代理没有实现接口的类。
JDK代理的优点是简单易用,缺点是只能代理接口,不能代理类。
实现方案
CGLIB代理
CGLIB(Code Generation Library)是一个强大的,高性能的代码生成库,可以在运行时扩展Java类和实现接口。
CGLIB可以在运行时动态生成代理类,通过继承目标类,重写目标类的方法来实现代理。
CGLIB代理不需要目标类实现接口,可以代理没有实现接口的类。
CGLIB代理通过字节码增强技术,对目标类进行增强,生成一个子类作为代理类。
CGLIB代理的优点是可以代理没有实现接口的类,缺点是生成的代理类比较庞大,性能相对较低。
CGLIB代理需要依赖CGLIB库,使用时需要引入相应的依赖。
实现方案
总结
JDK代理和CGLIB代理都是Java中常用的代理技术。
JDK代理只能代理接口,CGLIB代理可以代理类和接口。
JDK代理通过反射机制实现,CGLIB代理通过字节码增强技术实现。
JDK代理生成的代理对象相对较轻量,CGLIB代理生成的代理对象相对较重。
JDK代理性能相对较高,CGLIB代理性能相对较低。
选择使用JDK代理还是CGLIB代理取决于具体的需求和场景。
总结
代理模式通过引入代理对象来控制对真实对象的访问,可以实现职责分离、控制访问权限和提高性能。在实际应用中,可以根据具体的需求选择不同类型的代理模式,如远程代理、虚拟代理、安全代理和智能指引等。
桥接模式Bridge
概述<br>
桥接模式是一种结构型设计模式,它将抽象部分与实现部分分离,使它们可以独立变化。通过将实现部分与抽象部分分离,桥接模式可以提高系统的灵活性和可扩展性。
角色
抽象类(Abstraction):定义抽象部分的接口,通常包含一个对实现部分对象的引用。
扩展抽象类(RefinedAbstraction):对抽象类进行扩展,可以调用实现部分的方法。
实现类接口(Implementor):定义实现部分的接口,通常包含抽象类中定义的方法。
具体实现类(ConcreteImplementor):实现实现类接口的具体类<br>
实现方案
优点
1. 分离抽象部分和实现部分,可以独立地变化。
2. 提高了系统的灵活性,可以动态地切换实现。
3. 桥接模式符合开闭原则,增加新的抽象类和实现类都很方便<br>
与适配器模式的区别
桥接模式关注于将抽象部分和实现部分分离,使得它们可以独立变化;适配器模式关注于将一个接口转换成另一个接口,使得原本不兼容的类可以一起工作。
与装饰器模式的区别
桥接模式关注于将抽象部分和实现部分分离,以便它们可以独立变化;装饰器模式关注于在不改变接口的情况下动态地扩展对象的功能
适用场景
1. 当一个类存在两个(或多个)独立变化的维度时,可以使用桥接模式将这些维度分离,使它们可以独立扩展。
2. 当一个系统需要在抽象化和具体化角色之间增加更多的灵活性时,可以使用桥接模式<br>
装饰器模式Decorator
概述
装饰器模式是一种结构型设计模式,它允许向现有对象添加新功能,而无需修改其代码。通过将对象包装在装饰器类中,可以在运行时动态地添加、修改或删除对象的行为。
核心角色
Component: 定义一个抽象接口,用于被具体组件和装饰器类实现。
ConcreteComponent: 实现Component接口的具体组件类。
Decorator: 维持一个指向Component对象的引用,并实现与Component接口一致的接口。
ConcreteDecorator: 扩展Decorator类的功能,具体实现装饰器的逻辑。
实现方案
优点
动态地扩展对象的功能,避免了静态继承的限制。
遵循开闭原则,不需要修改现有代码就可以添加新功能。
符合单一职责原则,每个装饰器类只关注特定的功能。
缺点<br>
使用装饰器模式会增加许多具体装饰器类,增加了系统的复杂性。<br>
装饰器模式会导致设计中出现许多小对象,增加了系统的开销。<br>
适用场景
需要在不修改现有代码的情况下,动态地扩展对象的功能。
需要为多个独立对象添加相同的功能,但是不希望通过继承来实现。
适配器模式Adapter
概述
适配器模式是一种结构型设计模式,用于将一个类的接口转换成客户端所期望的另一个接口。它允许不兼容的类能够合作,使得原本由于接口不匹配而无法一起工作的类能够一起工作。
适配器模式通过创建一个适配器类,将原本不兼容的接口转换成目标接口,从而实现类之间的互操作性。
适配器模式是一种补救措施,用于解决接口不兼容的问题,通常在系统设计阶段使用。
角色
目标接口(Target):定义客户端所期待的接口。
适配器(Adapter):将源接口转换成目标接口的类。
源接口(Adaptee):需要被适配的类。
实现方式
类适配器:通过多重继承实现适配器类,同时继承目标接口和源接口。
对象适配器:通过组合的方式,在适配器类中持有源接口的实例,实现目标接口的方法。
应用场景
将旧的接口转换成新的接口,使得旧的类能够与新的类一起工作。
在已有的系统中,引入第三方库或组件,需要将其接口转换成系统内部的接口。
在系统设计阶段,预测可能需要适配的接口,提前实现适配器,以便后续的扩展和维护。
优缺点
优点:
提高了类的复用性,使得原本不兼容的类能够一起工作。
解耦了客户端和被适配类之间的依赖关系。
可以灵活地增加新的适配器,扩展系统的功能。
缺点:
增加了系统的复杂性,引入了额外的类和接口。
适配器模式需要全面考虑系统的设计,不适用于简单的情况。
总结
适配器模式是一种解决接口不兼容的问题的设计模式。
它通过创建适配器类,将源接口转换成目标接口,使得原本不兼容的类能够一起工作。
适配器模式提高了类的复用性,解耦了客户端和被适配类之间的依赖关系,但也增加了系统的复杂性。
外观模式Facade
概述
外观模式是一种结构型设计模式,它为复杂的子系统提供了一个简单的接口,以便与客户端进行交互。该模式隐藏了子系统的复杂性,并将其封装在一个外观类中。<br>
角色
外观(Facade):提供统一的接口,将客户端的请求转发给子系统进行处理
子系统(SubSystem):实现子系统的功能,处理外观接口传递过来的请求
实现方案
适用场景
当需要使用一个复杂的子系统时,可以使用外观模式来简化客户端与子系统之间的交互
当需要对子系统进行分层,通过外观接口来简化上层与下层之间的依赖关系
当需要将子系统进行解耦,使得子系统的变化不会影响到客户端
优缺点
优点
简化了客户端与子系统之间的交互,客户端只需要调用外观接口即可,不需要了解子系统的具体实现
降低了客户端与子系统之间的耦合度,客户端只依赖外观接口,而不依赖具体的子系统接口
提高了子系统的可维护性和可扩展性,因为客户端与子系统解耦,子系统的变化不会影响到客户端
缺点
不能很好的控制客户端直接使用子系统类,如果客户端访问子系统类做太多的限制则减少了可变性和灵活性
如果设计不当,增加新的子系统可能需要修改外观类的源代码,违背了开闭原则
适用场景
当一个复杂的子系统需要与客户端进行交互时,可以使用外观模式来简化交互过程。
当需要将子系统的接口与实现分离时,可以使用外观模式来隐藏子系统的实现细节<br>
组合模式Composite
概述
概述组合模式是一种结构型设计模式,它允许将对象组合成树形结构来表示整体-部分层次结构。该模式使得客户端能够统一处理单个对象和组合对象,无需区分它们的差异。将对象组合成树形结构以表示“部分-整体”的层次结构,使得用户对单个对象和组合对象的使用具有一致性。<br>
角色
组件(Component)
抽象类或接口,定义了叶子节点和组合节点的共同操作。
叶子节点(Leaf)
没有子节点的节点。
组合节点(Composite)
包含子节点的节点。
客户端(Client)
通过组件接口操作组合对象。
实现方案
优点
定义了包含基本对象和组合对象的类层次结构。
简化了客户端代码,客户端可以一致地使用组合对象和单个对象。
更容易增加新类型的组合对象,无需修改现有代码。
缺点
限制了组合对象的类型,只能是同一种类型或接口。
增加了系统的复杂性,使得设计变得更加抽象。
应用实例
文件系统
图形界面中的布局管理器
公司组织架构图
适用场景
需要表示对象的部分-整体层次结构。
希望用户可以忽略组合对象与单个对象的差异,统一使用。
希望在不改变用户代码的情况下,增加新的组合对象。
享元模式Flyweight
概述
享元模式是一种结构型设计模式,用于优化大量细粒度对象的共享和复用,以减少内存使用和提高性能。
角色
享元工厂(FlyweightFactory):负责创建和管理享元对象。
抽象享元(Flyweight):定义享元对象的接口,声明公共方法。
具体享元(ConcreteFlyweight):实现抽象享元接口,实现具体的业务逻辑。
非共享具体享元(UnsharedConcreteFlyweight):不可共享的享元对象,一般不会出现在享元工厂中。
实现方案
优点
减少内存使用:通过共享对象减少内存中对象的数量。
提高性能:共享对象可以在多个上下文中共享,减少了创建和销毁对象的开销。
简化代码:将对象的状态分为内部状态和外部状态,减少了对象的复杂性。
缺点
为了使对象可以共享,需要将享元对象的部分状态外部化,分离内部状态和外部状态,使程序逻辑复杂
适用场景
系统中存在大量相似对象,占用大量内存。
对象的大部分状态可以外部化,并且可以将这些状态传递给对象。
对象可以被共享,且多个上下文需要共享这些对象。
行为型
观察者模式Observer
概述
观察者模式是一种行为设计模式,用于在对象之间定义一对多的依赖关系,以便当一个对象的状态发生改变时,其相关对象都能得到通知并自动更新。
角色
- Subject(主题):定义了被观察者对象的接口,可以添加、删除和通知观察者对象。
- ConcreteSubject(具体主题):实现了主题接口,维护一个观察者对象列表,并在状态发生改变时通知观察者。
- Observer(观察者):定义了观察者对象的接口,包含一个更新方法,用于接收主题通知并进行相应操作。
- ConcreteObserver(具体观察者):实现了观察者接口,具体定义了接收到通知时的操作。
实现方案
优点
- 解耦性:主题和观察者之间是松耦合的,它们可以独立地进行扩展和修改。
- 可扩展性:可以在任何时候增加新的观察者,无需修改主题代码。
- 灵活性:可以根据需求增加或删除观察者,实现动态的对象间通信。
缺点
如果观察者过多或者观察者的处理逻辑复杂,会导致主题通知观察者的时间延迟
可能导致系统中观察者对象与主题对象之间的循环依赖,导致系统崩溃或者内存泄漏
应用场景
- 当一个对象的改变需要同时改变其他对象,且不知道有多少对象需要改变时,可以使用观察者模式。
- 当一个对象必须通知其他对象,但又不希望与被通知的对象形成紧耦合关系时,可以使用观察者模式。
- 当一个抽象模型有两个方面,其中一个方面依赖于另一个方面,可以使用观察者模式实现解耦。
注意事项
- 避免观察者和主题之间的循环依赖,以免造成系统崩溃。
- 注意观察者的顺序,确保通知的顺序符合预期。
模版方法模式TemplateMethod
概述
模版方法模式是一种行为设计模式,用于定义一个算法的骨架,将一些步骤延迟到子类中实现。模版方法模式的核心思想是将算法的结构固定下来,但是留出一些可变的部分供子类实现。
角色:<br>
抽象类(AbstractClass):定义一个模版方法,其中包含了算法的骨架和一些基本的操作方法,这些操作方法可以是具体方法、抽象方法或钩子方法。
具体类(ConcreteClass):继承抽象类,实现抽象方法和钩子方法,完成算法中的具体步骤。
实现方案:<br>
<br>
优点:<br>
提高代码复用性:将公共的部分抽取到父类中,子类只需要实现特定的步骤,可以避免重复代码的编写。
提高扩展性:通过继承的方式,可以很方便地新增或修改算法的步骤。
符合开闭原则:抽象类定义了算法的骨架,具体的实现可以在子类中进行扩展,不需要修改抽象类的代码。
适用性:<br>
当多个子类有共同的行为,但是实现细节各不相同时,可以使用模版方法模式。
当需要通过一个固定的算法骨架来实现某个功能,但是具体的步骤可能有所不同时,可以使用模版方法模式。
当需要控制子类的扩展,只允许在特定的步骤进行扩展时,可以使用模版方法模式。
应用场景包括:<br>
多个子类有共同的行为,但是实现细节各不相同。
需要通过一个固定的算法骨架来实现某个功能,但是具体的步骤可能有所不同。
需要控制子类的扩展,只允许在特定的步骤进行扩展。
与其他相关模式的比较:<br>
模版方法模式与策略模式:模版方法模式通过继承的方式实现算法的扩展,而策略模式通过组合的方式实现算法的扩展。
模版方法模式与工厂方法模式:模版方法模式将算法的骨架固定下来,具体的步骤由子类实现,而工厂方法模式将对象的创建延迟到子类中实现。
模版方法模式与装饰器模式:模版方法模式通过继承的方式实现算法的扩展,而装饰器模式通过包装对象的方式实现功能的增强。
策略模式Strategy
概述
策略模式是一种行为型设计模式,它定义了一系列算法,并将每个算法封装在可以相互替换的策略类中,从而使得算法可以独立于客户端而变化。策略模式可以使得算法的变化独立于使用算法的客户端,提高了代码的灵活性和可维护性。
角色
抽象策略(Strategy):定义了一个公共接口,用于封装不同算法的具体实现。
具体策略(Concrete Strategy):实现了抽象策略接口,提供具体的算法实现。
环境(Context):持有一个策略对象的引用,用于调用具体策略的算法。
实现方案
优缺点
优点
1. 算法可以独立于客户端而变化,增加了代码的灵活性和可维护性。
2.可以避免使用大量的条件语句,提高了代码的可读性。
3. 策略类之间可以自由切换,扩展性良好。
缺点
1. 客户端必须了解所有的策略类,并自行决定使用哪个策略类。
2. 策略模式会增加系统中类的数量。
应用场景
1. 需要在不同时间应用不同的算法或策略。
2. 需要封装算法的具体实现,使得算法可以独立于客户端而变化。
3.一个类定义了多种行为,并且这些行为在该类的操作中以多个条件语句的形式出现
策略模式与其他模式的关系
1. 策略模式和工厂模式:策略模式通常使用工厂模式来创建具体策略对象。
2. 策略模式和状态模式:策略模式封装了不同的算法,而状态模式封装了不同的状态。两者都可以通过切换不同的策略或状态来改变对象的行为。
3. 策略模式和模板方法模式:策略模式将算法封装在具体策略中,而模板方法模式将算法封装在抽象类中。两者都是通过继承来实现算法的变化。
职责链模式Chain of responsibility
概述
避免将一个请求的发送者与接收者耦合在一起,让多个对象都有机会处理请求.将接收请求的对象连接成一条链,并且沿着这条链传递请求,直到有一个对象能够处理它为止
角色
抽象处理者(Handler)角色:定义一个处理请求的接口,包含抽象处理方法和一个后继连接(链上的每个处理者都有一个成员变量来保存对于下一处理者的引用) 。
具体处理者(Concrete Handler)角色:实现抽象处理者的处理方法,判断能否处理本次请求,如果可以处理请求则处理,否则将该请求转给它的后继者。
客户类(Client)角色:创建处理链,并向链头的具体处理者对象提交请求,它不关心处理细节和请求的传递过程。
实现方案
优缺点
优点
1.降低了对象之间的耦合度,降低了请求发送者和接收者的耦合度
2.增强了系统的可扩展性,可以根据需要增加新的请求处理类,满足开闭原则。
3.增强了对象指派职责的灵活性,当工作流程发生变化,可以动态地改变链内的成员或者修改它们的次序,也可动态地新增或者删除责任
4.简化了对象之间的连接,一个对象只需保持一个指向其后继者的引用,不需保持其他所有处理者的引用,这避免了使用众多的 if 或者 if···else 语句
5.责任分担,每个类只需要处理自己该处理的工作,不能处理的传递给下一个对象完成,明确各类的责任范围,符合类的单一职责原则
缺点
1.不能保证每个请求一定被处理。由于一个请求没有明确的接收者,所以不能保证它一定会被处理,该请求可能一直传到链的末端都得不到处理。
2.对比较长的职责链,请求的处理可能涉及多个处理对象,系统性能将受到一定影响。
3.职责链建立的合理性要靠客户端来保证,增加了客户端的复杂性,可能会由于职责链的错误设置而导致系统出错,如可能会造成循环调用。
应用场景
1.在运行时需要动态使用多个关联对象来处理同一次请求时。比如,请假流程、员工入职流程、编译打包发布上线流程等。
2.不想让使用者知道具体的处理逻辑时。比如,做权限校验的登录拦截器。
3.需要动态更换处理对象时。比如,工单处理系统、网关 API 过滤规则系统等。
4.职责链模式常被用在框架开发中,用来实现框架的过滤器、拦截器功能,让框架的使用者在不修改源码的情况下,添加新的过滤拦截功能.
状态模式State
概述
允许一个对象在其内部状态改变时改变它的行为. 对象看起来似乎修改了它的类.状态模式就是用于解决系统中复杂对象的状态转换以及不同状态下行为的封装问题. 状态模式将一个对象的状态从该对象中分离出来,封装到专门的状态类中(用类来表示状态) ,使得对象状态可以灵活变化.
角色
上下文信息类(Context):实际上就是存储当前状态的类,对外提供更新状态的操作。在该类中维护着一个抽象状态接口State实例,这个实例定义当前状态.
抽象状态类(State):可以是一个接口或抽象类,用于定义声明状态更新的操作方法有哪些,具体实现由子类完成。
具体状态类(StateA 等):实现抽象状态类定义的方法,根据具体的场景来指定对应状态改变后的代码实现逻辑。
实现方案
优缺点
优点
1.将所有与某个状态有关的行为放到一个类中,并且可以方便地增加新的状态,只需要改变对象状态即可改变对象的行为。
2.允许状态转换逻辑与状态对象合成一体,而不是某一个巨大的条件语句块
缺点
1.状态模式的使用必然会增加系统类和对象的个数。
2.状态模式的结构与实现都较为复杂,如果使用不当将导致程序结构和代码的混乱。
3.状态模式对"开闭原则"的支持并不太好 (添加新的状态类需要修改那些负责状态转换的源代码)。
应用场景
1.对象根据自身状态的变化来进行不同行为的操作时, 比如,购物订单状态。
2.对象需要根据自身变量的当前值改变行为,不期望使用大量 if-else 语句时, 比如,商品库存状态。
3.对于某些确定的状态和行为,不想使用重复代码时, 比如,某一个会员当天的购物浏览记录。
迭代器模式Iterator
概述
迭代器提供一种对容器对象中的各个元素进行访问的方法,而又不需要暴露该对象的内部细节。在软件系统中,容器对象拥有两个职责: 一是存储数据,而是遍历数据.从依赖性上看,前者是聚合对象的基本职责.而后者是可变化的,又是可分离的.因此可以将遍历数据的行为从容器中抽取出来,封装到迭代器对象中,由迭代器来提供遍历数据的行为,这将简化聚合对象的设计,更加符合单一职责原则
角色
抽象集合(Aggregate)角色:用于存储和管理元素对象, 定义存储、添加、删除集合元素的功能,并且声明了一个createIterator()方法用于创建迭代器对象。
具体集合(ConcreteAggregate)角色:实现抽象集合类,返回一个具体迭代器的实例。
抽象迭代器(Iterator)角色:定义访问和遍历聚合元素的接口,通常包含 hasNext()、next() 等方法。
具体迭代器(Concretelterator)角色:实现抽象迭代器接口中所定义的方法,完成对集合对象的遍历,同时记录遍历的当前位置。
实现方案
优缺点
优点
1.迭代器模式支持以不同方式遍历一个集合对象,在同一个集合对象上可以定义多种遍历方式. 在迭代器模式中只需要用一个不同的迭代器来替换原有的迭代器,即可改变遍历算法,也可以自己定义迭代器的子类以支持新的遍历方式.
2.迭代器简化了集合类。由于引入了迭代器,在原有的集合对象中不需要再自行提供数据遍历等方法,这样可以简化集合类的设计。
3.在迭代器模式中,由于引入了抽象层,增加新的集合类和迭代器类都很方便,无须修改原有代码,满足 "基于接口编程而非实现" 和 "开闭原则" 的要求。
缺点
1.由于迭代器模式将存储数据和遍历数据的职责分离,增加了类的个数,这在一定程度上增加了系统的复杂性。
2.抽象迭代器的设计难度较大,需要充分考虑到系统将来的扩展.
应用场景
1.减少程序中重复的遍历代码
2.当需要为遍历不同的集合结构提供一个统一的接口时或者当访问一个集合对象的内容而无须暴露其内部细节的表示时。
访问者模式Visitor
概述
访问者模式是一种行为型设计模式,它允许在不改变现有对象结构的情况下定义新操作。该模式将操作和对象结构分离,使得操作可以独立变化。访问者模式主要解决的是数据与算法的耦合问题, 尤其是在数据结构比较稳定,而算法多变的情况下.为了不污染数据本身,访问者会将多种算法独立归档,并在访问数据时根据数据类型自动切换到对应的算法,实现数据的自动响应机制,并确保算法的自由扩展.
角色
抽象访问者(Visitor)角色:可以是接口或者抽象类,定义了一系列操作方法,用来处理所有数据元素,通常为同名的访问方法,并以数据元素类作为入参来确定那个重载方法被调用
具体访问者(ConcreteVisitor)角色:访问者接口的实现类,可以有多个实现,每个访问者都需要实现所有数据元素类型的访问重载方法.
抽象元素(Element)角色:被访问的数据元素接口,定义了一个接受访问者的方法(accept),其意义是指,每一个元素都要可以被访问者访问。
具体元素(ConcreteElement)角色: 具体数据元素实现类,提供接受访问方法的具体实现,而这个具体的实现,通常情况下是使用访问者提供的访问该元素类的方法,其accept实现方法中调用访问者并将自己 "this" 传回。
对象结构(Object Structure)角色:包含所有可能被访问的数据对象的容器,可以提供数据对象的迭代功能,可以是任意类型的数据结构.
客户端 ( Client ) : 使用容器并初始化其中各类数据元素,并选择合适的访问者处理容器中的所有数据对象.
实现方案
优缺点
优点
1.扩展性好,在不修改对象结构中的元素的情况下,为对象结构中的元素添加新的功能。
2.复用性好,通过访问者来定义整个对象结构通用的功能,从而提高复用程度。
3.分离无关行为,通过访问者来分离无关的行为,把相关的行为封装在一起,构成一个访问者,这样每一个访问者的功能都比较单一。
缺点
1.对象结构变化很困难,在访问者模式中,每增加一个新的元素类,都要在每一个具体访问者类中增加相应的具体操作,这违背了“开闭原则”。
2.违反了依赖倒置原则,访问者模式依赖了具体类,而没有依赖抽象类。
应用场景
1.当对象的数据结构相对稳定,而操作却经常变化的时候。 比如,上面例子中路由器本身的内部构造会怎么变化,但是在不同操作系统下的操作可能会经常变化,比如,发送数据、接收数据等
2.需要将数据结构与不常用的操作进行分离的时候。 比如,扫描文件内容这个动作通常不是文件常用的操作,但是对于文件夹和文件来说,和数据结构本身没有太大关系,扫描是一个额外的动作,如果给每个文件都添加一个扫描操作会太过于重复,这时采用访问者模式是非常合适的,能够很好分离文件自身的遍历操作和外部的扫描操作。
3.需要在运行时动态决定使用哪些对象和方法的时候。 比如,对于监控系统来说,很多时候需要监控运行时的程序状态,但大多数时候又无法预知对象编译时的状态和参数,这时使用访问者模式就可以动态增加监控行为。
备忘录模式Memento
概述
备忘录模式是一种行为型设计模式,它在不破坏封装的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,这样可以在以后将对象恢复到原先保存的状态.备忘录模式提供了一种对象状态的撤销实现机制,当系统中某一个对象需要恢复到某一历史状态时可以使用备忘录模式进行设计.
角色
发起人(Originator)角色:状态需要被记录的元对象类, 记录当前时刻的内部状态信息,提供创建备忘录和恢复备忘录数据的功能,实现其他业务功能,它可以访问备忘录里的所有信息。
备忘录(Memento)角色:负责存储发起人的内部状态,在需要的时候提供这些内部状态给发起人。
看护人(Caretaker)角色:对备忘录进行管理,提供保存与获取备忘录的功能,但其不能对备忘录的内容进行访问与修改。
实现方案
优缺点
优点
1.提供了一种状态恢复的实现机制,使得用户可以方便的回到一个特定的历史步骤,当新的状态无效或者存在问题的时候,可以使用暂时存储起来的备忘录将状态复原.
2.备忘录实现了对信息的封装,一个备忘录对象是一种发起者对象状态的表示,不会被其他代码所改动.备忘录保存了发起者的状态,采用集合来存储备忘录可以实现多次撤销的操作
缺点
1.资源消耗过大,如果需要保存的发起者类的成员变量比较多, 就不可避免的需要占用大量的存储空间,每保存一次对象的状态,都需要消耗一定系统资源
应用场景
1.需要保存一个对象在某一时刻的状态时,可以使用备忘录模式.
2.不希望外界直接访问对象内部状态时.
命令模式Command
概述
命令模式将请求(命令)封装为一个对象,这样可以使用不同的请求参数化其他对象(将不 同请求依赖注入到其他对象),并且能够支持请求(命令)的排队执行、记录日志、撤销等 (附加控制)功能。命令模式的核心是将指令信息封装成一个对象,并将此对象作为参数发送给接收方去执行,达到使命令的请求与执行方解耦,双方只通过传递各种命令对象来完成任务.
角色
抽象命令类(Command)角色: 定义命令的接口,声明执行的方法。
具体命令(Concrete Command)角色:具体的命令,实现命令接口;通常会持有接收者,并调用接收者的功能来完成命令要执行的操作。
实现者/接收者(Receiver)角色: 接收者,真正执行命令的对象。任何类都可能成为一个接收者,只要它能够实现命令要求实现的相应功能。
调用者/请求者(Invoker)角色: 要求命令对象执行请求,通常会持有命令对象,可以持有很多的命令对象。这个是客户端真正触发命令并要求命令执行相应操作的地方,也就是说相当于使用命令对象的入口。
实现方案
优缺点
优点
1.降低系统的耦合度。命令模式能将调用操作的对象与实现该操作的对象解耦。
2.增加或删除命令非常方便。采用命令模式增加与删除命令不会影响其他类,它满足“开闭原则”,对扩展比较灵活。
3.可以实现宏命令。命令模式可以与组合模式结合,将多个命令装配成一个组合命令,即宏命令。
缺点
1.使用命令模式可能会导致某些系统有过多的具体命令类。
2.系统结构更加复杂。
应用场景
1.系统需要将请求调用者和请求接收者解耦,使得调用者和接收者不直接交互。
2.系统需要在不同的时间指定请求、将请求排队和执行请求。
3.系统需要支持命令的撤销(Undo)操作和恢复(Redo)操作。
解释器模式Interpreter
概述
解释器模式I用于定义语言的语法规则表示,并提供解释器来处理句子中的语法。解释器模式描述了如何为简单的语言定义一个文法,如何在该语言中表示一个句子,以及如何解释这些句子.
角色
抽象表达式(Abstract Expression)角色:定义解释器的接口,约定解释器的解释操作,主要包含解释方法 interpret()。
终结符表达式(Terminal Expression)角色:是抽象表达式的子类,用来实现文法中与终结符相关的操作,文法中的每一个终结符都有一个具体终结表达式与之相对应。上例中的value 是终结符表达式.
非终结符表达式(Nonterminal Expression)角色:也是抽象表达式的子类,用来实现文法中与非终结符相关的操作,文法中的每条规则都对应于一个非终结符表达式。上例中的 plus , minus 都是非终结符表达式
环境(Context)角色:通常包含各个解释器需要的数据或是公共的功能,一般用来传递被所有解释器共享的数据,后面的解释器可以从这里获取这些值。
客户端(Client):主要任务是将需要分析的句子或表达式转换成使用解释器对象描述的抽象语法树,然后调用解释器的解释方法,当然也可以通过环境角色间接访问解释器的解释方法。
实现方案
优缺点
优点
1.易于改变和扩展文法:解释器模式中使用类来表示语言的文法规则,可以通过继承等机制改变或者扩展文法.每一个文法规则都可以表示为一个类,可以快速的实现一个迷你的语言
2.实现文法比较容易:在抽象语法树中每一个表达式节点类的实现方式都是相似的,这些类的代码编写都不会特别复杂
3.增加新的解释表达式比较方便:如果用户需要增加新的解释表达式,只需要对应增加一个新的表达式类就可以了.原有的表达式类不需要修改,符合开闭原则
缺点
1.对于复杂文法难以维护:在解释器中一条规则至少要定义一个类,因此一个语言中如果有太多的文法规则,就会使类的个数急剧增加,当值系统的维护难以管理.
2.执行效率低:在解释器模式中大量的使用了循环和递归调用,所有复杂的句子执行起来,整个过程也是非常的繁琐
应用场景
1.当语言的文法比较简单,并且执行效率不是关键问题.
2.当问题重复出现,且可以用一种简单的语言来进行表达
3.当一个语言需要解释执行,并且语言中的句子可以表示为一个抽象的语法树的时候
中介者模式Mediator
概述
用一个中介对象封装一系列对象交互,中介者使各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。中介者对象就是用于处理对象与对象之间的直接交互,封装了多个对象之间的交互细节。
角色
抽象中介者(Mediator):定义了各个具体中介者类的统一接口,用于与各个同事对象进行通信。
具体中介者(ConcreteMediator):实现了抽象中介者接口,协调各个同事对象的行为。
抽象同事类(Colleague):定义了各个同事类的统一接口,维护一个抽象中介者的引用,用于与中介者进行通信。
具体同事类(ConcreteColleague):实现了抽象同事类接口,与其他同事对象进行通信时,通过中介者进行协调<br>
实现方案
优缺点
优点
减少了对象之间的直接依赖,降低了耦合度。
将对象间的交互关系集中管理和控制,简化了系统的设计和维护。
增加了系统的灵活性和可扩展性,可以方便地增加新的同事类和中介者类<br>
缺点
中介者对象会变得复杂且庞大,承担了较多的责任,可能会导致中介者对象难以维护和理解。
中介者模式将原本多个对象之间的交互转移到了中介者对象中,可能会导致中介者对象过于庞大,处理逻辑复杂<br>
适用场景
系统中对象之间存在复杂的交互关系,导致彼此之间的依赖关系难以维护和理解。
系统中的对象需要通过中介者进行统一的协调,以实现某些共同的行为。
系统中需要灵活地增加新的对象,且对象之间的交互关系不确定或有变化的情况<br>
0 条评论
下一页