高级软件设计
工厂方法模式-创建型模式
如何理解工厂方法模式
工厂方法模式定义了一个创建对象的接口(抽象工厂),但让子类(具体工厂)决定要实例化的类(具体产品)是哪一个。工厂方法让类的实例化延迟到子类。
一个便于理解的实例:汽车工厂(抽象工厂)造车(抽象产品),汽车工厂有宝马(具体工厂)、奔驰(具体工厂),对应造的是宝马汽车(具体产品)和奔驰汽车(具体产品)。
工厂方法模式的类图
工厂方法模式的组成
- 抽象工厂:具体工厂的父类;描述具体工厂的公共接口
- 具体工厂:抽象工厂的子类,被外界调用;描述具体工厂,用于创建产品实例
- 抽象产品:具体产品的父类;描述具体产品的公共接口
- 具体产品:抽象产品的子类,由工厂类创建;描述要生产的具体产品
工厂方法模式的代码实例
//抽象工厂类,工厂里有制造产品的方法 |
工厂方法模式的优缺点
优点:
- 封装性:将对象的创建过程封装在工厂类中,客户端无需关心对象的具体创建过程,只需知道所需产品的类型即可。
- 灵活性:通过增加新的具体工厂和具体产品类,可以很容易地扩展系统以支持新的产品类型,符合开闭原则。
- 解耦:客户端与具体产品类解耦,只需要和抽象产品以及具体工厂打交道,降低了系统的耦合度。
- 代码结构清晰:通过工厂方法模式,可以将产品类的实例化代码集中在一起,使得代码结构更加清晰,易于维护。
缺点:
- 类的层级结构增加:引入了额外的抽象层和具体工厂类,可能导致类的层级结构变得复杂。
- 增加系统的理解与设计难度:对于不熟悉工厂方法模式的开发者,可能需要花费更多时间理解系统的设计和实现。
- 每次新增产品时都需要添加新的具体工厂:如果产品种类繁多,可能会导致具体工厂类数量的增长。
和其他设计模式的对比
比较模板方法模式和工厂模式
- 工厂模式是模板方法的特殊实现,工厂里面的创建对象,就相当于只有一个步骤的模板方法模式,这个步骤交给子类去实现。
比较抽象工厂模式和工厂模式
- 工厂方法模式可以看成是抽象方法模式的一种特例,工厂方法模式是创建一个产品结构的,而抽象工厂模式是用来创建多个产品结构的。工厂方法只有一个产品抽象类,而抽象工厂有多个产品抽象类。工厂方法模式中的工厂具体类只能创建一类产品类对象,而抽象工厂模式的具体工厂可以创建多个产品类的实例。
工厂方法模式的适用场景
- 对象创建过程比较复杂:当创建对象涉及大量初始化工作,或者需要依赖具体环境、条件来确定创建何种对象时,工厂方法模式可以将这些复杂性封装在工厂类中。
- 系统需要支持多种产品类型:如果有多种不同类型的产品需要创建,并且希望在不修改客户端代码的情况下引入新产品,工厂方法模式可以很好地适应这种需求。
- 隔离直接创建对象的责任:如果不想让客户端直接接触对象创建的细节,或者需要将对象的创建过程与使用过程分离,可以使用工厂方法模式。
其他补充内容
- 简单工厂不符合开闭,工厂方法符合开闭。
- 工厂模式对工厂的要求:封装性,即工厂应隐藏产品的创建细节,用户只需要知道如何使用工厂,而不需要了解产品是如何被创建和组装的;灵活性,即工厂应能够根据不同的条件或参数生产不同类型的产品;解耦性,即工厂模式的一个重要目的是解耦产品的创建和使用,降低系统各部分之间的依赖关系。
- 工厂模式对产品的要求:抽象性,即产品通常是遵循一定接口或抽象类定义的,这样工厂就可以生产具有共同特性和行为的不同具体产品;可扩展性,即产品设计应易于扩展,以便在不修改工厂代码的情况下,可以引入新的产品类型;一致性,即所有产品都应该实现相同的接口或继承自同一个抽象类,确保它们在使用上具有一致性。
- 三种工厂模式的对比:简单工厂模式适用于产品种类较少且不经常变化的情况,而工厂方法模式和抽象工厂模式则适用于产品种类多且经常变化的复杂场景。在工厂方法模式中,每种产品都有一个对应的工厂;在抽象工厂模式中,则是有一个工厂接口用于创建一系列相关或依赖的对象,而不是单一的产品。简单工厂模式是最简单的形式,工厂方法模式在此基础上增加了更多的抽象和灵活性,而抽象工厂模式是在工厂方法的基础上进一步扩展,用于处理产品族的创建。简单工厂模式最简单,但灵活性较差;工厂方法模式灵活性较高,但实现相对复杂;抽象工厂模式灵活性最高,但实现最为复杂。
抽象工厂模式-创建型模式
如何理解抽象工厂模式
抽象工厂模式创建的是对象家族,也就是很多对象而不是一个对象,并且这些对象是相关的,也就是说这些对象必须一起创建出来。而工厂方法模式只用于创建一个对象,这和抽象工厂模式有很大的不同。抽象工厂模式是围绕一个超级工厂创建其他工厂。该超级工厂又称为其他工厂的工厂。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
一个便于理解的实例:有不同类型的衣柜,每个衣柜(具体工厂)只能存放一类衣服(成套的具体产品),如商务装、时尚装等。每套衣服包括具体的上衣和裤子(具体产品)。所有衣柜都是衣柜类(抽象工厂)的具体实现,所有上衣和裤子分别实现上衣接口和裤子接口(抽象产品)。即衣柜是抽象工厂,放商务装的衣柜和放时尚装的衣柜是具体工厂,每个工厂可以生产多种产品,包含上衣和裤子,是抽象产品,更具体的上衣和裤子是具体产品。
抽象工厂模式的类图
抽象工厂模式的组成
- 抽象工厂:具体工厂的父类;声明一组创建产品对象的方法,每个方法对应一种产品
- 具体工厂:抽象工厂的子类;负责创建具体产品对象的实例
- 抽象产品:具体产品的父类;描述具体产品的公共接口
- 具体产品:抽象产品的子类;描述要生产的具体产品
抽象工厂模式的代码实例
//抽象产品(操作系统相关的UI组件接口) |
抽象工厂模式的优缺点
优点:
- 封装性:将一组相关产品的创建过程封装在一起,客户端无需了解具体产品的创建细节。
- 一致性:提供一个统一的接口来创建相关或依赖的对象,保持产品族内部的一致性。
- 灵活性与扩展性:当需要增加新的产品族时,只需增加一个新的具体工厂和相关产品类即可,不影响已有代码,符合开闭原则。
- 隔离变化:通过抽象工厂隔离了高层模块与具体产品的依赖关系,使得高层模块无需关心产品族内部的变化。
缺点:
- 增加复杂性:对于简单系统,引入抽象工厂可能会增加不必要的复杂性。
- 产品族扩展困难:如果产品族中添加新产品,不仅需要修改抽象工厂和具体工厂,还可能影响到使用抽象工厂的客户端代码,这就违反了开闭原则。
- 类的层级结构复杂:随着产品种类的增加,可能会导致类的层级结构变得复杂。
和其他设计模式的对比
比较抽象工厂模式和工厂方法模式
- 工厂方法模式可以看成是抽象方法模式的一种特例,工厂方法模式是创建一个产品结构的,而抽象工厂模式是用来创建多个产品结构的。工厂方法只有一个产品抽象类,而抽象工厂有多个产品抽象类。工厂方法模式中的工厂具体类只能创建一类产品类对象,而抽象工厂模式的具体工厂可以创建多个产品类的实例。
抽象工厂模式的适用场景
- 产品族内有多组相关产品:当一个系统需要创建一系列相关或依赖的产品对象时,且这些产品之间存在一定的约束或兼容性要求,抽象工厂模式非常适合。
- 需要隔离高层模块与具体产品的依赖:如果希望高层模块不直接依赖具体产品的创建过程,可以通过抽象工厂来封装创建细节。
- 系统需要支持多种产品系列:如果有多种产品系列(如不同操作系统上的图形界面组件),且希望在不修改客户端代码的情况下引入新的产品系列,可以使用抽象工厂模式。
其他补充内容
- 当抽象工厂模式方法中每一个具体工厂只创建一个具体产品,退化为工厂方法模式(抽象工厂:iPhone工厂创建多种iPhone;工厂方法:iPhone工厂只有一种iPhone)
- 抽象工厂模式用到了工厂方法模式来创建对象,AbstractFactory中的createProductA和createProductB方法都是让子类来实现。从高层次来看,抽象工厂使用了组合,即Client组合了AbstractFactory,而工厂方法模式使用了继承。
单例模式-创建型模式
如何理解单例模式
这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这种模式常用于那些需要严格控制全局唯一且共享访问点的场景,如数据库连接池、缓存管理器、日志记录器等。
单例模式的类图
较为简单,并且不重要,此处不再描述
单例模式的组成
较为简单,并且无需理解,此处不再描述
单例模式的代码实例
//懒汉式,线程不安全,即要使用时有一个getInstance方法 |
单例模式的优缺点
优点:
- 资源利用率:对于需要消耗大量资源的单例,懒汉式单例可以在真正需要时才初始化,避免资源浪费。
- 全局唯一:确保整个应用程序中单例类的实例只有一个,有利于资源的协调与共享。
- 控制全局状态:对于需要全局统一控制状态或行为的对象,单例模式可以提供一个集中的访问点。
缺点:
- 灵活性受限:单例模式不利于代码的测试,特别是依赖于单例类的代码。在单元测试中,往往需要模拟不同的单例对象,而单例模式的单一实例特性对此造成了阻碍。
- 扩展困难:如果需要支持多个实例,或者需要实例化的时机有变化,单例模式难以应对。
- 并发问题:懒汉式单例如果不正确地实现线程安全性,可能导致多个实例被创建。在多线程环境下,需要仔细设计和实现以确保单例的正确性。
和其他设计模式的对比
单例模式的适用场景
- 需要频繁实例化然后销毁的对象:如线程池、数据库连接池等,通过单例模式可以减少频繁创建和销毁对象带来的性能开销。
- 需要严格控制全局状态的对象:如日志记录器、缓存、设备驱动等,通过单例模式可以确保整个系统中这些对象的状态一致。
- 需要保证数据共享与一致性:如配置文件读取、应用程序计数器等,通过单例模式可以避免数据的不一致性和重复加载。
其他补充内容
- 与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心实例化方式。
策略模式-行为型模式
如何理解策略模式
在策略模式定义了一系列算法或策略,并将每个算法封装在独立的类中,使得它们可以互相替换。通过使用策略模式,可以在运行时根据需要选择不同的算法,而不需要修改客户端代码。解决在多种相似算法存在时,使用条件语句(如if…else)导致的复杂性和难以维护的问题。
策略模式的类图
关注策略中的具体策略执行方法,以及上下文类中持有策略的特点,和上下文类中设置策略并使用策略的方法。
课件上的案例:鸭子的飞行方式和叫声方式
策略模式的组成
- 环境上下文:维护一个策略对象的引用,负责将客户端请求委派给具体的策略对象执行
- 抽象策略:定义了策略对象的公共接口或抽象类,规定了具体策略类必须实现的方法
- 具体策略:实现了抽象策略定义的接口或抽象类,包含了具体的算法实现
策略模式的代码实例
//抽象策略类 |
策略模式的优缺点
优点:
- 开闭原则:策略模式允许在不修改现有代码的基础上新增策略。
- 多态性:客户端通过策略接口调用方法,无需关注具体实现细节,增强了代码的灵活性和可扩展性。
- 解耦:策略模式将算法从使用它的上下文中解耦出来,便于算法的独立管理和测试。
缺点:
- 策略类数量会随着算法需求的增加而增加。
- 所有策略类都需要对外公开。
- 客户端必须了解不同策略之间的差异,增加了客户端的复杂性。
和其他设计模式的对比
比较策略模式和状态模式
- 策略模式允许在运行时选择算法或行为,状态模式允许对象在内部状态改变时改变它的行为。策略模式是定义一个算法家族,把它们封装起来,使得他们之间可以相互替换。状态模式是将一个个状态封装为一个个类,当内部状态改变时,改变他们的行为。策略模式和状态模式的类图几乎一模一样,策略模式中,客户端知道具体的策略有哪些。状态模式中,客户端不知道内部状态是怎么样变化的,状态模式通过状态转移来组合State对象,最后把行为呈现出来。策略模式关注的是算法的不同实现,客户端可以选择使用不同的策略来达到相同的目标。状态模式关注的是对象在不同状态下的不同行为,对象在不同状态下有不同的行为响应。策略模式各个策略之间通常是独立的,彼此之间没有太多关联。状态模式不同的状态之间通常存在复杂的转换关系,状态之间的切换可能受到一些条件的限制。
比较策略模式和模板方法模式
- 它们的共同点都是封装算法。策略模式是定义一个算法家族,把它们封装起来,使得他们之间可以相互替换。模板方法是针对一个算法流程,某个具体步骤的细节交给子类去实现。策略模式可以改变算法流程,使得一个个步骤可以相互替换,模板方法的算法流程是固定的。策略模式使用组合来实现,模板方法模式使用继承来实现。
策略模式的适用场景
- 系统需要多种算法解决同一问题,且在运行时可以动态切换算法。
- 算法的实现可以相互独立,互不影响。
- 希望避免使用多重条件判断(例如switch-case或if-else)来选择算法。
其他补充内容
- 体现的设计原则:封装变化;针对接口编程;多用组合少用继承。
- 具体策略类中只关注行为不关注属性?错误的,即使是行为也有自己独特的属性,例如飞行行为,也有速度、高度等属性。
状态模式-行为型模式
如何理解状态模式
在状态模式中,类的行为是基于它的状态改变的,这种类型的设计模式属于行为型模式。状态模式允许对象在内部状态改变时改变其行为,使得对象在不同的状态下有不同的行为表现。通过将每个状态封装成独立的类,可以避免使用大量的条件语句来实现状态切换。
状态模式的类图
关注状态中的方法,以及上下文持有状态的特点,还有上下文get、set状态的方法,还有上下文具体的工作方法(不同状态)
课件上的案例:口香糖机器的不同状态
状态模式的组成
- 环境上下文:维护当前状态对象的引用,可以通过状态对象来委托处理状态相关的行为
- 抽象状态:定义了一个接口,用于封装与上下文相关的一个状态的行为
- 具体状态:实现了状态接口,负责处理与该状态相关的行为
状态模式的代码实例
//抽象状态类 |
状态模式的优缺点
优点:
- 结构清晰,将状态相关的代码组织在一起,符合单一职责原则。
- 将状态转换的逻辑封装在状态类中,使得状态转换逻辑与上下文对象分离,易于扩展新的状态和转换逻辑。
- 提高了对象的可扩展性和可维护性。
缺点:
- 增加类和对象数量,每个状态都需要一个具体的状态类。
- 实现复杂,模式结构和实现相对复杂。
- 开闭原则支持不足,增加新状态或修改状态行为可能需要修改现有代码。
和其他设计模式的对比
比较策略模式和状态模式
- 策略模式允许在运行时选择算法或行为,状态模式允许对象在内部状态改变时改变它的行为。策略模式是定义一个算法家族,把它们封装起来,使得他们之间可以相互替换。状态模式是将一个个状态封装为一个个类,当内部状态改变时,改变他们的行为。策略模式和状态模式的类图几乎一模一样,策略模式中,客户端知道具体的策略有哪些。状态模式中,客户端不知道内部状态是怎么样变化的,状态模式通过状态转移来组合State对象,最后把行为呈现出来。策略模式关注的是算法的不同实现,客户端可以选择使用不同的策略来达到相同的目标。状态模式关注的是对象在不同状态下的不同行为,对象在不同状态下有不同的行为响应。策略模式各个策略之间通常是独立的,彼此之间没有太多关联。状态模式不同的状态之间通常存在复杂的转换关系,状态之间的切换可能受到一些条件的限制。
对比策略模式、状态模式和模板方法模式
- 策略模式封装可互换的行为并使用委托来决定使用哪种行为;状态模式封装基于状态的行为并将行为委托给当前状态;模板方法模式是子类自行决定如何在算法中实现步骤。
状态模式的适用场景
- UI控件的状态变化,如按钮的禁用/启用状态。
- 状态机的实现,如ATM机的不同操作状态。
- 工作流系统,如订单处理的不同阶段。
其他补充内容
- 谨慎使用,以避免系统变得过于复杂。
观察者模式-行为型模式
如何理解观察者模式
定义了一种一对多的依赖关系,当一个对象的状态发生改变时,其所有依赖者都会收到通知并自动更新。当对象间存在一对多关系时,则使用观察者模式。比如,当一个对象被修改时,则会自动通知依赖它的对象。
观察者模式的类图
关注主题的增删告知方法,关注观察者的update方法
观察者模式的组成
- 抽象被观察者:维护着一个观察者列表,并提供添加、删除和通知观察者的方法
- 具体被观察者:抽象被观察者的具体实现类
- 抽象观察者:观察者是接收通知的对象,需要拥有一个更新方法
- 具体观察者:具体观察者是观察者的具体实现类,实现了更新方法
观察者模式的代码实例
//抽象被观察者,即主题 |
观察者模式的优缺点
优点:
- 松耦合:观察者模式实现了主题(被观察者)和观察者之间的松耦合。主题不需要知道观察者的具体类型,只需要通过一个通用的接口进行通知,这有助于降低模块间的耦合度,提高系统的灵活性和可维护性。
- 扩展性好:增加新的观察者或删除已有的观察者都很方便,只需修改具体的观察者类,对主题和其他观察者没有影响。
- 响应式编程友好:观察者模式可以很容易地构建基于事件的系统,使得系统具备事件驱动能力,能够及时响应状态变化。
缺点:
- 如果观察者过多,每次主题状态变化时都需要通知所有的观察者,可能会造成效率低下,尤其是在大型系统中,通知机制可能成为瓶颈。
- 如果观察者和主题之间存在循环依赖,可能导致系统难以理解和调试。
- 如果没有良好的设计,可能会出现观察者忘记取消注册而导致内存泄露的情况。
- 当主题类的状态频繁变化时,会导致大量无谓的通知发送,加大系统的负载。
- 对于观察者来说,可能会因为不清楚主题的状态何时发生变化,而过度依赖于外部通知,不利于逻辑的清晰性和独立性。
和其他设计模式的对比
观察者模式的适用场景
- 在GUI编程中,按钮点击事件通知多个组件更新界面。
- 数据绑定场景,数据模型变化时自动更新视图层。
- 网络通信,服务器状态改变时通知所有订阅的客户端。
- 日志框架中,日志级别变更时重新配置所有相关的日志处理器。
其他补充内容
- 在观察者模式中,update方法并不是必须的,因为具体的观察者类可以根据需要选择实现这个方法或不实现。观察者模式的核心思想是定义对象间的一对多的依赖关系,当一个对象(被观察者)的状态发生变化时,所有依赖于它的对象(观察者)都得到通知并被自动更新。在典型的观察者模式中,通常有两种方式来通知观察者:一个是推模型即主题对象推送详细信息给观察者,观察者在接收到通知后,调用相应的更新方法进行处理。另一个是拉模型即主题对象只通知观察者发生了变化,观察者根据需要自行从主题对象中获取详细信息。如果使用推模型,那么观察者类通常需要实现update方法,因为主题对象需要调用这个方法来传递详细信息。但如果使用拉模型,观察者类可以选择性地实现update方法,因为观察者可以通过其他方式获取信息。
模板模式-行为型模式
如何理解模板模式
在模板模式中,一个抽象类公开定义了执行它的方法的方式/模板。它的子类可以按需要重写方法实现,但调用将以抽象类中定义的方式进行。简单来说,就是在父类中定义了算法的骨架,并允许子类在不改变算法结构的前提下重定义算法的某些特定步骤。
模板模式的类图
只需关注模板方法,最多再关注一下模板中“不变”的那些方法
模板模式的组成
- 抽象类:定义了模板方法和一些抽象方法或具体方法
- 具体类:继承自抽象父类,并实现抽象方法
模板模式的代码实例
//抽象类,以倒咖啡和冲茶为例,烧水和倒进杯子就是“不变”的那些方法 |
模板模式的优缺点
优点:
- 提高代码复用性:通过抽象模板方法,封装了算法的通用部分,使得代码更易于维护和扩展。
- 灵活性:允许子类根据需要重新定义或扩展算法中的特定步骤,从而实现定制化。
- 便于管理:通过模板方法对算法进行统一管理和调度,提高了代码的结构化程度。
缺点
- 可能导致代码膨胀:如果模板方法中的步骤过多或过于复杂,会增加子类的实现难度,导致代码膨胀。
- 限制灵活性:模板方法模式可能限制了子类的灵活性,特定步骤的实现必须符合父类定义的算法框架。
和其他设计模式的对比
比较策略模式和模板方法模式
- 它们的共同点都是封装算法。策略模式是定义一个算法家族,把它们封装起来,使得他们之间可以相互替换。模板方法是针对一个算法流程,某个具体步骤的细节交给子类去实现。策略模式可以改变算法流程,使得一个个步骤可以相互替换,模板方法的算法流程是固定的。策略模式使用组合来实现,模板方法模式使用继承来实现。
比较模板方法模式和工厂模式
- 工厂模式是模板方法的特殊实现,工厂里面的创建对象,就相当于只有一个步骤的模板方法模式,这个步骤交给子类去实现。
模板模式的适用场景
- 当需要实现一组算法中的共同部分,并将可变部分延迟到子类中时。
- 当存在多个具有相似算法结构但实现细节不同的情况时。
- 当希望通过一个固定的算法框架来管理和调度算法时。
其他补充内容
命令模式-行为型模式
如何理解命令模式
将请求封装为一个对象,使得请求的发送者和接收者之间解耦。命令对象可以携带参数,支持撤销操作,并且可以被存储、记录、序列化、排队、日志等,从而为系统提供更大的灵活性。
命令模式的类图
invoker持有command,并且有set方法,也在内部调了command的执行方法,而这个执行方法又调用了receiver的执行方法
命令模式的组成
- 命令接口:定义执行命令的公共接口,通常包含一个执行方法
- 具体命令:实现命令接口,与接收者关联,并封装了需要执行的操作
- 接收者:定义了具体执行操作的对象,包含一组可以被具体命令调用的方法
- 调用者:负责调用命令对象的execute()方法
命令模式的代码实例
//命令接口 |
命令模式的优缺点
优点:
- 降低耦合度,请求者和执行者之间的耦合度降低。
- 易于扩展,新命令可以很容易地添加到系统中。
缺点:
- 系统可能会有过多的具体命令类,增加系统的复杂度。
- 如果命令需要携带大量参数,可能会导致命令类的构造函数变得复杂。
和其他设计模式的对比
命令模式的适用场景
- 需要将请求、操作封装为对象,以便进行存储、传递、调用、撤销等操作。
- 需要支持事务操作,即一组操作必须全部成功或全部失败。
- 需要将请求的发送者与接收者解耦,使得二者独立变化。
其他补充内容
- 命令模式的宏命令一种特殊的命令,它可以执行一系列的命令。简单来说,宏命令是命令的集合,当执行宏命令时,它内部的所有命令依序执行。在命令模式中,通常会有一个命令接口,定义了执行命令的方法。然后,具体的命令类实现这个接口。对于宏命令,可以创建一个特殊的命令类,它也实现了命令接口,但它内部维护了一个命令列表,并在执行时依次执行这些命令。
迭代器模式-行为型模式
如何理解迭代器模式
迭代器模式提供一种方法顺序访问一个聚合对象中的各个元素,而又不暴露其内部的表示。
迭代器模式的类图
关注迭代器的next和hasNext方法,关注聚合对象的创建迭代器方法
迭代器模式的组成
- 迭代器接口:定义了访问和遍历聚合对象中各个元素的方法
- 具体迭代器:实现了迭代器接口,负责对聚合对象进行遍历和访问
- 抽象聚合对象:定义创建迭代器的接口,通常包括一个工厂方法用于创建迭代器对象
- 具体聚合对象:实现了聚合对象接口,负责创建具体的迭代器对象
迭代器模式的代码实例
//迭代器接口 |
迭代器模式的优缺点
优点:
- 封装性好,隐藏了集合内部表示,简化了客户端代码。
- 提供了统一的遍历各种聚合结构的方法,易于扩展和替换不同的数据结构。
- 支持多种遍历方式,如正向、反向遍历等。
缺点:
- 对于不同的聚合结构,可能需要编写不同的迭代器,增加了类的数量。
- 迭代器模式只提供了一种线性访问方式,复杂的随机访问可能需要额外的设计。
- 如果迭代器本身比较复杂,可能会增加系统的理解难度和维护成本。
和其他设计模式的对比
迭代器模式的适用场景
- 当需要遍历集合、数组或其他容器中的元素时。
- 当希望提供多种遍历方式(顺序、条件等)时。
- 在不希望暴露集合内部结构或实现细节的情况下,对外提供一致的访问接口。
其他补充内容
装饰器模式-结构型模式
如何理解装饰器模式
允许向一个现有的对象添加新的功能,同时又不改变其结构。装饰器模式通过将对象包装在装饰器类中,以便动态地修改其行为。这种模式创建了一个装饰类,用来包装原有的类,并在保持类方法签名完整性的前提下,提供了额外的功能。
装饰器模式的类图
关注抽象组件的基本方法,以及具体装饰器的独特方法,并且装饰器持有抽象组件的引用,而非具体组件
装饰器模式的组成
- 组件接口:所有具体组件和装饰器都要实现这个接口
- 具体组件:实现了组件接口,是需要被装饰的基础对象
- 装饰器:实现组件接口,并持有一个组件接口类型的引用,用于存储被装饰的对象
- 具体装饰器:继承自装饰器类,为具体组件添加新的职责或修改其行为
装饰器模式的代码实例
//组件接口 |
装饰器模式的优缺点
优点:
- 装饰器模式遵循开闭原则:允许在不修改原有类的情况下,动态地为对象添加新功能,增强了系统的可扩展性。
- 灵活性:即可以使用多个装饰器对同一个对象进行多次装饰,从而实现不同组合的增强功能。
- 透明性:即装饰后的对象与未装饰的对象在对外接口上保持一致,客户端无需关心对象是否被装饰以及如何装饰,只需面向组件接口编程;提供了一种继承之外的扩展对象功能的方式。
缺点:
- 复杂性:如果过度使用装饰器模式,可能会导致类的层次过深,增加系统的复杂性。
- 不易于理解:对于不熟悉装饰器模式的开发人员来说,可能需要花费更多时间理解装饰器的实现逻辑。
和其他设计模式的对比
装饰器模式的适用场景
- 需要为对象动态添加新功能,且新功能与原有功能可独立变化:装饰器模式可以在运行时为对象添加新功能,新功能与原有功能通过装饰器类进行封装,二者可以独立变化。
- 需要保持类的开放封闭原则,避免修改已有代码:装饰器模式可以在不修改原有类的情况下,通过创建装饰器类为对象添加新功能,符合开放封闭原则。
- 需要为对象提供多种可选的附加功能,且这些功能可以自由组合:通过使用多个装饰器对同一对象进行装饰,可以实现不同组合的增强功能。
其他补充内容
适配器模式-结构型模式
如何理解适配器模式
它允许将一个接口转换为客户期望的另一个接口,从而使原本不兼容的类能够协同工作。适配器模式的主要目的是解决接口不匹配的问题,它通过创建一个适配器类,将源接口转换为目标接口,使得原本无法直接交互的类能够通过适配器进行通信。
适配器模式的类图
关注目标接口的方法,关注适配器重写的方法,关注源接口的方法
适配器模式的组成
- 目标接口:定义客户端期望的接口,可以是一个接口或抽象类
- 源接口:需要被适配的接口,通常是第三方提供的接口或者遗留系统中的接口
- 适配器:适配器类实现了目标接口,并持有源接口的一个实例
适配器模式的代码实例
//目标接口 |
适配器模式的优缺点
优点:
- 复用现存类:适配器模式允许在不修改现有代码的情况下,将一个类的接口转换为另一个接口,使得现有类可以被复用。
- 兼容性:通过适配器模式,可以使原本不兼容的接口之间能够协同工作,提高了系统的兼容性。
- 灵活性:对象适配器模式可以将一个接口转换成多个接口,而类适配器模式由于Java的单继承限制,只能一对一地适配。
缺点:
- 如果系统中存在大量的适配器类,可能会增加系统的复杂性,不易管理。
- 在使用类适配器时,可能会增加类的数量,特别是当系统中存在大量需要适配的类时。
和其他设计模式的对比
比较适配器模式、外观模式、装饰者模式的意图
- 适配器模式的目的是将一个类的接口转换成客户端期望的另一个接口。适配器让原本接口不兼容的类可以一起工作。当你希望使用某个类,但其接口与其他代码不兼容时,可以使用适配器模式。这在整合旧系统或使用第三方库时尤为常见。外观模式提供了一个统一的接口,用来访问子系统中的一群接口。外观定义了一个高层接口,使得子系统更容易使用。当有一个复杂的子系统,并想为子系统中的一组接口提供一个简单的接口时,可以使用外观模式。它常用于简化客户端和复杂系统之间的交互。装饰器模式动态地给一个对象添加一些额外的职责。就增加功能来说,装饰器模式比生成子类更为灵活。当希望在不改变对象接口的前提下,增强或修改对象的行为时,可以使用装饰器模式。这种模式在动态地添加功能以及在运行时选择功能方面非常有效。总结来说,适配器模式主要用于使现有接口适应新的接口,外观模式用于简化复杂接口,而装饰器模式则用于在不改变接口的情况下增加对象的功能。
适配器模式的适用场景
- 需要使用一个已经存在的类,但它的接口不符合需求:可以通过适配器模式创建一个适配器类,将已有类的接口转换为期望的接口。
- 想要创建一个可以复用的类,该类可以与其他不相关的类或不可预见的类协同工作:适配器模式可以将目标接口与源接口解耦,使得适配器类可以适应更多的源接口。
- 需要使用几个具有类似功能但接口不兼容的类:通过适配器模式可以创建统一的接口,简化客户端代码。
其他补充内容
外观模式-结构型模式
如何理解外观模式
目的是为子系统提供一个统一的、更高级别的接口,使得客户端能够更容易地与子系统进行交互,而不必了解子系统的内部细节。外观模式通过创建一个外观类(Facade),封装了子系统中的复杂接口或多个子系统接口,提供一个更简洁、更易于使用的接口给客户端,从而降低了客户端与子系统之间的耦合度。
一个便于理解的实例:比较自己泡茶和去茶馆喝茶的区别,如果是自己泡茶需要自行准备茶叶、茶具和开水。而去茶馆喝茶,最简单的方式就是跟茶馆服务员说想要一杯什么样的茶,是铁观音、碧螺春还是西湖龙井?正因为茶馆有服务员,顾客无须直接和茶叶、茶具、开水等交互,整个泡茶过程由服务员来完成,顾客只需与服务员交互即可,整个过程非常简单省事。
外观模式的类图
子系统并不知道外观的存在,它们独立于外观进行开发和演化
外观模式的组成
- 子系统:由多个类组成的模块或系统,每个类负责子系统的一部分功能
- 外观:子系统的统一接口,外观将客户端的请求转发给适当的子系统对象处理
外观模式的代码实例
//子系统类 |
外观模式的优缺点
优点:
- 简化接口:为子系统提供了一个简化的接口,降低了客户端与子系统的耦合度。
- 减少复杂性:隐藏了子系统的复杂性,使得客户端无需了解子系统的内部结构和实现细节。
- 易于使用和扩展:客户端只需与外观交互,简化了系统的使用,同时新增或修改子系统时,只需调整外观类,不影响客户端代码。
缺点:
- 违反开闭原则:若新增子系统功能,可能需要修改外观类,这在一定程度上违反了“开闭原则”。
- 可能增加系统的复杂性:若设计不当,外观类可能会过于庞大,成为另一个难以维护的中心点。
和其他设计模式的对比
外观模式的适用场景
- 子系统接口复杂,不易理解和使用:当子系统包含多个接口,或者接口方法众多且使用复杂时,可以使用外观模式提供一个简单易用的接口。
- 需要简化并统一子系统接口:在不同子系统之间存在大量交互,或者客户端需要与多个子系统交互时,可以通过外观模式提供一个统一的接口。
- 希望减少客户端与子系统之间的耦合:通过外观模式隔离客户端与子系统的直接联系,使得两者可以独立演化。
其他补充内容
组合模式-结构型模式
如何理解组合模式
允许将对象组合成树形结构来表示“部分-整体”的层次结构。这种模式使得客户端可以以一致的方式处理单个对象(叶子节点)和组合对象(容器节点),无需关心处理的是个体还是群体。组合模式使得你可以将对象看作是树形结构中的节点,节点可以是简单的对象,也可以是包含其他节点的复合对象,这样就能形成一个层次化的结构。
组合模式的类图
关注叶子与容器组件中不同的方法
组合模式的组成
- 抽象组件:共享的公共接口,规定了如何访问和管理对象的子部件
- 叶子组件:是组合结构的终端节点,不包含任何子组件
- 容器组件:包含一个或多个子组件,每个子组件也是Component的实例
组合模式的代码实例
//抽象组件 |
组合模式的优缺点
优点:
- 单一职责原则:组合模式使得叶子节点和容器节点都遵循单一职责原则,各自专注于自己的功能。
- 透明性:客户端可以一致地处理单个对象和组合对象,无需知道处理的是叶子还是容器,提高了代码的透明性和简洁性。
- 易于扩展:新类型的组件只需继承Component或实现相关接口即可加入到组合结构中,不影响已有代码。
缺点:
- 设计复杂度增加:为了实现组合模式,需要设计额外的抽象层和接口,使得系统变得相对复杂。
- 递归操作可能导致性能问题:如果组合结构非常深,递归操作可能会导致栈溢出或效率下降。
和其他设计模式的对比
组合模式的适用场景
- 系统需要处理对象的“部分-整体”关系:当需要表示对象的层级结构时,组合模式可以很好地表示这种关系。
- 希望客户端以一致的方式处理单个对象和组合对象:组合模式使得客户端无需关心处理对象的具体类型,简化了客户端代码。
- 希望简化新组件类型的添加:新的叶子节点或容器节点只需要符合Component接口即可轻松融入现有系统。
其他补充内容
代理模式-结构型模式
如何理解代理模式
在代理模式中,一个类代表另一个类的功能,这种类型的设计模式属于结构型模式。代理模式通过引入一个代理对象来控制对原对象的访问。代理对象在客户端和目标对象之间充当中介,负责将客户端的请求转发给目标对象,同时可以在转发请求前后进行额外的处理。
代理模式的类图
代理模式的组成
- 抽象主题:真实主题和代理的共同接口,使得可用真实主题的地方都可用代理主题
- 真实主题:实现了抽象主题接口,是代理对象所代表的真实对象
- 代理:实现了抽象主题接口,并持有对真实主题的引用
代理模式的代码实例
//抽象主题(接口) |
代理模式的优缺点
优点:
- 职责清晰:代理对象与真实主题对象职责明确,代理对象负责控制访问、增强功能等,真实主题对象专注于业务逻辑。
- 扩展性好:通过代理模式可以方便地添加新的功能,如访问控制、日志记录等,而无需修改真实主题。
- 保护真实主题:通过代理对象可以隐藏真实主题的实现细节,提供额外的安全性和控制。
缺点:
- 增加复杂性:引入代理对象会使系统变得更复杂,增加了额外的类和对象关系。
- 性能损耗:代理模式可能会带来一定的性能损耗,尤其是在代理对象执行额外操作的情况下。
和其他设计模式的对比
代理模式的适用场景
- 需要为对象增加额外功能:如添加日志、事务管理、访问控制等,而又不想修改对象本身。
- 需要对对象的访问进行控制:如远程访问、延迟加载、资源优化等。
- 需要隐藏对象的复杂性:通过代理对象对外提供简洁的接口,隐藏真实主题的复杂实现。