原创

Java设计模式


Java设计模式

设计模式综述

设计面向对象软件比较困难, 而设计可复用的面向对象软件更加困难. 必须找到相关的对象, 以适当的粒度将它们归类, 再定义类的接口和继承层次, 建立对象直接的基本关系. 设计应该对当前的问题有针对性, 同时对将来的问题和需求也要有足够的通用性, 避免重复设计或尽可能少做重复设计.

设计模式是一套被反复使用的, 多数人知晓的, 经过分类编目的代码设计经验的总结, 使用设计模式为了可重用代码,让代码更易于理解且提高代码的可靠性.

设计模式是对被用来在特定场景下解决一般设计问题的类和相互通信的对象的描述.

基本要素

模式名称(Pattern Name)

模式名称通过一两个词来描述模式的问题, 解决方案和效果, 方便理解模式并方便开发人员之间的交流.

问题(Problem)

问题描述了应该在何时使用模式,它包含了设计中存在的问题以及问题存在的原因,有时问题部分会包括使用模式必须满足的一系列先决条件.

解决方案(Solution)

解决方案描述了设计的组成部分, 以及这些组成成分之间的相关的关系和协作方式. 模式提供设计问题的抽象描述和怎样用一个具有一般意义的元素组合来解决问题.

效果(Consequences)

效果描述了模式应用的效果以及在使用模式时应权衡的问题.模式效果包括它对系统的灵活性, 扩展性或可移植性的影响,显式地列出这些效果对理解和评价模式很有帮助.

模式分类

范围/目的创建型结构型行为型
类模式工厂方法模式类适配器模式解释器模式
模板方法模式
对象模式抽象工厂模式
建造者模式
原型模式
单例模式
对象适配器模式
桥接模式
组合模式
装饰模式
外观模式
享元模式
代理模式
职责链模式
命令模式
迭代器模式
中介者模式
备忘录模式
观察者模式
状态模式
策略模式
访问者模式

模式简介

类别名称说明
创建型抽象工厂模式(Abstract Factory)提供一个创建一系列相关或相互依赖对象的接口, 而无需指定它们具体的类.
创建型建造者模式(Builder)将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示.
创建型工厂方法模式(Factory Method)定义一个用于创建对象的接口,但是让子类决定将哪一个类实例化.让一个类的实例化延迟到其子类.
创建型原型模式(Prototype)用原型实例指定创建对象的种类,并且通过拷贝这个原型来创建新的对象.
创建型单例模式(Singleton)确保一个类只有一个实例,并提供一个访问它的全局访问点.
结构型适配器模式(Adapter)将一个类的接口转换成客户端希望的另一个接口,适配器模式让那些接口不兼容的类可以一起工作.
结构型桥接模式(Bridge)将抽象部分与它的实现部分解耦,使得两者都能够独立变化.
结构型组合模式(Composite)将对象组合成树形结构以表示部分-整体的层次结构,组合模式让客户端统一对待单个对象和组合对象.
结构型装饰模式(Decorator)动态地给对象增加一些额外的职责,更加灵活地扩展原对象的功能.
结构型外观模式(Facade)为子系统的一组接口提供一个统一的入口.外观模式定义了一个高层接口,这个接口使得这一子系统更加容易使用.
结构型享元模式(Flyweight)运用共享技术有效地支持大量细粒度对象的复用.
结构型代理模式(Proxy)给对象提供一个代理或占位符, 并由代理对象来控制对原对象的访问.
行为型职责链模式(Chain Of Responsibility)为解除请求的发送者和接收者之间的耦合,而使多个对象都有机会处理请求.将接收请求的对象连成一条链,并且沿着这条链传递请求,直到有一个对象能处理请求为止.
行为型命令模式(Command)将一个请求封装为一个对象,从而可用不同的请求对客户端进行参数化,对请求排队或者记录请求日志,以及支持可撤销的操作.
行为型解释器模式(Interpreter)给定一个语言,定义它的文法的一种表示,并定义一个解释器,解释器使用该文法表示来解释语言中的句子.
行为型迭代器模式(Iterator)提供一种方法顺序访问一个聚合对象中的各个元素,而又不用暴露该对象的内部表示.
行为型中介者模式(Mediator)定义一个对象来封装一系列对象的交换.中介者使各对象之间不需要显式地互相引用,从而使其耦合松散,而且可以独立地改变它们之间的交互.
行为型备忘录模式(Memento)在不破坏封装的前提下捕获一个对象的内部状态,并在对象之外保存这个状态.这样以后可将对象恢复到原先保存的状态.
行为型观察者模式(Observer)定义对象间的一种一对多的依赖关系,以便当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并自动刷新.
行为型状态模式(State)允许一个对象在其内部状态改变时改变它的行为,对象看起来似乎修改了它所属的类.
行为型策略模式(Strategy)定义一系列的算法,并封装起来,并让它们可以互相替换.策略模式让算法的变化独立于使用它的客户.
行为型模板方法模式(Template Method)定义一个操作中算法的框架,而将一些步骤延迟到子类中,模板方法模式使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤.
行为型访问者模式(Visitor)表示一个作用于某对象结构中各个元素的操作.访问者模式可以在不改变各元素类的前提下定义作用于这些元素的新操作.

模式优点

  1. 设计模式融合了专家的设计经验,提供一套软件设计的通用词汇以方便开发人员交流,降低开发人员理解系统的复杂度.
  2. 设计模式让人们更加简单方便地复用成功的设计和体系结构,并避免导致不可重用的设计方案.
  3. 设计模式使得设计方案更加灵活,且易于修改.
  4. 设计模式的使用将提高软件系统的开发效率和软件质量,并且在一定程度上节约设计成本.
  5. 设计模式有助于初学者更深入地理解面向对象思想.

解决设计问题

寻找合适的对象

面向对象设计最困难的部分是将系统分解成对象集合,要考虑许多因素: 封装, 粒度, 依赖关系, 灵活性, 性能, 演化, 复用等等, 这些因素都影响着系统的分解,并且这些因素通常还是相互冲突的.

面向对象设计方法: (可参考书: 领域驱动设计)

  1. 可以写出一个问题描述,挑出名词和动词, 进而创建相应的类和操作.
  2. 可以关注于系统的协作和职责关系.
  3. 可以对现实世界建模,再将分析时发现的问题转化至设计中.

设计的许多对象来源于现实世界的分析模型,但设计结果所得到的类通常在现实世界中并不存在(如数组之类的底层类).组合模式引入了统一对待现实世界中并不存在的对象的抽象方法.设计中的抽象对于产生灵活的设计是至关重要的.

设计模式帮你确定并不明显的抽象和描述这些抽象的对象, 比如描述过程或者算法的对象现实不存在,但却是设计的关键部分.策略模式描述了怎样实现可互换的算法族.状态模式将实体的每个状态都描述为一个对象.这些对象在分析阶段,甚至设计阶段的早期不存在,后来为使设计更灵活,复用性更好才将它们发掘处理.

决定对象的粒度

设计模式很好地解决了在大小和数目上变化极大且能表示任何事物的对象应该是什么的问题.外观模式描述了怎么用对象表示完整的子系统.享元模式描述了如何支持大量的最小粒度的对象.抽象工厂模式和构建者模式产生那些专门负责生成其他对象的对象.访问者模式和命令模式生成的对象专门负责实现对其他对象或对象组的请求.

指定对象的接口

设计模式通过确定接口的主要组成部分以及接口发送的数据类型,来帮助你定义接口, 还会告诉你接口中不应包括哪些东西.备忘录模式是一个很好的例子,它描述了怎样封装和保存对象内部状态,以便一段时间后对象能恢复到此状态.模式规定了备忘录对象必须定义两个接口: 一个允许客户端保持和复制备忘录的限制接口, 和一个只有原对象才能使用的用来存储和提取备忘录中状态的特权接口.

设计模式指定了接口之间的关系,特别地,模式经常要求一些类具有相似的接口,或者对一些类的接口做了限制.装饰者模式和代理模式要求装饰者和代理对象的接口与被修饰的对象和受委托的对象一致.而访问者模式中,访问者接口必须反映出访问者能访问的对象的所有类.

描述对象的实现

类继承根据一个对象的实现定义了另一个对象的实现,它是代码和表示的共享机制.

接口继承描述了一个对象什么时候能被用来替代另一个对象.

很多设计模式依赖上述的差别.责任链模式中的对象必须有一个公共的类型,但一般情况下它们不具有公共的实现.组合模式中,构件定义了一个公共的接口,但组合通常定义一个公共的实现.命令模式,观察者模式,状态模式,策略模式通常纯粹作为接口的抽象类来实现.

对接口编程而不是对实现编程.只根据抽象类中定义的接口来操作对象有两个好处: 1)客户端无须知道他们使用对象的特定类型,只须对象有客户端所期望的接口.2)客户端无须知道他们使用的对象是用什么类来实现的,只须知道定义接口的抽象类. 这将极大地减少子系统实现之间的依赖关系.

不将变量声明为某个特定的具体类的实例对象,而是让它遵从抽象类所定义的接口.如果你不得不在系统的某个地方实例化具体的类(指定一个特定的实现),创建型模式可以帮助你.通过抽象对象的创建过程,这些模式提供不同方式以在实例化时建立接口和实现的透明连接.创建型模式确保你的系统是采用针对接口的方式书写的.

运用复用机制

  1. 继承和组合的比较

    通过生成子类的复用通常称为白箱复用(white-box reuse)---因父类的内部细节对子类可见.

    通过对象组合(要求被组合的对象有良好定义的接口)的复用通常称为黑箱复用(black-box reuse)--因为对象的内部细节不可见.

    继承在编译时刻静态定义的,可直接使用,程序设计语言支持类解除.但继承对子类揭示了父类的实现细节,被任务破坏了封装性,父类实现中任何变化都会导致子类发生变化.继承限制了灵活性并最终限制了复用性.一个可用的解决方法是只继承抽象类,因为抽象类通常提供较少的实现.

    对象组合是通过获得对象的引用而在运行时刻动态定义的.组合要求对象遵守彼此的接口约定.对象组合中对象只能通过接口访问,并不破坏封装性,只要类型一致,运行时刻还可以用一个对象来替代另一个对象,且依赖关系较少,松耦合.

    对象组合有助于保持每个类被封装,并被集中在单个任务上,这样类和类继承层次会保持较小规模.基于对象组合的设计会有更多的对象(有较少的类),且系统的行为依赖于对象间的关系而不是被定义在某个类中.

    优先使用对象组合,而不是类继承.

  2. 委托(delegation)

    委托是对象组合的特例,委托告诉你对象组合作为一个代码复用机制可以替代继承.

    在委托方式下, 有两个对象参与处理一个请求,接受请求的对象将操作委托给它的代理者(请求转发).

    委托的优点在于便于运行时刻组合对象操作以及改变这些操作的组合方式.委托有运行低效的问题(经过一层请求转发),只有当委托使设计比较简单而不是更复杂时,才是好的选择.

    状态模式中一个对象将请求委托给一个描述当前状态的状态对象来处理.在策略模式中一个对象将一个特定的请求委托给一个描述请求执行策略的对象,一个对象只会有一个状态,但它对不同的请求可以有许多策略.这两个模式的目的都是通过改变受托对象来改变委托对象的行为.在访问者模式中,对象结构中的每个元素上的操作总是被委托到访问者对象.

    中介者模式引进了一个中介对象,中介对象只是简单的将请求转发给其他对象,有时中介对象沿着指向自己的引用来传递请求,使用真正意义的委托.职责链模式通过将请求沿着对象链传递来处理请求,有时,这个请求本身带有一个接受请求对象的引用,这时职责链模式就使用了委托.桥接模式将实现和抽象分离,如果抽象和一个特定实现非常匹配,那么这个实现可以代理抽象的操作.

  3. 参数化类型

    参数化类型提供了除类继承和对象组合外的第三种方法来组合面向对象系统中的行为.

    对象组合技术允许你在运行时刻改变被组合的行为,但存在间接性,比较低效.继承允许你提供操作的缺省实现,并通过子类重定义这些操作.参数化类型允许你改变类所用到的类型.继承和参数化类型都不能在运行时刻改变.

关联运行时刻和编译时刻的结构

一个面向对象程序运行时刻的结构通常与它的代码结构相差较大.代码结构在编译时刻被确定下来,它由继承关系固定的类组成.而程序的运行时刻结构是由快速变化的通信对象网络组成.两个结构彼此独立.

考虑对象聚合和相识的差别以及它们在编译和运行时刻的表示不同:

聚合(aggregation)意味着一个对象包含另一个对象或者是另一个对象的一部分,聚合对象和其所有者具有相同的生命周期.

相识(acquaintance)意味着一个对象仅仅知道另一个对象,相识的对象可能请求彼此的操作,但是不为对方负责.相识是一种比聚合要弱的关系,只标识了对象间较松散的耦合关系.

聚合和相识容易混肴,都是以指针或引用实现. 但从根本上讲, 是聚合还是相识是由你的意图而不是语言机制决定的.聚合关系使用较少且比相识关系更持久.相识关系出现频率较高,有时只存在于一个操作期间,相识也更具动态性.

组合模式和装饰者模式对于构造复杂的运行时刻结构特别有用.观察者模式也和运行时刻有关,但这些结构对于不了解模式的人来说很难理解.职责链模式也产生了继承无法展示的通信模式.只有理解了模式,才能清楚代码中的运行时刻结构.

设计应支持变化

为了设计适应需求变化,且具有健壮性的系统, 必须考虑系统在它的生命周期内会发生怎样的变化.

设计模式可以确保系统能以特定方式变化,从而帮助你避免重新设计系统.每一个设计模式允许系统结构的某个方面的变化独立于其他方面,这样产生的系统对于某一种特殊变化将更健壮.

导致重新设计的一般原因和解决这些问题的设计模式:

  1. 通过显式地指定一个类来创建对象: 在创建对象时指定类名将使你受特定实现的约束而不是接口的约束.这会使未来的变化更复杂,要避免这种情况,应该间接地创建对象.

    设计模式: 抽象工厂模式, 工厂方法模式, 原型模式

  2. 对特殊操作的依赖: 当你为请求指定一个特殊的操作时, 完成该请求的方式就固定下来了.为避免把请求代码写死,可以在编译时刻或运行时刻很方便地改变响应请求的方法的模式:

    设计模式: 职责链模式, 命令模式

  3. 对硬件和软件平台的依赖: 外部的操作系统接口和应用编程接口(API)在不同的软硬件平台上是不同的.依赖于特定的平台的软件将很难移植到其他平台上,甚至都很难跟上本地平台的更新,设计时应限制平台相关性.

    设计模式: 抽象工厂模式, 桥接模式

  4. 对对象表示或实现的依赖: 知道对象怎样表示,保存,定位或实现的客户端在对象发生变化时可能也需要变化,对客户隐藏这些信息能阻止连锁变化.

    设计模式: 抽象工厂模式. 桥接模式, 备忘录模式, 代理模式

  5. 算法依赖: 算法在开发和复用时常常被扩展,优化和替代.依赖于某个特定算法的对象在算法变化时不得不变化,把有可能发生变化的算法应该被孤立起来.

    设计模式: 构建者模式, 迭代器模式, 策略模式, 模板方法模式, 访问者模式

  6. 紧耦合: 紧耦合的类很难独立地被复用,很难移植和维护.

    设计模式: 抽象工厂模式, 命令模式, 外观模式, 中介者模式, 观察者模式, 职责链模式

  7. 通过生成子类来扩充功能: 定义新子类要对父类有深入的了解,可能会导致类爆炸.简单的扩充会引入新的子类.

    设计模式: 桥接模式, 职责链模式, 组合模式, 装饰器模式, 观察者模式, 策略模式

  8. 不能方便的对类进行修改: 有时不得不改变一个难以修改的类, 可能需要源代码缺没有, 或者可能对类的任何改变会要求修改许多已存在的其他子类.

    设计模式: 适配器模式, 装饰者模式, 访问者模式

设计模式支持的设计可变方面

目的设计模式可变的方面
创建抽象工厂模式产品对象家族
创建创建者模式如何创建一个组合或复杂对象
创建工厂方法模式被实例化的子类
创建原型模式被实例化的类
创建单例模式一个类的唯一实例
结构适配器模式对象的接口
结构桥接模式对象的实现
结构组合模式一个对象的结构和组成
结构装饰器模式对象的职责,不生成子类
结构外观模式一个子系统的接口
结构享元模式对象的存储开销
结构代理模式如何访问一个对象;对象的位置
行为职责链模式满足一个请求的对象
行为命令模式何时,怎样满足一个请求
行为解释器模式一个语言的文法及解释
行为迭代器模式如何遍历,访问一个聚合对象的各元素
行为中介者模式对象间怎么交互, 和谁交互
行为备忘录模式一个对象中哪些私有信息存放在对象之外,以及在什么时候进行存储
行为观察者模式多个对象依赖于另外一个对象,而这些对象如何保持一致
行为状态模式对象的状态
行为策略模式算法
行为模板方法模式算法中的某些步骤
行为访问者模式某些可作用于一个或一组对象上的操作,但不修改这些对象的类

使用设计模式

使用设计模式的循序渐进的方法:

  1. 大致浏览一遍模式, 特别注意其适用性部分和效果部分,确定它适合你的问题.
  2. 回头研究结构部分, 参与者部分和协作部分, 确保理解这个模式的类和对象以及它们如何关联的.
  3. 看代码示例部分,看看这个模式代码形式的具体例子, 研究代码有助于你实现模式.
  4. 选择模式参与者的名词,使它们在应用上下文中有意义
  5. 定义类,声明它的接口,建立它们的继承关系,定义代码数据和对象引用的实例变量,识别模式会影响到你的应用中存在的类,做出相应的修改.
  6. 定义模式中专用于应用的操作名称,使用与每一个操作相关联的责任和协作为指导,名字约定要一致.
  7. 实现执行模式中责任和协作的操作,实现部分提供线索指导你进行实现.

面向对象设计原则

软件的可维护性和可复用性是两个非常重要的用于衡量软件质量的属性. 软件的可维护性是指软件能够被理解,改正,适应以及扩展的难易程度,软件的可复用性是指软件能够被重复使用的难易程度.

面向对象设计的目标之一在于支持可维护性复用, 一方面需要实现设计方案或者源代码的复用, 另一方面要确保系统能够易于扩展和修改,具有良好的可维护性.面向对象设计原则为支持可维护性复用而生,但非强制性的.

常见的面向对象设计原则:

原则名称定义
单一职责原则一个对象应该只包含单一的职责,并且该职责被完整地封装在一个类中.
开闭原则软件实体应当对扩展开放,对修改关闭.
里氏代换原则所有引用基类的地方必须能透明地使用其子类地对象.
依赖倒转原则高层模块不应该依赖底层模块,都应该依赖抽象.抽象不应该依赖于细节,细节应该依赖于抽象.
接口隔离原则客户端不应该依赖那些它不需要的接口.
合成复用原则优先使用对象组合,而不是通过继承来达到复用的目的.
迪米特法则每一个软件单位对其他单位都只有最少的了解,而且局限于那些与本单位密切相关的软件单位.

单一职责原则

单一职责原则用于控制类的粒度大小.

一个类承担的职责越多,它被复用的可能性就越小.一个类承担的职责过多,相当于将这些职责耦合在一起,当其中一个职责变化时可能会影响其他职责的运作,因此需要将这些职责进行分离,将不同的职责封装在不同的类中.单一职责原则是实现高内聚,低耦合的指导方针.

开闭原则

开闭原则是指软件实体应尽量不修改原有代码的情况下进行扩展.

为了满足开闭原则,需要对系统进行抽象化设计,抽象化是开闭原则的关键.如果需要修改系统的行为,无需对抽象层进行任何改动,只需要增加新的具体类来实现新的业务即可.

里氏代换原则

里氏代换原则是实现开闭原则的重要方式之一,由于在使用基类对象的地方都可以使用子类对象,因此在程序中尽量使用基类类型来对对象进行定义,而在运行时再确定其子类类型,用子类对象替换父类对象.

依赖倒转原则

依赖倒转原则是面向对象设计的主要实现机制之一,它是系统抽象化的具体实现.依赖倒转原则要求针对接口编程,不要针对实现编程.

依赖倒转原则要求在程序代码中传递参数时或在关联关系中尽量引用层次高的抽象层类,在程序中尽量使用抽象层编程,而将具体类写在配置文件中,这样如果系统行为发生变化,只需要对抽象层进行扩展,并修改配置文件,无须修改原有系统的源代码,满足开闭原则.

接口隔离原则

根据接口隔离原则,当一个接口太大时,需要将它分割成一些更细小的接口,使用该接口的客户端仅需知道与之相关的方法即可,每一个接口应该承担一种相对独立的角色.

把接口理解为类型,接口的划分将直接带来类型的划分,每个类型有它特定的方法.

把接口理解为语言上的接口,那接口仅仅提供客户端需要的行为,客户端不需要的行为则隐藏起来.需要注意控制接口的粒度,接口不能太小,否则接口泛滥,不利于维护;接口不能太大,太大违背接口隔离原则.

合成复用原则

合成复用原则就是在一个新的对象里通过关联关系(组合或聚合)来使用一些已有的对象,新对象通过委派调用已有对象的方法到达功能复用的目的.

如果两个类之间是Has-A的关系应使用组合或聚合,如果是Is-A的关系可使用继承.Is-A表示一个类是另一个类的一种,Has-A表示某一个对象具有一项功能.

迪米特法则

迪米特法则要求一个软件实体尽量少的与其他实体发生相互作用,如果符合迪米特法则,则其中一个模块发生修改时,则对其他模块的影响则尽可能少.迪米特法则要求限制软件实体之间通信的宽度和深度.应用迪米特法则可降低系统的耦合度.

系统设计运用迪米特法则的注意点:

  1. 类的划分上尽量创建松耦合的类
  2. 在类的结构设计上,每一个类都应当尽量降低其成员变量和成员函数的访问权限
  3. 在类的设计上,只要有可能,一个类型应当设计成不变类
  4. 在对其他类的引用上,一个对象对其他对象的引用应当降到最低

简单工厂模式

简单工厂模式: 在工厂类中提供一个创建产品的工厂方法(一般为静态方法,也称静态工厂方法),该方法根据传入的参数的不同创建不同的产品对象.

结构

工厂角色(Factory): 即工厂类, 负责实现创建所有产品实例的内部逻辑, 可被外界直接调用,返回抽象产品类型Product.

抽象产品角色(Product): 工厂类创建的所有对象的父类,封装了各种产品对象的公有方法.引入抽象产品角色,将提高系统的灵活性,这样工厂类只需定义一个通用的工厂方法即可.

具体产品角色(ConcreteProduct): 简单工厂模式创建的目标,所创建的对象是具体产品类的实例.每个具体产品角色都继承抽象产品角色,需要实现抽象产品中声明的抽象方法.

应用实例

开发一套图表库,图表库可以为应用系统提供多种不同外观的图表,如柱状图(HistogramChart),饼状图(PieChart),折线图(LineChart)等.希望通过设置不同的参数即可得到不同类型的图表,且可以方便的在将来增加一些新类型的图表.

/
 * 图表接口
 *
 * @author lzlg
 * 2023/2/23 13:50
 */
public interface Chart {
    /
     * 显示(抽象方法)
     */
    void display();
}

/
 * 柱状图
 *
 * @author lzlg
 * 2023/2/23 13:51
 */
public class HistogramChart implements Chart {
    public HistogramChart() {
        System.out.println("创建柱状图.");
    }

    /
     * 显示(抽象方法)
     */
    @Override
    public void display() {
        System.out.println("显示柱状图.");
    }
}

/
 * 饼状图
 *
 * @author lzlg
 * 2023/2/23 13:51
 */
public class PieChart implements Chart {
    public PieChart() {
        System.out.println("创建饼状图.");
    }

    /
     * 显示(抽象方法)
     */
    @Override
    public void display() {
        System.out.println("显示饼状图.");
    }
}

/
 * 折线图
 *
 * @author lzlg
 * 2023/2/23 13:51
 */
public class LineChart implements Chart {
    public LineChart() {
        System.out.println("创建折线图.");
    }

    /
     * 显示(抽象方法)
     */
    @Override
    public void display() {
        System.out.println("显示折线图.");
    }
}

/
 * 图表工厂类
 *
 * @author lzlg
 * 2023/2/23 13:53
 */
public class ChartFactory {

    /
     * 图表类型
     */
    public enum ChartType {
        HISTOGRAM, PIE, LINE;
    }

    /
     * 静态工厂方法
     *
     * @param chartType 图表类型
     * @return 图表
     */
    public static Chart getChart(ChartType chartType) {
        switch (chartType) {
            case HISTOGRAM:
                return new HistogramChart();
            case PIE:
                return new PieChart();
            case LINE:
                return new LineChart();
            default:
                throw new RuntimeException("没有其他类型图表");
        }
    }
}

/
 * 客户端
 *
 * @author lzlg
 * 2023/2/23 13:57
 */
public class Client {
    public static void main(String[] args) {
        Chart chart = ChartFactory.getChart(ChartFactory.ChartType.HISTOGRAM);
        chart.display();
        System.out.println("====================");
        chart = ChartFactory.getChart(ChartFactory.ChartType.LINE);
        chart.display();
    }
}

对象的创建和使用

面向对象软件系统中与一个对象相关的职责通常有3类,即对象本身的职责,创建对象的职责和使用对象的职责.

java语言中创建对象的方式: 1)使用new关键字直接创建对象; 2)通过反射机制创建对象; 3)通过克隆方法创建对象; 4)通过工厂类创建对象; 5)通过反序列化创建对象;

所有的工厂模式都强调: 工厂类和产品类之间应该仅仅是工厂创建产品,或是工厂使用产品,但不能即创建产品还使用产品,应将对象的创建和使用分离,使得系统更加符合单一职责原则,有利于对功能的复用和系统的维护.

将对象的创建和使用分离,防止用来实例化一个类的数据和代码在多个类中到处都有.

java语言的构造器只能通过传入不同的参数来区分使用,而使用工厂方法可提供一系列方法名称不同的工厂方法,利于客户端使用.

如果一个类很简单,而且不存在太多变化,构造过程也简单,此时无须为其提供工厂类,直接new出来即可.

有时候为了简化简单工厂模式,直接在抽象产品角色里编写工厂方法.

优缺点

优点

  1. 工厂类包含必要的判断逻辑,可以决定在什么时候创建哪一个产品类的实例,客户端可直接使用,简单工厂模式实现了对象创建和使用的分离.
  2. 客户端无须知道所创建的具体产品类的类名,只需要知道具体产品类所对应的参数即可.方便客户端使用.
  3. 通过引入配置文件,可以在不修改任何客户端代码的情况下更换和增加新的具体产品类.提高了系统灵活性.

缺点

  1. 工厂类集中了所有产品的创建逻辑,职责过重,一旦不能正常工作,整个系统将受到影响.
  2. 使用简单工厂模式势必会增加系统中类的个数,增加了系统的复杂度和理解难度.
  3. 系统扩展困难,一旦添加新产品就不得不修改工厂逻辑.在产品较多时有可能造成工厂逻辑过于复杂.
  4. 简单工厂模式使用了静态工厂方法,造成工厂角色无法形成基于继承的等级结构.

适用环境

  1. 工厂类负责创建的对象比较少,这样工厂方法中创建的业务逻辑不会太复杂.
  2. 客户端只知道传入工厂类的参数,对于如何创建对象并不关心.

工厂方法模式

工厂方法模式中不再提供一个统一的工厂类来创建所有的产品对象,而是针对不同的产品提供不同的工厂,系统提供一个与产品等级结构对应的工厂等级结构.

工厂方法模式提供一个抽象工厂接口来声明抽象工厂方法,而由其子类来具体实现工厂方法,创建具体的产品对象.

结构

抽象产品(Product): 是定义产品的接口,是工厂方法模式所创建对象的超类型,是产品对象的公共父类.

具体产品(ConcreteProduct): 实现了抽象产品接口,由相应的具体工厂创建,和具体工厂一一对应.

抽象工厂(Factory): 声明工厂方法,用于返回一个产品,创建对象的子工厂都必须实现此接口.

具体工厂(ConcreteFactory): 是抽象工厂的子类,实现抽象工厂声明的抽象方法,并由客户端调用返回具体产品实例.

应用实例

某系统使用日志记录器(Logger)可以通过多种途径保存系统的运行日志,例如通过文件记录或数据库记录,可通过修改配置文件灵活更换日志记录方式.开发时发现日志记录器的初始化比较复杂,某些参数有严格的先后次序.为更好的封装记录器的初始化过程并保证多种记录器切换的灵活性,使用工厂方法模式设计该系统.

/
 * 日志记录器接口(抽象产品)
 *
 * @author lzlg
 * 2023/2/23 14:44
 */
public interface Logger {
    /
     * 记录日志
     */
    void writeLog();
}

/
 * 文件记录日志器(具体产品)
 *
 * @author lzlg
 * 2023/2/23 14:45
 */
public class FileLogger implements Logger {
    /
     * 记录日志
     */
    @Override
    public void writeLog() {
        System.out.println("文件记录日志.");
    }
}

/
 * 数据库记录日志器(具体产品)
 *
 * @author lzlg
 * 2023/2/23 14:46
 */
public class DatabaseLogger implements Logger {
    /
     * 记录日志
     */
    @Override
    public void writeLog() {
        System.out.println("数据库日志记录.");
    }
}

/
 * 日志记录器工厂类(抽象工厂)
 *
 * @author lzlg
 * 2023/2/23 14:47
 */
public interface LoggerFactory {
    /
     * 创建日志记录器(抽象工厂方法)
     *
     * @return 日志记录器
     */
    Logger createLogger();
}

/
 * 文件记录器创建工厂类(具体工厂)
 *
 * @author lzlg
 * 2023/2/23 14:50
 */
public class FileLoggerFactory implements LoggerFactory {
    /
     * 创建日志记录器(抽象工厂方法)
     *
     * @return 日志记录器
     */
    @Override
    public Logger createLogger() {
        return new FileLogger();
    }
}

/
 * 数据库记录器创建工厂类(具体工厂)
 *
 * @author lzlg
 * 2023/2/23 14:50
 */
public class DatabaseLoggerFactory implements LoggerFactory {
    /
     * 创建日志记录器(抽象工厂方法)
     *
     * @return 日志记录器
     */
    @Override
    public Logger createLogger() {
        return new DatabaseLogger();
    }
}

/
 * 客户端
 *
 * @author lzlg
 * 2023/2/23 14:51
 */
public class Client {
    public static void main(String[] args) {
        LoggerFactory factory = new DatabaseLoggerFactory();
        Logger logger = factory.createLogger();
        logger.writeLog();
        System.out.println("========================");
        factory = new FileLoggerFactory();
        logger = factory.createLogger();
        logger.writeLog();
    }
}

工厂方法重载:

创建日志记录器时,可传入一些相关的参数,来配置创建的日志记录器.

/
 * 日志记录器工厂类(抽象工厂)
 *
 * @author lzlg
 * 2023/2/23 14:47
 */
public interface LoggerFactory {
    /
     * 创建日志记录器(抽象工厂方法)
     *
     * @return 日志记录器
     */
    Logger createLogger();

    /
     * 创建日志记录器(抽象工厂方法)
     *
     * @param args 参数
     * @return 日志记录器
     */
    Logger createLogger(String args);
}

工厂方法隐藏:

有时为了进一步简化客户端的使用, 在工厂类中直接调用产品类的业务方法,客户端无须调用工厂方法创建产品对象,直接使用工厂对象即可调用所创建的产品对象中的业务方法.

/
 * 日志记录器工厂类(抽象工厂)
 *
 * @author lzlg
 * 2023/2/23 14:47
 */
public abstract class AbstractLoggerFactory {
    /
     * 创建日志记录器(抽象工厂方法)
     *
     * @return 日志记录器
     */
    protected abstract Logger createLogger();

    /
     * 直接调用日志记录器的业务方法
     */
    public void writeLog() {
        Logger logger = this.createLogger();
        logger.writeLog();
    }
}

/
 * 客户端
 *
 * @author lzlg
 * 2023/2/23 14:51
 */
public class Client {
    public static void main(String[] args) {
        AbstractLoggerFactory factory = new DBLoggerFactory();
        factory.writeLog();
    }
}

优缺点

优点

  1. 工厂方法用来创建客户所需要的产品,同时向客户端隐藏了哪种具体产品类将被实例化的细节,客户端只须关心所需产品对应的工厂,无须关心创建细节,甚至无须知道具体产品类的类名.
  2. 基于工厂角色和产品角色的多态性设计,能够让工厂自主确定创建何种产品对象.
  3. 系统中加入新产品时无须修改抽象工厂和抽象产品提供的接口,无须修改客户端,无须修改其他具体的工厂和具体产品,而只要添加一个具体工厂和具体产品即可.符合开闭原则.

缺点

  1. 添加新的产品时需要编写新的具体产品类,同时还要提供与之对应的具体工厂类,系统中类的个数成对增加,一定程度上增加了系统的复杂度,有更多的类需要编译运行,会给系统带来一些额外的开销.
  2. 由于考虑到系统的可扩展性,需要引入抽象层,客户端代码均使用抽象层,增加了系统的抽象性和理解难度.

适用环境

  1. 当客户端不知道它所需要的对象的类时.客户端只需要知道对应的工厂即可.
  2. 抽象工厂类通过子类来指定创建哪个对象.

抽象工厂模式

抽象工厂模式用于创建多个位于不同产品等级结构,属于不同类型的具体产品,负责创建一族产品.与工厂方法模式的区别是工厂方法模式针对的是一个产品等级结构,而抽象工厂模式需要面对多个产品等级结构.

结构

抽象工厂(AbstractFactory): 声明一组用于创建一族产品的方法,每一个方法对应一种产品.

具体工厂(ConcreteFactory): 实现抽象工厂中声明的创建产品的方法,生成一组具体产品,这些产品构成一个产品族.

抽象产品(AbstractProduct): 为每种产品声明接口,声明产品具有的业务方法.

具体产品(ConcreteProduct): 定义具体工厂生产的具体产品对象,实现抽象产品接口中声明的业务方法.

应用实例

某公司开发一套界面皮肤库,不同的皮肤将提供视觉效果不同的按钮,文本框,组合框等界面元素.皮肤比如有春天(Spring)风格的,有夏天(Summer)风格的.该皮肤库需要具备良好的灵活性和可扩展性,用户可自由选择皮肤,开发人员可在不修改原有代码的基础上添加新的皮肤.

/
 * 按钮(抽象产品)
 *
 * @author lzlg
 * 2023/2/24 17:07
 */
public interface Button {
    /
     * 展示
     */
    void display();
}

/
 * 春天风格的按钮(具体产品)
 *
 * @author lzlg
 * 2023/2/24 17:09
 */
public class SpringButton implements Button {
    /
     * 展示
     */
    @Override
    public void display() {
        System.out.println("显示浅绿色按钮.");
    }
}

/
 * 夏天风格的按钮(具体产品)
 *
 * @author lzlg
 * 2023/2/24 17:09
 */
public class SummerButton implements Button {
    /
     * 展示
     */
    @Override
    public void display() {
        System.out.println("显示浅蓝色按钮.");
    }
}

/
 * 文本框(抽象产品)
 *
 * @author lzlg
 * 2023/2/24 17:07
 */
public interface TextField {
    /
     * 展示
     */
    void display();
}

/
 * 春天风格的文本框(具体产品)
 *
 * @author lzlg
 * 2023/2/24 17:11
 */
public class SpringTextField implements TextField {
    /
     * 展示
     */
    @Override
    public void display() {
        System.out.println("显示绿色边框文本框.");
    }
}

/
 * 夏天风格的文本框(具体产品)
 *
 * @author lzlg
 * 2023/2/24 17:11
 */
public class SummerTextField implements TextField {
    /
     * 展示
     */
    @Override
    public void display() {
        System.out.println("显示蓝色边框文本框.");
    }
}

/
 * 组合框(抽象产品)
 *
 * @author lzlg
 * 2023/2/24 17:09
 */
public interface ComboBox {
    /
     * 展示
     */
    void display();
}

/
 * 春天风格的组合框(具体产品)
 *
 * @author lzlg
 * 2023/2/24 17:12
 */
public class SpringComboBox implements ComboBox {
    /
     * 展示
     */
    @Override
    public void display() {
        System.out.println("显示绿色边框组合框.");
    }
}

/
 * 夏天风格的组合框(具体产品)
 *
 * @author lzlg
 * 2023/2/24 17:12
 */
public class SummerComboBox implements ComboBox {
    /
     * 展示
     */
    @Override
    public void display() {
        System.out.println("显示蓝色边框组合框.");
    }
}

/
 * 皮肤工厂(抽象工厂)
 *
 * @author lzlg
 * 2023/2/24 17:06
 */
public interface SkinFactory {
    /
     * 创建按钮
     *
     * @return 按钮
     */
    Button createButton();

    /
     * 创建文本框
     *
     * @return 文本框
     */
    TextField createTextField();

    /
     * 创建组合框
     *
     * @return 组合框
     */
    ComboBox createComboBox();
}

/
 * 春天风格皮肤工厂(具体工厂)
 *
 * @author lzlg
 * 2023/2/24 17:15
 */
public class SpringSkinFactory implements SkinFactory {
    /
     * 创建按钮
     *
     * @return 按钮
     */
    @Override
    public Button createButton() {
        return new SpringButton();
    }

    /
     * 创建文本框
     *
     * @return 文本框
     */
    @Override
    public TextField createTextField() {
        return new SpringTextField();
    }

    /
     * 创建组合框
     *
     * @return 组合框
     */
    @Override
    public ComboBox createComboBox() {
        return new SpringComboBox();
    }
}

/
 * 夏天风格皮肤工厂(具体工厂)
 *
 * @author lzlg
 * 2023/2/24 17:15
 */
public class SummerSkinFactory implements SkinFactory {
    /
     * 创建按钮
     *
     * @return 按钮
     */
    @Override
    public Button createButton() {
        return new SummerButton();
    }

    /
     * 创建文本框
     *
     * @return 文本框
     */
    @Override
    public TextField createTextField() {
        return new SummerTextField();
    }

    /
     * 创建组合框
     *
     * @return 组合框
     */
    @Override
    public ComboBox createComboBox() {
        return new SummerComboBox();
    }
}

如果有新的界面组件(比如单选按钮),则新增时将十分麻烦,因为抽象工厂SkinFactory没有声明创建单选按钮的方法.

抽象工厂方法增加新的产品族很方便(比如再添加一种风格的皮肤界面组件),但是添加新的产品等级结构很麻烦(比如再添加表单界面组件),抽象工厂这种性质称为开闭原则的倾斜性.需要在设计之初能够考虑全面,产品等级结构不会在系统中有大的变动.

优缺点

优点

  1. 抽象工厂模式隔离了具体类的生成,更容易替换一个具体工厂.
  2. 当一个产品族中的多个对象设计成一起工作时,能够保证客户端始终只使用同一个产品族中的对象.
  3. 增加新的产品族很方便,无须修改原有系统.

缺点

增加新的产品等级结构麻烦,需要对原有系统进行较大的修改.

适用环境

  1. 一个系统不应当依赖于产品类实例创建,组合和表达的细节,用户无须关心对象的创建过程,将对象的创建和使用解耦.
  2. 系统中有多于一个的产品族,而每次只能使用其中某一个产品族.
  3. 系统中属于同一个产品族的产品将在一起使用.
  4. 产品等级结构稳定,设计完成后不会增加或删除已有的产品等级结构.

建造者模式

建造者模式关注一步一步地创建一个包含多个组成部分地复杂对象,将客户端与复杂对象地创建过程分离,客户端无须知道复杂对象的创建细节,只需要知道所需建造者的类型即可.

结构

抽象建造者(Builder): 为创建一个产品对象的各个部件指定抽象接口.

具体建造者(ConcreteBuilder): 实现抽象建造者的接口,实现各个部件的具体构造和装配方法,定义并明确它所创建的对象,还可提供一个返回创建好的产品对象的方法.

产品(Product): 是被构建的复杂对象,包含多个组成部件,具体创建者创建产品的内部表示并定义产品的装配过程.

指挥者(Director): 负责安排复杂对象的构建次序,调用建造者的对象构造和装配方法,完成复杂对象的建造.

应用实例

设计一个游戏角色,游戏角色包含性别,脸型,攻击力等组成部分,不同的游戏角色的外部特征有所差异,例如天使有美丽的面容和披肩的长发,而恶魔及其丑陋,留着光头并穿一身刺眼的黑衣.

/
 * 角色(产品)
 *
 * @author lzlg
 * 2023/2/24 18:06
 */
public class Actor {
    // 角色类型
    private String type;
    // 性别
    private String sex;
    // 面容
    private String face;
    // 服装
    private String costume;
    // 发型
    private String hairstyle;

    public String getType() {
        return type;
    }

    public void setType(String type) {
        this.type = type;
    }

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    public String getFace() {
        return face;
    }

    public void setFace(String face) {
        this.face = face;
    }

    public String getCostume() {
        return costume;
    }

    public void setCostume(String costume) {
        this.costume = costume;
    }

    public String getHairstyle() {
        return hairstyle;
    }

    public void setHairstyle(String hairstyle) {
        this.hairstyle = hairstyle;
    }

    @Override
    public String toString() {
        return "角色{" +
                "类型='" + type + '\'' +
                ", 性别='" + sex + '\'' +
                ", 面容='" + face + '\'' +
                ", 服装='" + costume + '\'' +
                ", 发型='" + hairstyle + '\'' +
                '}';
    }
}

/
 * 角色建造者(抽象建造者)
 *
 * @author lzlg
 * 2023/2/24 18:08
 */
public abstract class ActorBuilder {
    // 角色
    protected Actor actor = new Actor();

    /
     * 构建角色类型
     */
    protected abstract void buildType();

    /
     * 构建角色性别
     */
    protected abstract void buildSex();

    /
     * 构建角色面容
     */
    protected abstract void buildFace();

    /
     * 构建角色服装
     */
    protected abstract void buildCostume();

    /
     * 构建角色发型
     */
    protected abstract void buildHairstyle();

    /
     * 返回角色
     *
     * @return 角色
     */
    public Actor createActor() {
        return actor;
    }
}

/
 * 角色创建指挥者(指挥者)
 *
 * @author lzlg
 * 2023/2/24 18:18
 */
public class ActorDirector {
    /
     * 构建角色
     *
     * @param builder 建造者
     * @return 角色
     */
    public Actor construct(ActorBuilder builder) {
        builder.buildType();
        builder.buildSex();
        builder.buildFace();
        builder.buildCostume();
        builder.buildHairstyle();
        return builder.createActor();
    }
}

/
 * 英雄角色建造者(具体建造者)
 *
 * @author lzlg
 * 2023/2/24 18:12
 */
public class HeroBuilder extends ActorBuilder {
    /
     * 构建角色类型
     */
    @Override
    protected void buildType() {
        actor.setType("英雄");
    }

    /
     * 构建角色性别
     */
    @Override
    protected void buildSex() {
        actor.setSex("女");
    }

    /
     * 构建角色面容
     */
    @Override
    protected void buildFace() {
        actor.setFace("漂亮");
    }

    /
     * 构建角色服装
     */
    @Override
    protected void buildCostume() {
        actor.setCostume("披风");
    }

    /
     * 构建角色发型
     */
    @Override
    protected void buildHairstyle() {
        actor.setHairstyle("飘逸");
    }
}

/
 * 天使角色建造者(具体建造者)
 *
 * @author lzlg
 * 2023/2/24 18:12
 */
public class AngelBuilder extends ActorBuilder {
    /
     * 构建角色类型
     */
    @Override
    protected void buildType() {
        actor.setType("天使");
    }

    /
     * 构建角色性别
     */
    @Override
    protected void buildSex() {
        actor.setSex("女");
    }

    /
     * 构建角色面容
     */
    @Override
    protected void buildFace() {
        actor.setFace("白净");
    }

    /
     * 构建角色服装
     */
    @Override
    protected void buildCostume() {
        actor.setCostume("白裙");
    }

    /
     * 构建角色发型
     */
    @Override
    protected void buildHairstyle() {
        actor.setHairstyle("长发");
    }
}

/
 * 恶魔角色建造者(具体建造者)
 *
 * @author lzlg
 * 2023/2/24 18:12
 */
public class DevilBuilder extends ActorBuilder {
    /
     * 构建角色类型
     */
    @Override
    protected void buildType() {
        actor.setType("恶魔");
    }

    /
     * 构建角色性别
     */
    @Override
    protected void buildSex() {
        actor.setSex("妖");
    }

    /
     * 构建角色面容
     */
    @Override
    protected void buildFace() {
        actor.setFace("丑陋");
    }

    /
     * 构建角色服装
     */
    @Override
    protected void buildCostume() {
        actor.setCostume("黑衣");
    }

    /
     * 构建角色发型
     */
    @Override
    protected void buildHairstyle() {
        actor.setHairstyle("光头");
    }
}

/
 * 客户端
 *
 * @author lzlg
 * 2023/2/24 18:20
 */
public class Client {
    public static void main(String[] args) {
        ActorBuilder builder = new HeroBuilder();
        ActorDirector director = new ActorDirector();
        Actor actor = director.construct(builder);
        System.out.println(actor);
        System.out.println("=================================");
        builder = new AngelBuilder();
        actor = director.construct(builder);
        System.out.println(actor);
        System.out.println("=================================");
        builder = new DevilBuilder();
        actor = director.construct(builder);
        System.out.println(actor);
    }
}

某些情况下为了简化系统结构,可以将指挥者和抽象建造者进行合并:

    /
     * 构建角色
     *
     * @return 角色
     */
    public Actor construct() {
        this.buildType();
        this.buildSex();
        this.buildFace();
        this.buildCostume();
        this.buildHairstyle();
        return this.createActor();
    }

可在抽象建造者中增加一类钩子方法用于控制是否对某一步方法的调用:

public abstract class ActorBuilder {
    /
     * 钩子方法
     */
    protected boolean isBareHead() {
        return false;
    }
}

public class DevilBuilder extends ActorBuilder {
    /
     * 子类重写钩子方法
     */
    @Override
    protected boolean isBareHead() {
        return true;
    }
}

public class ActorDirector {
    /
     * 构建角色
     *
     * @param builder 建造者
     * @return 角色
     */
    public Actor construct(ActorBuilder builder) {
        builder.buildType();
        builder.buildSex();
        builder.buildFace();
        builder.buildCostume();
        // 钩子方法的使用
        if (!builder.isBareHead()) {
            builder.buildHairstyle();
        }
        return builder.createActor();
    }
}

优缺点

优点

  1. 客户端不必知道产品内部组成的细节,将产品本身与产品的创建过程解耦,相同的创建过程可以创建不同的对象.
  2. 可方便更换具体的建造者,增加新的建造者无须改动原有代码.
  3. 可以更加精细地控制产品的创建过程.

缺点

  1. 如果创建的产品的很多组成部分都不相同,则不适合适用建造者模式.
  2. 如果产品内部变化复杂,可能会需要定义很多具体建造者,导致系统变得庞大,增加系统理解难度和运行成本.

适用环境

  1. 需要生成的产品对象有复杂的内部结构,这些产品对象通常包含多个成员变量.
  2. 需要生成的产品对象的属性相互依赖,需要指定其生成顺序.
  3. 对象的创建过程独立于创建该对象的类,引入指挥者类把建造过程封装.
  4. 隔离复杂对象的创建和使用,使得相同的创建过程可以创建不同的产品.

原型模式

原型模式是将一个原型对象传给发动创建的对象,这个要发动创建的对象通过请求原型对象复制自己来实现创建过程,通过克隆方法所创建的对象是全新的对象,在内存中拥有新的地址,每一个克隆对象都是相互独立的.

结构

抽象原型类(Prototype): 声明克隆方法的接口,是所有具体原型类的公共父类.

具体原型类(ConcretePrototye): 实现在抽象原型类中声明的克隆方法,在克隆方法中返回自己的一个克隆对象.

客户端(Client): 让一个原型对象克隆自身从而创建一个新的对象.针对抽象原型类编程.

应用实例

使用OA系统的用户写工作周报很多内容都是重复的,迫切希望有一种机制能够快速创建相同或者相似的周报,包括周报的附件.

使用java语言的clone机制,需要实现cloneable接口:

/
 * 周报(原型类)
 * 使用java语言的clone机制,需要实现cloneable接口
 *
 * @author lzlg
 * 2023/2/25 15:46
 */
public class WeeklyLog implements Cloneable {
    // 假设周报里只有一份附件
    private Attachment attachment;

    private String name;

    private String date;

    private String content;

    public Attachment getAttachment() {
        return attachment;
    }

    public void setAttachment(Attachment attachment) {
        this.attachment = attachment;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getDate() {
        return date;
    }

    public void setDate(String date) {
        this.date = date;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }

    @Override
    protected WeeklyLog clone() {
        try {
            return (WeeklyLog) super.clone();
        } catch (CloneNotSupportedException e) {
            throw new RuntimeException(e);
        }
    }
}

/
 * 周报附件
 *
 * @author lzlg
 * 2023/2/25 15:46
 */
public class Attachment {
    // 附件文件名称
    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void download() {
        System.out.println("下载附件,文件名为:" + name);
    }
}

/
 * 客户端
 *
 * @author lzlg
 * 2023/2/25 15:51
 */
public class Client {
    public static void main(String[] args) {
        // 原型对象
        WeeklyLog prototype = new WeeklyLog();
        prototype.setName("2023-02第四周周报");
        prototype.setContent("本周没有工作");
        prototype.setDate("2023-02-24");
        // 周报附件
        Attachment attachment = new Attachment();
        attachment.setName("工作文档.docx");
        prototype.setAttachment(attachment);
        // 通过克隆生成对象(浅克隆)
        WeeklyLog clone = prototype.clone();
        System.out.println("周报对象是否相同? " + (clone == prototype));
        System.out.println("周报附件对象是否相同? " + (clone.getAttachment() == prototype.getAttachment()));
    }
}

使用序列化机制实现深克隆:

/
 * 周报(原型类)
 *
 * @author lzlg
 * 2023/2/25 15:46
 */
public class WeeklyLog implements Serializable {

    private static final long serialVersionUID = -19881234556L;

    // 假设周报里只有一份附件
    private Attachment attachment;

    private String name;

    private String date;

    private String content;

    public Attachment getAttachment() {
        return attachment;
    }

    public void setAttachment(Attachment attachment) {
        this.attachment = attachment;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getDate() {
        return date;
    }

    public void setDate(String date) {
        this.date = date;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }

    public WeeklyLog serialClone() {
        // 序列化对象
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        try (ObjectOutputStream oos = new ObjectOutputStream(bos)) {
            oos.writeObject(this);
            // 反序列化
            ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
            ObjectInputStream ois = new ObjectInputStream(bis);
            return (WeeklyLog) ois.readObject();
        } catch (IOException | ClassNotFoundException e) {
            throw new RuntimeException(e);
        }
    }
}

/
 * 周报附件
 *
 * @author lzlg
 * 2023/2/25 15:46
 */
public class Attachment implements Serializable {

    private static final long serialVersionUID = -89991234556L;

    // 附件文件名称
    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void download() {
        System.out.println("下载附件,文件名为:" + name);
    }
}

/
 * 客户端
 *
 * @author lzlg
 * 2023/2/25 15:51
 */
public class Client {
    public static void main(String[] args) {
        // 原型对象
        WeeklyLog prototype = new WeeklyLog();
        prototype.setName("2023-02第四周周报");
        prototype.setContent("本周没有工作");
        prototype.setDate("2023-02-24");
        // 周报附件
        Attachment attachment = new Attachment();
        attachment.setName("工作文档.docx");
        prototype.setAttachment(attachment);
        // 通过反序列化生成对象(深克隆)
        WeeklyLog clone = prototype.serialClone();
        System.out.println("周报对象是否相同? " + (clone == prototype));
        System.out.println("周报附件对象是否相同? " + (clone.getAttachment() == prototype.getAttachment()));
    }
}

一般在开发时使用Spring框架的BeanUtils.copyProperties()做属性值的复制.

优缺点

优点

  1. 创建新的对象实例比较复杂时,使用原型模式可以简化对象的创建过程,提高创建效率.
  2. 扩展性好,使用抽象原型类,客户端可以针对抽象编程.
  3. 原型模式提供了简化的创建结构,直接使用在原型类中封装的克隆方法,无须专门的工厂类来创建产品.
  4. 可以使用深克隆的方式保存对象的状态,以便在需要的时候使用,可辅助实现撤销操作.

缺点

  1. 需要为每一个类配备一个克隆方法,克隆方法在类的内部,对已有类改造时需修改源代码,违背了开闭原则.
  2. 实现深克隆时需要编写较为复杂的代码,需要每一层对象都必须支持深克隆,实现较麻烦.

适用环境

  1. 创建新对象成本较大,新对象通过复制已有对象来获得,如果是相似对象,可对成员变量稍作修改.
  2. 系统需要保存对象的状态,而对象的状态变化很小.
  3. 需要避免使用分层次的工厂类来创建分层次的对象,并且类的实例对象只有一个或很少的组合状态,通过复制员对象可能比使用构造函数创建一个新实例更加方便.

单例模式

单例模式能够保证一个系统的某些类只有一个对象.有三个要点: 一是类只能有一个实例; 而是它必须自行创建这个实例; 三是它必须自行向整个系统提供整个实例.

结构

单例类(Singleton): 在单例类的内部创建它的唯一实例,并通过静态方法提供实例,构造函数可见性设为private.

应用实例

饿汉式

/
 * 饿汉式单例
 *
 * @author lzlg
 * 2023/2/25 16:38
 */
public class EagerSingleton {

    private static final EagerSingleton instance = new EagerSingleton();

    private EagerSingleton() {
    }

    public static EagerSingleton getInstance() {
        return instance;
    }
}

懒汉式

/
 * 双重检测懒汉式
 *
 * @author lzlg
 * 2023/2/25 16:40
 */
public class LazySingleton {
    // 必须声明为volatile,否则会因为重排序的优化,造成重复创建
    private static volatile LazySingleton singleton = null;

    private LazySingleton() {
    }

    /
     * 双重检查单例(线程安全)
     */
    public static LazySingleton getInstance() {
        if (singleton == null) {
            synchronized (LazySingleton.class) {
                if (singleton == null) {
                    singleton = new LazySingleton();
                }
            }
        }
        return singleton;
    }
}

枚举单例

/
 * 枚举单例
 *
 * @author lzlg
 * 2023/2/25 16:45
 */
public enum EnumSingleton {

    INSTANCE;
}

静态内部类

/
 * 使用静态内部类实现单例
 * 使用虚拟机加载类的机制保证线程安全
 *
 * @author lzlg
 * 2023/2/25 16:46
 */
public class StaticInnerClassSingleton {

    private static class Holder {
        private static final StaticInnerClassSingleton singleton = new StaticInnerClassSingleton();
    }

    public static StaticInnerClassSingleton getInstance() {
        return Holder.singleton;
    }
}

优缺点

优点

  1. 单例模式提供了对唯一实例的严格受控访问.
  2. 系统内存中只存在一个对象,节约系统资源,可以提高系统的性能.
  3. 允许可变数目的实例,基于单例模式可以扩展,使用与控制单例对象相似的方法来获得指定个数的实例对象.

缺点

  1. 单例模式没有抽象层,单例类的扩展很困难.
  2. 单例类的职责过重,一定程度上违背了单一职责原则.单例类将对象的创建和对象本身的功能耦合在一起.
  3. 自动垃圾回收有可能会回收单例对象(长时间不使用),下次使用还得实例化,导致共享的单例对象状态的丢失.

适用环境

  1. 系统只需要一个实例对象.
  2. 客户端调用的单个实例只允许使用一个公共访问点,其他途径不能访问.

适配器模式

适配器模式是把客户端的请求转化为对适配者的相应接口的调用,适配器让那些接口不兼容的类可以一起工作.别名包装(Wrapper)器模式,可以作为类结构模式,也可以作为对象结构模式.

结构

目标抽象类(Target): 定义客户需要的接口,在Java中一般使用接口.

适配器类(Adapter): 对目标类和适配者进行适配.在类适配器中,通过实现目标类接口并继承(或组合)适配者;在对象适配器中,通过继承目标类并组合一个适配者.

适配者类(Adaptee): 被适配的角色,定义了已经存在的接口,包含了客户端希望使用的方法.

应用实例

某公司有一款儿童玩具汽车,公司以往产品中已经实现了控制灯光闪烁和声音提示的程序,需要使玩具汽车在移动过程中伴随着灯光闪烁和声音提示.

下面代码演示的是对象适配器:

/
 * 玩具汽车控制类(目标抽象类)
 *
 * @author lzlg
 * 2023/2/26 15:35
 */
public abstract class CarController {

    public void move() {
        System.out.println("玩具汽车移动.");
    }

    /
     * 声音提示
     */
    public abstract void sound();

    /
     * 灯光闪烁
     */
    public abstract void twinkle();
}

/
 * 警灯类(适配者)
 *
 * @author lzlg
 * 2023/2/26 15:38
 */
public class PoliceLamp {

    public void alarmLamp() {
        System.out.println("呈现警灯闪烁.");
    }
}

/
 * 警笛类(适配者)
 *
 * @author lzlg
 * 2023/2/26 15:38
 */
public class PoliceSound {

    public void alarmSound() {
        System.out.println("发出警笛声音.");
    }
}

/
 * 玩具警车(适配器)
 *
 * @author lzlg
 * 2023/2/26 15:40
 */
public class PoliceCarAdapter extends CarController {

    private PoliceLamp lamp;

    private PoliceSound sound;

    public PoliceCarAdapter() {
        this.lamp = new PoliceLamp();
        this.sound = new PoliceSound();
    }

    /
     * 声音提示
     */
    @Override
    public void sound() {
        this.sound.alarmSound();
    }

    /
     * 灯光闪烁
     */
    @Override
    public void twinkle() {
        this.lamp.alarmLamp();
    }
}

/
 * 客户端类
 *
 * @author lzlg
 * 2023/2/26 15:42
 */
public class Client {
    public static void main(String[] args) {
        CarController car = new PoliceCarAdapter();
        car.move();
        car.sound();
        car.twinkle();
    }
}

缺省适配器模式: 当不需要实现一个接口提供的所有方法时,可以使用一个抽象类实现接口,并为接口的每个方法提供一个默认的实现(空方法),那么抽象类的子类可以选择性的覆盖父类的某些方法实现需求.Java8之后接口有了默认方法,因此直接在接口定义模式方法即可,不再需要抽象适配器了.

/
 * Java8之前服务接口(适配者接口)
 *
 * @author lzlg
 * 2023/2/26 15:48
 */
public interface ServiceApi {

    void serviceA();

    void serviceB();
}

/
 * 缺省适配器类
 *
 * @author lzlg
 * 2023/2/26 15:49
 */
public abstract class AbstractServiceApi implements ServiceApi {

    @Override
    public void serviceA() {

    }

    @Override
    public void serviceB() {

    }
}

/
 * 具体子类
 *
 * @author lzlg
 * 2023/2/26 15:49
 */
public class ConcreteServiceApi extends AbstractServiceApi {

    @Override
    public void serviceA() {
        System.out.println("serviceA");
    }
}

/
 * Java8及以后适配接口
 *
 * @author lzlg
 * 2023/2/26 15:51
 */
public interface ServiceApiJava8 {

    default void serviceA() {
        System.out.println("Interface ServiceA.");
    }

    default void serviceB() {
        System.out.println("Interface ServiceB.");
    }
}

/
 * Java8及以后接口实现
 *
 * @author lzlg
 * 2023/2/26 15:53
 */
public class ConcreteServiceApiJava8 implements ServiceApiJava8 {

    @Override
    public void serviceA() {
        System.out.println("Concrete ServiceA");
    }
}

双向适配器:

/
 * 目标接口
 *
 * @author lzlg
 * 2023/2/26 15:54
 */
public interface Target {

    void request();
}

/
 * 适配者接口
 *
 * @author lzlg
 * 2023/2/26 15:55
 */
public interface Adaptee {

    void handle();
}

/
 * 双向适配器
 *
 * @author lzlg
 * 2023/2/26 15:56
 */
public class Adapter implements Target, Adaptee {

    private Target target;

    private Adaptee adaptee;

    public Adapter(Target target) {
        this.target = target;
    }

    public Adapter(Adaptee adaptee) {
        this.adaptee = adaptee;
    }

    @Override
    public void handle() {
        this.adaptee.handle();
    }

    @Override
    public void request() {
        this.target.request();
    }
}

类适配器: 继承适配者类,实现目标抽象接口.

public class Adapter extends Adaptee implements Target {
    public void request() {
        super.handle();
    }
}

优缺点

优点

  1. 将目标类和适配者类解耦,引入适配器类重用适配者类,无须修改原有结构.
  2. 具体的业务封装在适配器者类中,对客户端类是透明的,提高了适配者的复用性.
  3. 灵活性和扩展性都好,可以方便的更换或新增适配器,符合开闭原则.
  4. 类适配器中,适配器可置换一些适配者的方法,灵活性更强.
  5. 对象适配器中,适配器可把多个不同的适配者适配到同一目标;也可以适配一个适配者的子类.

缺点

  1. 类适配器中,Java语言中单继承,只能适配一个适配者类,不能同时适配多个适配者.
  2. 类适配器中,适配者类不能为final类.
  3. 类适配器中,Java语言中目标抽象类只能为接口,不能为类,有一定的局限性.
  4. 对象适配器中,置换适配者的某些方法比较麻烦.

适用环境

  1. 系统需要使用一些现有的类,而这些类的接口不符合系统的需要,甚至没有这样类的源代码.
  2. 想创建一个可以重复使用的类,用于和一些彼此之间没有太大关联的类一起工作.

桥接模式

桥接模式将两个独立变化的维度设计为两个独立的等级结构,两个独立的等级结构都能够独立变化.桥接模式在抽象层建立起一个抽象关联.桥接模式解决了多层继承存在的问题,将类之间的静态继承关系转换为动态的对象组合关系.

结构

抽象类(Abstraction): 通常是抽象类,其中组合了一个实现类接口的对象.

扩充抽象类(RefinedAbstraction): 扩充抽象类定义的接口,通常是具体类,实现了抽象类中定义的抽象业务方法,可以调用从抽象类中继承的实现类接口的方法.

实现类接口(Implementor): 定义实现类的接口,仅提供基本操作.

具体实现类(ConcreteImplementor): 具体实现了实现类接口定义的方法.

应用实例

某软件公司开发一个跨平台的图像浏览系统,要求系统能够显示BMP, JPG, GIF, PNG等多种格式的文件,并且能够在Windows, Linux和MacOS等多个操作系统上运行.系统首先将各种格式的文件解析为像素矩阵(Matrix),然后将像素矩阵显示在屏幕上,在不同的操作系统中可以调用不同的绘制函数来绘制像素矩阵.

/
 * 像素矩阵
 *
 * @author lzlg
 * 2023/2/26 16:44
 */
public class Matrix {

    private final String id;

    public Matrix(String id) {
        this.id = id;
    }

    @Override
    public String toString() {
        return "Matrix{" +
                "id='" + id + '\'' +
                '}';
    }
}

import java.util.UUID;

/
 * 抽象图像类(抽象类)
 *
 * @author lzlg
 * 2023/2/26 16:45
 */
public abstract class AbstractImage {
    // 绘制像素矩阵
    protected ImagePaint paint;

    public AbstractImage(ImagePaint paint) {
        this.paint = paint;
    }

    public void setPaint(ImagePaint paint) {
        this.paint = paint;
    }

    /
     * 解析文件(待子类实现的抽象方法)
     *
     * @param filename 文件名称
     */
    public abstract void parseFile(String filename);

    /
     * 转换图像为像素矩阵
     *
     * @return 像素矩阵
     */
    public Matrix toMatrix() {
        return new Matrix(UUID.randomUUID().toString());
    }
}

/
 * BMP格式图像(扩充抽象类)
 *
 * @author lzlg
 * 2023/2/26 16:48
 */
public class BMPImage extends AbstractImage {

    public BMPImage(ImagePaint paint) {
        super(paint);
    }

    /
     * 解析文件(待子类实现的抽象方法)
     *
     * @param filename 文件名称
     */
    @Override
    public void parseFile(String filename) {
        Matrix matrix = this.toMatrix();
        this.paint.doPaint(matrix);
        System.out.println(filename + ",格式为BMP.");
    }
}

/
 * GIF格式图像(扩充抽象类)
 *
 * @author lzlg
 * 2023/2/26 16:48
 */
public class GIFImage extends AbstractImage {

    public GIFImage(ImagePaint paint) {
        super(paint);
    }

    /
     * 解析文件(待子类实现的抽象方法)
     *
     * @param filename 文件名称
     */
    @Override
    public void parseFile(String filename) {
        Matrix matrix = this.toMatrix();
        this.paint.doPaint(matrix);
        System.out.println(filename + ",格式为GIF.");
    }
}

/
 * JPG格式图像(扩充抽象类)
 *
 * @author lzlg
 * 2023/2/26 16:48
 */
public class JPGImage extends AbstractImage {

    public JPGImage(ImagePaint paint) {
        super(paint);
    }

    /
     * 解析文件(待子类实现的抽象方法)
     *
     * @param filename 文件名称
     */
    @Override
    public void parseFile(String filename) {
        Matrix matrix = this.toMatrix();
        this.paint.doPaint(matrix);
        System.out.println(filename + ",格式为JPG.");
    }
}

/
 * PNG格式图像(扩充抽象类)
 *
 * @author lzlg
 * 2023/2/26 16:48
 */
public class PNGImage extends AbstractImage {

    public PNGImage(ImagePaint paint) {
        super(paint);
    }

    /
     * 解析文件(待子类实现的抽象方法)
     *
     * @param filename 文件名称
     */
    @Override
    public void parseFile(String filename) {
        Matrix matrix = this.toMatrix();
        this.paint.doPaint(matrix);
        System.out.println(filename + ",格式为PNG.");
    }
}

/
 * 图像绘制接口(实现接口类)
 *
 * @author lzlg
 * 2023/2/26 16:46
 */
public interface ImagePaint {
    /
     * 绘制矩阵
     *
     * @param m 像素矩阵
     */
    void doPaint(Matrix m);
}

/
 * Linux下的具体实现类
 *
 * @author lzlg
 * 2023/2/26 16:52
 */
public class LinuxImagePaint implements ImagePaint {
    /
     * 绘制矩阵
     *
     * @param m 像素矩阵
     */
    @Override
    public void doPaint(Matrix m) {
        System.out.println("在Linux操作系统中绘制图像: " + m);
    }
}

/
 * MacOS下的具体实现类
 *
 * @author lzlg
 * 2023/2/26 16:52
 */
public class MacOSImagePaint implements ImagePaint {
    /
     * 绘制矩阵
     *
     * @param m 像素矩阵
     */
    @Override
    public void doPaint(Matrix m) {
        System.out.println("在MacOS操作系统中绘制图像: " + m);
    }
}

/
 * Windows下的具体实现类
 *
 * @author lzlg
 * 2023/2/26 16:52
 */
public class WindowsImagePaint implements ImagePaint {
    /
     * 绘制矩阵
     *
     * @param m 像素矩阵
     */
    @Override
    public void doPaint(Matrix m) {
        System.out.println("在Windows操作系统中绘制图像: " + m);
    }
}

/
 * 客户端类
 *
 * @author lzlg
 * 2023/2/26 16:59
 */
public class Client {
    public static void main(String[] args) {
        AbstractImage image = new PNGImage(new LinuxImagePaint());
        image.parseFile("杨过");
        System.out.println("======================================");
        image = new GIFImage(new WindowsImagePaint());
        image.parseFile("小龙女");
    }
}

桥接模式用于系统的初步设计阶段.初步设计完成后,当发现系统与已有类无法协同工作是可采用适配器模式.

优缺点

优点

  1. 分离抽象接口及其实现部分,使得抽象和实现可以沿着各自的维度变化.
  2. 很多情况下,桥接模式可以取代多层继承方案,多层继承方式违背单一职责原则,复用性差,且子类的个数非常多,桥接模式极大的减少了子类的个数.
  3. 桥接模式提供了系统的可扩展性,符合开闭原则.

缺点

  1. 桥接模式的使用会增加系统的理解和设计难度,要求开发者针对抽象层进行设计与编程.
  2. 桥接模式要正确识别出系统中两个独立变化的维度,使用范围有一定的局限性,且正确识别两个变化维度需要经验积累.

适用环境

  1. 如果一个系统需要在抽象化和具体化之间增加更多的灵活性,可通过桥接模式在抽象层建立关联关系.
  2. 系统需要对抽象化角色和实现化角色进行运行时动态耦合.
  3. 一个类存在两个或多个独立变化的维度,且这两个或多个维度都需要独立进行扩展.
  4. 桥接模式适合不希望使用继承或者因为多层继承导致系统类个数急剧增加的系统.

组合模式

组合模式通过巧妙地设计使得用户可以一致性地对待容器对象和叶子对象.又称为部分-整体模式,将对象组织到树形结构中,可用来描述整体与部分地关系.

结构

抽象构件(Component): 为叶子构件和容器构件声明接口,包含所有子类共有行为的声明和实现.定义了访问及管理子构件的方法.

叶子构件(Leaf): 表示叶子节点对象,没有子节点,实现了抽象构件中的行为,对于管理子构件的方法,可通过抛出异常,提示错误等方式处理.

容器构件(Composite): 表示容器节点对象,可包含叶子节点,也可包含容器节点,提供一个集合用于存储子节点,实现了抽象构件中定义的行为.在其业务方法上可递归调用子节点的业务方法.

应用实例

某杀毒软件可以对文件夹(Folder杀毒,也可以对单个文件(File)杀毒,可根据不同的文件类型(如图片文件ImageFile,文本文件TextFile)提供不同的杀毒方式.

/
 * 文件抽象父类(抽象构件)
 *
 * @author lzlg
 * 2023/2/27 18:21
 */
public abstract class AbstractFile {
    /
     * 添加文件
     *
     * @param file 文件
     */
    public abstract void add(AbstractFile file);

    /
     * 删除文件
     *
     * @param file 文件
     */
    public abstract void remove(AbstractFile file);

    /
     * 根据下标获取文件
     *
     * @param index 下标
     * @return 文件
     */
    public abstract AbstractFile getChild(int index);

    /
     * 杀毒方法
     */
    public abstract void killVirus();
}

/
 * 文本文件(叶子节点)
 *
 * @author lzlg
 * 2023/2/27 18:25
 */
public class TextFile extends AbstractFile {

    private final String name;

    public TextFile(String name) {
        this.name = name;
    }

    /
     * 添加文件
     *
     * @param file 文件
     */
    @Override
    public void add(AbstractFile file) {
        throw new RuntimeException("不支持该方法");
    }

    /
     * 删除文件
     *
     * @param file 文件
     */
    @Override
    public void remove(AbstractFile file) {
        throw new RuntimeException("不支持该方法");
    }

    /
     * 根据下标获取文件
     *
     * @param index 下标
     * @return 文件
     */
    @Override
    public AbstractFile getChild(int index) {
        throw new RuntimeException("不支持该方法");
    }

    /
     * 杀毒方法
     */
    @Override
    public void killVirus() {
        System.out.println("对文本文件:=" + name + "=进行杀毒.");
    }
}

/
 * 图片文件(叶子构件)
 *
 * @author lzlg
 * 2023/2/27 18:25
 */
public class ImageFile extends AbstractFile {

    private final String name;

    public ImageFile(String name) {
        this.name = name;
    }

    /
     * 添加文件
     *
     * @param file 文件
     */
    @Override
    public void add(AbstractFile file) {
        throw new RuntimeException("不支持该方法");
    }

    /
     * 删除文件
     *
     * @param file 文件
     */
    @Override
    public void remove(AbstractFile file) {
        throw new RuntimeException("不支持该方法");
    }

    /
     * 根据下标获取文件
     *
     * @param index 下标
     * @return 文件
     */
    @Override
    public AbstractFile getChild(int index) {
        throw new RuntimeException("不支持该方法");
    }

    /
     * 杀毒方法
     */
    @Override
    public void killVirus() {
        System.out.println("对图像文件:=" + name + "=进行杀毒.");
    }
}

import java.util.ArrayList;
import java.util.List;

/
 * 文本文件(容器节点)
 *
 * @author lzlg
 * 2023/2/27 18:25
 */
public class FolderFile extends AbstractFile {

    private final String name;

    private final List<AbstractFile> files;

    public FolderFile(String name) {
        this.name = name;
        this.files = new ArrayList<>();
    }

    /
     * 添加文件
     *
     * @param file 文件
     */
    @Override
    public void add(AbstractFile file) {
        files.add(file);
    }

    /
     * 删除文件
     *
     * @param file 文件
     */
    @Override
    public void remove(AbstractFile file) {
        files.remove(file);
    }

    /
     * 根据下标获取文件
     *
     * @param index 下标
     * @return 文件
     */
    @Override
    public AbstractFile getChild(int index) {
        return files.get(index);
    }

    /
     * 杀毒方法
     */
    @Override
    public void killVirus() {
        for (AbstractFile file : files) {
            file.killVirus();
        }
    }
}

/
 * 客户端类
 *
 * @author lzlg
 * 2023/2/27 18:33
 */
public class Client {
    public static void main(String[] args) {
        AbstractFile folder = new FolderFile("个人文件夹");
        AbstractFile imageFolder = new FolderFile("图片文件夹");
        AbstractFile textFolder = new FolderFile("文本文件夹");
        folder.add(imageFolder);
        folder.add(textFolder);

        AbstractFile textFile1 = new TextFile("九阴真经.txt");
        AbstractFile textFile2 = new TextFile("降龙十八掌.txt");
        textFolder.add(textFile1);
        textFolder.add(textFile2);

        AbstractFile imageFile1 = new ImageFile("葵花宝典.png");
        AbstractFile imageFile2 = new ImageFile("辟邪剑法.png");
        imageFolder.add(imageFile1);
        imageFolder.add(imageFile2);

        folder.killVirus();
        System.out.println("========================");
        imageFolder.killVirus();
        System.out.println("========================");
        imageFile2.killVirus();
    }
}

上面用的是透明组合模式: 抽象构件中声明了所有用于管理成员对象的方法,客户端可以一致对待所有对象.但不够安全,因为叶子对象和容器对象在本质上不一样,叶子对象无须实现add, remove, getChild等方法.

安全组合模式: 抽象构件中没有声明任何用于管理成员对象的方法.而在容器构件中声明并实现管理成员的方法.这样做比较安全,缺点是不够透明,客户端不能完全针对抽象编程,必须区别对待叶子构件和容器构件.

优缺点

优点

  1. 可以清楚的定义分层次的复杂对象,让客户端忽略层次差异,方便对整个层次结构进行控制.
  2. 客户端可一致的使用一个组合结构或单个对象,不必关心处理的是单个对象还是整个组合结构,简化客户端代码.
  3. 组合模式中增加新的容器构件和叶子构件都很方便,无须对现有代码进行修改,符合开闭原则.
  4. 为树形结构的面向对象实现提供了一种灵活的解决方案,通过叶子对象和容器对象的递归组合可以形成复杂的树形结构,但对树形结构的控制却非常简单.

缺点

增加新构件时很难对容器中的构件类型进行限制,有时希望一个容器只能包含某些特定类型的对象,使用组合模式不能依赖类型系统来施加约束,因为都来自相同的抽象层.

适用环境

  1. 具有整体和部分的层次结构中,客户端可一致对待整体和部分.
  2. 使用面向对象语言开发的系统需要处理一个树形结构.
  3. 一个系统中能够分离出叶子对象和容器对象,而且类型不固定,需要增加一些新的类型.

装饰模式

装饰模式可以在不改变一个对象本身的功能的基础上给对象增加额外的新行为.装饰模式是一种用于替代继承的技术,能够给对象动态增加职能,使用对象间的关联关系取代类之间的继承关系.

结构

抽象构件(Component): 是具体构件和抽象装饰类的共同父类,声明了在具体构件中要实现的业务方法.可使客户端一致对待未装饰和已装饰的对象.

具体构件(ConcreteComponent): 是抽象构件的子类,实现了抽象构件中声明的方法,装饰类可以给它增加新的职责.

抽象装饰类(Decorator): 是抽象构件的子类,用于给具体构件增加职责,具体职责在其子类中实现.维护一个指向抽象构件的引用,通过该引用可以调用具体构件的方法.

具体装饰类(ConcreteDecorator): 是抽象装饰类的子类,负责向构件添加新的职责,定义了新的行为,可以调用在抽象装饰类中定义的方法.

应用实例

某软件公司开发了一套图形界面构件库,构件库提供了大量的基本构件如窗体(Window),文本框(TextBox),列表框(ListBox).由于使用构件库时,用户经常要求定制一些特殊的显示效果,如带滚动条的窗体,带黑色边框的文本框,既带滚动条又带黑色边框的列表框等.

/
 * 抽象构件类
 *
 * @author lzlg
 * 2023/2/27 19:14
 */
public abstract class Component {
    /
     * 显示
     */
    public abstract void display();
}

/
 * 窗体具体构件
 *
 * @author lzlg
 * 2023/2/27 19:16
 */
public class Window extends Component {
    /
     * 显示
     */
    @Override
    public void display() {
        System.out.println("显示窗体.");
    }
}

/
 * 文本框具体构件
 *
 * @author lzlg
 * 2023/2/27 19:16
 */
public class TextBox extends Component {
    /
     * 显示
     */
    @Override
    public void display() {
        System.out.println("显示文本框.");
    }
}

/
 * 列表框具体构件
 *
 * @author lzlg
 * 2023/2/27 19:16
 */
public class ListBox extends Component {
    /
     * 显示
     */
    @Override
    public void display() {
        System.out.println("显示列表框.");
    }
}

/
 * 抽象装饰类
 *
 * @author lzlg
 * 2023/2/27 19:17
 */
public abstract class Decorator extends Component {

    private final Component component;

    public Decorator(Component component) {
        this.component = component;
    }

    /
     * 显示
     */
    @Override
    public void display() {
        component.display();
    }
}

/
 * 黑边框(具体装饰类)
 *
 * @author lzlg
 * 2023/2/27 19:19
 */
public class BlackBorderDecorator extends Decorator {
    public BlackBorderDecorator(Component component) {
        super(component);
    }

    /
     * 显示
     */
    @Override
    public void display() {
        this.setScrollBar();
        super.display();
    }

    /
     * 添加新职责
     */
    private void setScrollBar() {
        System.out.println("添加黑边框.");
    }
}

/
 * 滚动条(具体装饰类)
 *
 * @author lzlg
 * 2023/2/27 19:19
 */
public class ScrollBarDecorator extends Decorator {
    public ScrollBarDecorator(Component component) {
        super(component);
    }

    /
     * 显示
     */
    @Override
    public void display() {
        this.setScrollBar();
        super.display();
    }

    /
     * 添加新职责
     */
    private void setScrollBar() {
        System.out.println("添加滚动条.");
    }
}

/
 * 客户端类
 *
 * @author lzlg
 * 2023/2/27 19:21
 */
public class Client {
    public static void main(String[] args) {
        Component window = new Window();
        Component blackBorder = new BlackBorderDecorator(window);
        blackBorder.display();
        System.out.println("===================");
        Component bar = new ScrollBarDecorator(blackBorder);
        bar.display();
    }
}

透明装饰模式: 要求客户端完全针对抽象编程,要求客户端程序将对象全部声明为抽象构件类型.使用透明装饰模式,可以让客户端透明的使用装饰之前的对象和装饰之后的对象,无须关系之间的区别,还可以对一个已装饰过的对象多次装饰.

半透明装饰模式: 有时需要单独调用新增的业务方法,此时需要使用具体装饰类型来定义装饰后的对象,而具体构件类型仍可使用抽象构件类型来定义.半透明装饰模式给系统带来更多灵活性,设计相对简单.

优缺点

优点

  1. 对于扩展一个对象的功能,装饰模式比继承更加灵活,不会导致类的个数急剧增加.
  2. 可以通过一种动态的方式来扩展一个对象的功能.
  3. 可以对一个对象进行多次装饰,通过使用不同的具体装饰类以及装饰类的排列组合可以创造出很多不同行为的组合,得到功能更强大的对象.
  4. 具体构件类和具体装饰类可独立变化,符合开闭原则.

缺点

  1. 使用装饰模式进行系统设计时将产生很多小对象,这些对象的区别是它们之间的相互连接方式不同,而不是类或属性值不同,大量小对象产生占用更多的系统资源,一定程度上影响程序的性能.
  2. 装饰模式比继承更容易出错,排错更加困难,尤其是多次装饰的对象,需逐级排查.

适用环境

  1. 在不影响其他对象的情况下以动态,透明的方式给单个对象添加职责.
  2. 当不能采用继承方式方式对系统进行扩展,或者采用继承不利于系统扩展或维护时可使用装饰模式.

外观模式

外观模式中,外观类充当了软件系统中的"服务员",为多个业务类的调用提供了一个统一的入口.在外观模式中,外观类将客户端类与子系统的内部复杂性分隔开,使得客户端类只需要与外观类打交道,不需要与子系统内部的很多对象打交道.

结构

外观角色(Facade): 它将所有从客户端发来的请求转发到相应的子系统,传递给相应的子系统对象处理.

子系统角色(SubSystem): 每一个子系统可以不是一个单独的类而是一个类的集合,实现子系统的功能.每一个子系统可以被客户端直接调用,或者被外观角色调用,处理外观类传过来的请求.子系统不知道外观类的存在,外观类仅仅是子系统的另一个客户端而已.

应用实例

某软件公司开发一个可应用于多个软件的文件加密模块,具体流程有3个部分: 读取源文件; 加密; 保存加密后的文件.其中读取文件和保存文件使用流来实现,加密操作通过求模运算实现.三个操作相对独立,实现代码的独立重用,并且让客户端能够轻松使用.

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;

/
 * 文件读取操作类(子系统角色)
 *
 * @author lzlg
 * 2023/2/28 10:51
 */
public class MyFileReader {
    /
     * 读取文件明文内容
     *
     * @param filename 文件名称
     * @return 文件内容
     */
    public String read(String filename) {
        if (filename == null || "".equals(filename)) {
            throw new RuntimeException("要加密的文件地址为空");
        }
        File file = new File(filename);
        if (!file.exists()) {
            throw new RuntimeException("要加密的文件不存在");
        }
        StringBuilder builder = new StringBuilder();
        try (FileReader fileReader = new FileReader(file);
             BufferedReader reader = new BufferedReader(fileReader)) {
            reader.lines().forEach(builder::append);
            String content = builder.toString();
            System.out.println("文件明文内容为:\n" + content);
            return content;
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

import java.nio.charset.StandardCharsets;
import java.util.Base64;

/
 * 加密子系统(子系统角色)
 *
 * @author lzlg
 * 2023/2/28 11:00
 */
public class MyCipher {
    /
     * 加密内容
     *
     * @param content 内容
     * @return 加密后字符串
     */
    public String encrypt(String content) {
        if (content == null || "".equals(content)) {
            throw new RuntimeException("要加密的内容为空");
        }
        System.out.println("数据加密,将明文转换为密文:");
        String encrypt = Base64.getEncoder().encodeToString(content.getBytes(StandardCharsets.UTF_8));
        System.out.println(encrypt);
        return encrypt;
    }
}

import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;

/
 * 文件保存类(子系统角色)
 *
 * @author lzlg
 * 2023/2/28 11:02
 */
public class MyFileWriter {
    /
     * 将加密字符串写入文件中
     *
     * @param encrypt 加密信息
     * @param dest    文件存放地址
     */
    public void write(String encrypt, String dest) {
        if (encrypt == null || "".equals(encrypt)) {
            throw new RuntimeException("密文内容为空");
        }
        if (dest == null || "".equals(dest)) {
            throw new RuntimeException("存放文件地址为空");
        }
        try (FileWriter fileWriter = new FileWriter(dest);
             BufferedWriter writer = new BufferedWriter(fileWriter)) {
            writer.write(encrypt);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

/
 * 加密外观类
 *
 * @author lzlg
 * 2023/2/28 11:11
 */
public class EncryptFacade {

    private final MyFileReader reader;

    private final MyFileWriter writer;

    private final MyCipher cipher;

    public EncryptFacade() {
        this.reader = new MyFileReader();
        this.writer = new MyFileWriter();
        this.cipher = new MyCipher();
    }

    public void fileEncrypt(String srcFile, String destFile) {
        String content = reader.read(srcFile);
        String encrypt = cipher.encrypt(content);
        writer.write(encrypt, destFile);
    }
}

/
 * 客户端类
 *
 * @author lzlg
 * 2023/2/28 11:13
 */
public class Client {
    public static void main(String[] args) {
        EncryptFacade facade = new EncryptFacade();
        facade.fileEncrypt("src.txt", "dest.txt");
    }
}

如果需要增加,删除或更换与外观类交换的子系统类,可以通过引入抽象外观类对系统进行改进.

优缺点

优点

  1. 对客户端屏蔽了子系统,减少了客户端所处理的对象数目,使子系统用起来更加容易.
  2. 实现了子系统与客户端之间的松耦合关系,子系统的变化不会影响到调用它的客户端.
  3. 一个子系统的修改对其他子系统没有任何影响,子系统内部变化也不会影响到外观对象.

缺点

  1. 不能很好的限制客户端直接使用子系统类,如果对客户端访问子系统类做太多限制则减少了可变性和灵活性.
  2. 如果设计不当,增加新的子系统可能需要修改外观类的源代码,违背开闭原则.

适用环境

  1. 当要为访问一系列复杂的子系统提供一个简单入口时可使用外观模式.
  2. 客户端与多个子系统之间存在很大依赖性,引入外观类可以将子系统和客户端解耦,提高子系统的独立性和可移植性.
  3. 在层次结构中可以使用外观模式定义系统中每一层的入口,层与层之间不直接产生联系,而通过外观类建立联系,降低层之间的耦合度.

享元模式

享元模式通过共享技术实现相同或相似对象的重用,存储共享实例对象的地方称为享元池.使用享元模式需要区分内部状态外部状态.内部状态是存储在享元对象内部并且不会随环境改变而改变的状态,内部状态可以共享.外部状态是随环境改变而改变的,不可以共享的状态,通常由客户端保存,并在享元对象创建后需要使用时传入到享元对象内部.享元模式要求能够被共享的对象必须是细粒度对象.

结构

抽象享元类(Flyweight): 通常是接口或抽象类,声明了具体享元类公共的方法,这些方法向外界提供享元对象的内部数据,也可以通过这些方法设置外部数据.

具体享元类(ConcreteFlyweight): 实现了抽象享元类,在具体享元类类中为内部状态提供了存储空间,通常配合单例模式来设计具体享元类.

非共享具体享元类(UnsharedConcreteFlyweight): 并不是所有的抽象享元类的子类都需要被共享,不能被共享的子类设计为非共享具体享元类.需要一个非共享具体享元类的对象时直接通过实例化创建.

享元工厂类(FlyweightFactory): 用于创建并管理享元对象,针对抽象享元类进行编程,将各种类型的具体享元对象存储在一个享元池中.

应用实例

使用享元模式设计围棋软件的棋子对象,棋子形状,大小一样,只是出现的位置不同,且分为黑子和白子.

/
 * 围棋棋子类(抽象享元类)
 *
 * @author lzlg
 * 2023/2/28 12:03
 */
public abstract class IgoChessman {

    public abstract Color getColor();

    public void display() {
        System.out.println("棋子颜色:" + getColor());
    }

    public enum Color {
        BLACK("黑色"), WHITE("白色");

        private final String color;

        Color(String color) {
            this.color = color;
        }

        @Override
        public String toString() {
            return this.color;
        }
    }
}

/
 * 黑子(具体享元类)
 *
 * @author lzlg
 * 2023/2/28 12:05
 */
public class BlackIgoChessman extends IgoChessman {

    @Override
    public Color getColor() {
        return Color.BLACK;
    }
}

/
 * 白子(具体享元类)
 *
 * @author lzlg
 * 2023/2/28 12:05
 */
public class WhiteIgoChessman extends IgoChessman {

    @Override
    public Color getColor() {
        return Color.WHITE;
    }
}

import java.util.HashMap;
import java.util.Map;

/
 * 围棋工厂类
 *
 * @author lzlg
 * 2023/2/28 12:06
 */
public class IgoChessmanFactory {

    private final Map<IgoChessman.Color, IgoChessman> pool;

    private IgoChessmanFactory() {
        this.pool = new HashMap<>(2);
        this.pool.put(IgoChessman.Color.BLACK, new BlackIgoChessman());
        this.pool.put(IgoChessman.Color.WHITE, new WhiteIgoChessman());
    }

    private static final IgoChessmanFactory FACTORY = new IgoChessmanFactory();

    public static IgoChessmanFactory instance() {
        return FACTORY;
    }

    public IgoChessman getIgoChessman(IgoChessman.Color color) {
        return this.pool.get(color);
    }
}

/
 * @author lzlg
 * 2023/2/28 12:14
 */
public class Client {
    public static void main(String[] args) {
        IgoChessman white1 = IgoChessmanFactory.instance().getIgoChessman(IgoChessman.Color.WHITE);
        IgoChessman white2 = IgoChessmanFactory.instance().getIgoChessman(IgoChessman.Color.WHITE);
        white1.display();
        white2.display();
        System.out.println("两个白子是否相同? " + (white1 == white2));
        System.out.println("========================================");
        IgoChessman black1 = IgoChessmanFactory.instance().getIgoChessman(IgoChessman.Color.BLACK);
        IgoChessman black2 = IgoChessmanFactory.instance().getIgoChessman(IgoChessman.Color.BLACK);
        black1.display();
        black2.display();
        System.out.println("两个黑子是否相同? " + (black1 == black2));
    }
}

引入外部状态的享元模式:

/
 * 围棋坐标
 *
 * @author lzlg
 * 2023/2/28 12:23
 */
public class Coordinate {

    private final int x;

    private final int y;

    public Coordinate(int x, int y) {
        this.x = x;
        this.y = y;
    }

    public int getX() {
        return x;
    }

    public int getY() {
        return y;
    }

    @Override
    public String toString() {
        return this.x + ", " + this.y;
    }
}

/
 * 围棋棋子类(抽象享元类)
 *
 * @author lzlg
 * 2023/2/28 12:03
 */
public abstract class IgoChessman {

    public abstract Color getColor();

    public void display() {
        System.out.println("棋子颜色:" + getColor());
    }

    public void displayWithCoordinate(Coordinate coordinate) {
        System.out.println("棋子颜色:" + getColor() + ",棋子位置:" + coordinate);
    }

    public enum Color {
        BLACK("黑色"), WHITE("白色");

        private final String color;

        Color(String color) {
            this.color = color;
        }

        @Override
        public String toString() {
            return this.color;
        }
    }
}

/
 * @author lzlg
 * 2023/2/28 12:14
 */
public class Client {
    public static void main(String[] args) {
        IgoChessman white1 = IgoChessmanFactory.instance().getIgoChessman(IgoChessman.Color.WHITE);
        IgoChessman white2 = IgoChessmanFactory.instance().getIgoChessman(IgoChessman.Color.WHITE);
        white1.display();
        white2.display();
        System.out.println("两个白子是否相同? " + (white1 == white2));
        System.out.println("========================================");
        IgoChessman black1 = IgoChessmanFactory.instance().getIgoChessman(IgoChessman.Color.BLACK);
        IgoChessman black2 = IgoChessmanFactory.instance().getIgoChessman(IgoChessman.Color.BLACK);
        black1.display();
        black2.display();
        System.out.println("两个黑子是否相同? " + (black1 == black2));
        System.out.println("========================================");
        white1.displayWithCoordinate(new Coordinate(1, 2));
        white2.displayWithCoordinate(new Coordinate(0, 3));
        black1.displayWithCoordinate(new Coordinate(2, 4));
        black2.displayWithCoordinate(new Coordinate(5, 9));
    }
}

单纯享元模式: 所有的具体享元类都是可以共享的,不存在非共享具体享元类.

复合享元模式: 通过使用组合模式可以形成复合享元对象,这样的复合享元对象不能共享,但是可以分解成单纯享元对象.复合享元模式可以让复合享元对象中的单纯享元对象都具有相同的外部状态,而这些单纯享元对象的内部状态可以不同.

优缺点

优点

  1. 享元模式可以减少内存中对象的数量,节约系统资源,提高系统性能.
  2. 享元模式的外部状态相对独立,不会影响其内部状态,享元对象可以在不同的环境中被共享.

缺点

  1. 享元模式使系统变得复杂,需要分离出内部状态和外部状态,使得程序的逻辑复杂化.
  2. 为使对象可以共享,享元模式需要将享元对象的部分状态外部化,而读取外部状态将使运行时间变长.

适用环境

  1. 一个系统中由大量相同或者相似的对象,造成内存的大量耗费.
  2. 对象的大部分状态可以外部化,可将这些外部状态传入对象中.
  3. 使用享元模式需要维护一个存储享元对象的享元池,会耗费一定的系统资源,应当需要多次重复使用享元对象使才使用享元模式.

代理模式

由于某些原因,客户端不想或者不能直接访问一个对象,此时可以通过一个称为代理的第三者实现间接访问,称为代理模式.常见的代理模式有远程代理,保护代理,虚拟代理,缓冲代理等.

结构

抽象主题角色(AbstractSubject): 声明了真实主题和代理主题的共同接口,客户端需要针对抽象主题角色编程.

代理主题角色(Proxy): 包含了对真实主题的引用,可随时擦配置真实主题对象;代理主题角色和真实主题角色有相同的接口,都实现了抽象主题角色,代理主题角色可在任何时候替代真实主题;代理主题角色还可控制对真实主题角色的使用,还可在需要的时候创建和删除真实主题对象.

真实主题角色(RealSubject): 实现了抽象主题角色的接口,客户端可通过代理主题角色间接调用真实主题角色中的操作.

应用实例

开发人员已完成了商务信息查询模块的开发功能,希望能以一种松耦合的方式向原有系统增加身份验证和日志记录功能,客户端代码可以无区别的对待原始的商务信息查询模块和增加新功能后的商务信息查询模块,而且可能还会在将来增加新的功能.

/
 * 商务查询接口(抽象主题角色)
 *
 * @author lzlg
 * 2023/3/2 15:04
 */
public interface Searcher {
    /
     * 查询商务信息
     *
     * @param userId  用户编号
     * @param keyword 查询关键字
     * @return 结果
     */
    String search(String userId, String keyword);
}

/
 * 商务查询实现(真实主题角色)
 *
 * @author lzlg
 * 2023/3/2 15:07
 */
public class SearcherImpl implements Searcher {
    /
     * 查询商务信息
     *
     * @param userId  用户编号
     * @param keyword 查询关键字
     * @return 结果
     */
    @Override
    public String search(String userId, String keyword) {
        System.out.println("用户:" + userId + "使用关键字:" + keyword + "查询商务信息.");
        return "商务信息";
    }
}

import java.util.Arrays;
import java.util.List;

/
 * 身份验证类
 *
 * @author lzlg
 * 2023/3/2 15:08
 */
public class AccessValidator {

    private static final List<String> userIds = Arrays.asList("杨过", "郭靖");

    /
     * 验证用户登录
     *
     * @param userId 用户编号
     * @return 结果
     */
    public boolean validate(String userId) {
        System.out.println("在数据库中验证用户:" + userId + "是否为合法用户?");
        if (userIds.contains(userId)) {
            System.out.println("用户:" + userId + "登录成功.");
            return true;
        } else {
            System.out.println("用户:" + userId + "登录失败.");
            return false;
        }
    }
}

/
 * 日志记录类
 *
 * @author lzlg
 * 2023/3/2 15:12
 */
public class Logger {
    /
     * 记录用户查询日志
     *
     * @param userId 用户编号
     */
    public void log(String userId) {
        System.out.println("记录用户:" + userId + "查询商户信息.");
    }
}

/
 * 代理查询实现
 *
 * @author lzlg
 * 2023/3/2 15:13
 */
public class ProxySearcher implements Searcher {

    private final Searcher searcher;

    private final AccessValidator validator;

    private final Logger logger;

    public ProxySearcher() {
        this.searcher = new SearcherImpl();
        this.validator = new AccessValidator();
        this.logger = new Logger();
    }

    /
     * 查询商务信息
     *
     * @param userId  用户编号
     * @param keyword 查询关键字
     * @return 结果
     */
    @Override
    public String search(String userId, String keyword) {
        if (!this.validator.validate(userId)) {
            throw new RuntimeException("用户:" + userId + "无权限查询商务信息");
        }
        // 查询商务信息
        String result = this.searcher.search(userId, keyword);
        // 记录日志
        this.logger.log(userId);
        return result;
    }
}

/
 * 客户端
 *
 * @author lzlg
 * 2023/3/2 15:18
 */
public class Client {
    public static void main(String[] args) {
        Searcher searcher = new ProxySearcher();
        searcher.search("杨过", "黯然销魂掌");
    }
}

远程代理: 客户端可以访问在远程主机上的对象,远程代理将网络的细节隐藏起来,客户端不必考虑网络的存在.

在Java语言中,可以通过RMI(Remote Method Invocation)机制来实现远程代理,能够实现一个java虚拟机中的对象调用另一个java虚拟机中对象的方法.在RMI中,客户端对象可通过一个桩(Stub)对象与远程主机上的业务对象进行通信,由于桩对象和远程业务对象接口一致,因此对于客户端而言操作远程对象和本地对象没有区别,桩对象就是远程业务对象在本地主机的代理对象.

在RMI实现的过程中,远程主机端有一个Skeleton(骨架)对象来负责与Stub对象的通信,基本步骤:

  1. 客户端发起请求,将请求转交至RMI客户端的Stub类.
  2. Stub类将请求的接口,方法,参数等信息进行序列化.
  3. 将序列化后的流使用Socket传输至服务器端.
  4. 服务器端接收到流后将其转发至相应的Skeleton类.
  5. Skeleton类将请求信息反序列化后调用实际的业务处理类.
  6. 业务处理类处理完毕后将结果返回给Skeleton类.
  7. Skeleton类将结果序列化,再次通过Socket将流传送给客户端的Stub.
  8. Stub接收到流后进行反序列化,将反序列化后得到的java对象返回给客户端调用者.
import java.rmi.Remote;
import java.rmi.RemoteException;

/
 * 远程服务接口,需继承Remote接口,所有方法都得抛出RemoteException
 *
 * @author lzlg
 * 2023/3/2 15:50
 */
public interface HelloService extends Remote {
    /
     * 远程接口
     */
    String say(String name) throws RemoteException;
}

import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;

/
 * 远程服务接口实现类,需继承UnicastRemoteObject
 *
 * @author lzlg
 * 2023/3/2 15:50
 */
public class HelloServiceImpl extends UnicastRemoteObject implements HelloService {

    protected HelloServiceImpl() throws RemoteException {
        super();
    }

    /
     * 远程接口
     */
    @Override
    public String say(String name) throws RemoteException {
        return "[Registry] 你好, " + name;
    }
}

import java.rmi.AlreadyBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;

/
 * 服务端
 *
 * @author lzlg
 * 2023/3/2 15:51
 */
public class RegistryServer {
    public static void main(String[] args) {
        try {
            Registry registry = LocateRegistry.createRegistry(10010);
            HelloService service = new HelloServiceImpl();
            registry.bind("helloService", service);
            System.out.println("启动RMI服务成功.");
        } catch (RemoteException | AlreadyBoundException e) {
            throw new RuntimeException(e);
        }
    }
}

import java.rmi.NotBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;

/
 * 客户端
 *
 * @author lzlg
 * 2023/3/2 15:55
 */
public class RegistryClient {
    public static void main(String[] args) {
        try {
            Registry registry = LocateRegistry.getRegistry(10010);
            HelloService service = (HelloService) registry.lookup("helloService");
            String response = service.say("李哲龙");
            System.out.println("客户端接收到响应信息:" + response);
        } catch (RemoteException | NotBoundException e) {
            throw new RuntimeException(e);
        }
    }
}

虚拟代理: 对一些占用资源较多或者加载时间较长的对象,可提供一个虚拟代理,在真实对象创建成功之前虚拟代理扮演真实对象的替身,而当真实对象创建之后虚拟代理将用户的请求转发给真实对象.

通常以下两者情况可以考虑使用虚拟代理:

  1. 由于对象本身的复杂性或者网络等原因导致一个对象需要较长的加载时间,此时可以用一个加载时间相对较短的代理对象来代表真实对象.通常实时时,一个线程用于显示代理对象,其他线程用于加载真实对象.在程序启动时可用代理对象替代真实对象,加速了系统的启动时间,可缩短用户的等待时间.
  2. 当一个对象的加载十分耗费系统资源的时候也适合使用虚拟代理.虚拟代理可以让那些占用大量内存或处理起来非常复杂的对象推迟到使用它们的时候才创建.

Java动态代理: 动态代理可以让系统在运行时根据实际需要来动态创建代理类,让同一个代理类能够代理多个不同的真实主题类而且可以代理不同的方法.

Proxy类提供了用于创建动态代理类和实例对象的方法,是所创建的动态代理类的父类.常用方法:

  1. public static Class<?> getProxyClass(ClassLoader loader, Class<?>... interfaces) 方法用于返回一个Class类型的代理类,参数中需要提供类加载器并需要指定代理的接口数组.
  2. public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHander ih) 方法返回一个动态创建的代理类的实例,loader是类加载器,interfaces是代理类实现的接口数组,InvocationHander是所指派的调用处理程序类.

InvocationHandler接口是代理处理程序类的实现接口,该接口作为代理实例的调用处理者的公共父类,每一个代理类的实例都可以提供一个相关的具体调用处理者(接口的子类),接口方法:

public Object invoke(Object proxy, Method method, Object[] args) proxy是代理类的实例,method是需要代理的方法,args是代理方法的参数数组.

/
 * 要代理的接口
 *
 * @author lzlg
 * 2023/3/2 16:20
 */
public interface UserMapper {
    /
     * 是否存在用户
     *
     * @param userId 用户编号
     * @return 结果
     */
    boolean existUser(String userId);
}

/
 * @author lzlg
 * 2023/3/2 16:30
 */
public class UserMapperImpl implements UserMapper {
    /
     * 是否存在用户
     *
     * @param userId 用户编号
     * @return 结果
     */
    @Override
    public boolean existUser(String userId) {
        System.out.println("查询用户:" + userId + "的用户信息成功.");
        return true;
    }
}

/
 * 代理处理类
 *
 * @author lzlg
 * 2023/3/2 16:24
 */
public class ProxyHandler implements InvocationHandler {

    private UserMapper mapper;

    public ProxyHandler() {
    }

    public ProxyHandler(UserMapper mapper) {
        this.mapper = mapper;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        this.invokeTime();
        Object result = method.invoke(mapper, args);
        this.afterInvokeTime();
        return result;
    }

    private void invokeTime() {
        System.out.println(LocalDateTime.now());
    }

    private void afterInvokeTime() {
        System.out.println(LocalDateTime.now());
    }
}

import java.lang.reflect.Proxy;

/
 * @author lzlg
 * 2023/3/2 16:27
 */
public class TestJdkProxy {
    public static void main(String[] args) {
        ProxyHandler handler = new ProxyHandler(new UserMapperImpl());
        UserMapper mapper = (UserMapper) Proxy.newProxyInstance(UserMapper.class.getClassLoader(),
                new Class[]{UserMapper.class}, handler);
        boolean exist = mapper.existUser("李哲龙");
        System.out.println(exist);
    }
}

优缺点

优点

  1. 能够协调调用者和被调用者,在一定程度上降低了系统的耦合度.
  2. 客户端可以针对抽象主题角色进行编程,增加和更换代理类无须修改源代码,符合开闭原则,具有灵活性和扩展性.
  3. 远程代理为位于两个不同地址空间的对象访问提供了一种实现机制.
  4. 虚拟代理通过一个消耗资源少的对象代表一个消耗资源多的对象,在一定程度上节省系统的运行开销.
  5. 缓冲代理为某一个操作的结果提供临时的缓存存储空间,以便以后在后续使用中能够共享结果,优化系统性能,缩短执行时间.
  6. 保护代理可以控制对一个对象的访问权限,为不同用户提供不同级别的使用权限.

缺点

  1. 由于在客户端与真实主题之间增加了代理对象,有些类型的代理模式可能会造成请求的处理速度变慢.
  2. 实现代理模式需要额外的工作,代理模式实现较为复杂.

适用环境

  1. 当客户端对象需要访问远程主机中的对象时可以使用远程代理.
  2. 当需要一个消耗资源少的对象来代表一个消耗资源多的对象,从而降低系统开销,缩短运行时间时可使用虚拟代理.
  3. 当需要为某一个被频繁访问的操作结果提供一个临时存储空间,以供多个客户端共享访问这些结果时可以使用缓冲代理.
  4. 当需要控制对一个对象的访问为不同用户提供不同级别的访问权限时可使用保护代理.
  5. 当需要为一个对象的访问提供一些额外的操作时可使用智能引用代理.

职责链模式

职责链模式中链上的每个对象都是请求处理者,职责链模式可以将请求的处理者组成一条链,并让请求沿着链传播,由链上的处理者对请求进行相应的处理,客户端无须关心请求的处理细节以及请求的传递过程,只需将请求发送到链上.

结构

抽象处理者(Handler): 定义了处理请求的接口,引用了自身类型作为下个处理者,通过该引用连成一条链.

具体处理者(ConcreteHandler): 是抽象处理者的子类,实现了抽象处理者定义的请求处理方法;处理请求之前进行判断,是否有相应的处理权限,如果可以处理请求就进行处理,否则将请求转发给后继者(定义在抽象处理者的引用).

应用实例

某企业的采购审批根据采购金额的不同由不同层次的主管人员来审批,主任可以审批5万元以下的,副董事长可以审批5万元至10万元,董事长可审批10万元至50万元,50万元及以上的需要董事会讨论决定.

import java.math.BigDecimal;
import java.util.Objects;
import java.util.UUID;

/
 * 采购单请求类
 *
 * @author lzlg
 * 2023/3/4 14:26
 */
public class PurchaseRequest {
    // 采购金额
    private BigDecimal amount;
    // 采购单号
    private String no;
    // 采购目的
    private String purpose;

    public PurchaseRequest(BigDecimal amount, String purpose) {
        this(UUID.randomUUID().toString(), amount, purpose);
    }

    public PurchaseRequest(String no, BigDecimal amount, String purpose) {
        Objects.requireNonNull(no);
        Objects.requireNonNull(amount);
        if (BigDecimal.ZERO.compareTo(amount) > 0) {
            throw new RuntimeException("采购单金额不能小于0");
        }
        this.no = no;
        this.amount = amount;
        this.purpose = purpose;
    }

    public BigDecimal getAmount() {
        return amount;
    }

    public void setAmount(BigDecimal amount) {
        this.amount = amount;
    }

    public String getNo() {
        return no;
    }

    public void setNo(String no) {
        this.no = no;
    }

    public String getPurpose() {
        return purpose;
    }

    public void setPurpose(String purpose) {
        this.purpose = purpose;
    }

    @Override
    public String toString() {
        return "采购单{" +
                "采购金额=" + amount +
                ", 采购单号='" + no + '\'' +
                ", 采购目的='" + purpose + '\'' +
                '}';
    }
}

import java.math.BigDecimal;

/
 * 审批者(抽象处理者)
 *
 * @author lzlg
 * 2023/3/4 14:29
 */
public abstract class Approver {
    // 下个审批者
    protected Approver next;
    // 审批者名称
    protected String name;

    public Approver(String name) {
        this.name = name;
    }

    public void setNextHandler(Approver next) {
        this.next = next;
    }

    /
     * 处理采购单请求
     *
     * @param request 采购单请求
     */
    public abstract void processRequest(PurchaseRequest request);

    /
     * 是否能够处理采购单
     *
     * @param amount 采购单金额
     * @return 结果
     */
    public abstract boolean canHandle(BigDecimal amount);

    /
     * 是否有下一个处理者
     *
     * @return 结果
     */
    public boolean hadNextHandler() {
        return this.next != null;
    }
}

import java.math.BigDecimal;

/
 * 主任(具体处理者)
 *
 * @author lzlg
 * 2023/3/4 14:32
 */
public class Director extends Approver {

    public static final BigDecimal MAX_AMOUNT = new BigDecimal(5_0000);

    public Director(String name) {
        super(name);
    }

    /
     * 处理采购单请求
     *
     * @param request 采购单请求
     */
    @Override
    public void processRequest(PurchaseRequest request) {
        if (this.canHandle(request.getAmount())) {
            System.out.println("主任:" + name + ",审批采购单:" + request);
        } else {
            if (this.hadNextHandler()) {
                this.next.processRequest(request);
            } else {
                throw new RuntimeException("采购单无人处理");
            }
        }
    }

    /
     * 是否能够处理采购单
     *
     * @param amount 采购单金额
     * @return 结果
     */
    @Override
    public boolean canHandle(BigDecimal amount) {
        return amount.compareTo(MAX_AMOUNT) < 0;
    }
}

import java.math.BigDecimal;

/
 * 副董事(具体处理类)
 *
 * @author lzlg
 * 2023/3/4 14:36
 */
public class VicePresident extends Approver {

    public static final BigDecimal MAX_AMOUNT = new BigDecimal(10_0000);

    public VicePresident(String name) {
        super(name);
    }

    /
     * 处理采购单请求
     *
     * @param request 采购单请求
     */
    @Override
    public void processRequest(PurchaseRequest request) {
        if (this.canHandle(request.getAmount())) {
            System.out.println("副董事长:" + name + ",审批采购单:" + request);
        } else {
            if (this.hadNextHandler()) {
                this.next.processRequest(request);
            } else {
                throw new RuntimeException("采购单无人处理");
            }
        }
    }

    /
     * 是否能够处理采购单
     *
     * @param amount 采购单金额
     * @return 结果
     */
    @Override
    public boolean canHandle(BigDecimal amount) {
        return amount.compareTo(MAX_AMOUNT) < 0 && amount.compareTo(Director.MAX_AMOUNT) >= 0;
    }
}

import java.math.BigDecimal;

/
 * 董事长(具体处理类)
 *
 * @author lzlg
 * 2023/3/4 14:36
 */
public class President extends Approver {

    public static final BigDecimal MAX_AMOUNT = new BigDecimal(50_0000);

    public President(String name) {
        super(name);
    }

    /
     * 处理采购单请求
     *
     * @param request 采购单请求
     */
    @Override
    public void processRequest(PurchaseRequest request) {
        if (this.canHandle(request.getAmount())) {
            System.out.println("董事长:" + name + ",审批采购单:" + request);
        } else {
            if (this.hadNextHandler()) {
                this.next.processRequest(request);
            } else {
                throw new RuntimeException("采购单无人处理");
            }
        }
    }

    /
     * 是否能够处理采购单
     *
     * @param amount 采购单金额
     * @return 结果
     */
    @Override
    public boolean canHandle(BigDecimal amount) {
        return amount.compareTo(MAX_AMOUNT) < 0 && amount.compareTo(VicePresident.MAX_AMOUNT) >= 0;
    }
}

import java.math.BigDecimal;

/
 * 董事会(具体处理类)
 *
 * @author lzlg
 * 2023/3/4 14:36
 */
public class Congress extends Approver {

    public Congress(String name) {
        super(name);
    }

    /
     * 处理采购单请求
     *
     * @param request 采购单请求
     */
    @Override
    public void processRequest(PurchaseRequest request) {
        if (this.canHandle(request.getAmount())) {
            System.out.println("召开董事会审批采购单:" + request);
        } else {
            throw new RuntimeException("采购单无人处理");
        }
    }

    /
     * 是否能够处理采购单
     *
     * @param amount 采购单金额
     * @return 结果
     */
    @Override
    public boolean canHandle(BigDecimal amount) {
        return amount.compareTo(President.MAX_AMOUNT) >= 0;
    }
}

import java.math.BigDecimal;

/
 * 客户端
 *
 * @author lzlg
 * 2023/3/4 14:58
 */
public class Client {
    public static void main(String[] args) {
        Approver director = new Director("小李");
        Approver vicePresident = new VicePresident("小张");
        Approver president = new President("小王");
        Approver congress = new Congress("董事会");
        director.setNextHandler(vicePresident);
        vicePresident.setNextHandler(president);
        president.setNextHandler(congress);

        PurchaseRequest request1 = new PurchaseRequest(new BigDecimal(45_000), "购买MacPro");
        director.processRequest(request1);

        PurchaseRequest request2 = new PurchaseRequest(new BigDecimal(55_000), "购买MacMax");
        director.processRequest(request2);

        PurchaseRequest request3 = new PurchaseRequest(new BigDecimal(105_000), "购买MacPro Studio");
        director.processRequest(request3);

        PurchaseRequest request4 = new PurchaseRequest(new BigDecimal(505_000), "购买MacPro Studio Display Plus");
        director.processRequest(request4);
    }
}

纯的职责链模式: 要求一个具体处理者要么承担全部处理请求的职责,要么将责任推给下家,不允许只承担部分责任.

不纯的职责链模式: 允许请求被一个具体处理者部分处理后再向下传递或者一个具体处理者处理完请求后,下家处理者可以继续处理请求,而且一个请求可以最终不被处理.

优缺点

优点

  1. 职责链模式使得一个请求对象无须知道是哪个对象处理该请求,对象只需知道请求会被处理即可.接收者和发送者没有对方的明确信息,并且链中的对象不需要知道链的结构,降低了系统的耦合度.
  2. 请求处理对象仅需维持一个指向下个处理者的引用,不需维护所有的候选处理者,简化了对象之间的连接.
  3. 给对象分派职责时,职责链模式有更多的灵活性,可在运行时对链进行动态的增加和修改.
  4. 系统增加一个新的具体处理者时无须修改原有系统的代码,符合开闭原则.

缺点

  1. 由于一个请求没有明确的接收者,不能保证一定会处理;如果职责链没有正确配置也可造成请求不能得到处理.
  2. 如果职责链比较长,系统性能受到一定的影响,在进行调试时不太方便.
  3. 如果职责链没有正确配置,可能会造成循环调用,陷入死循环.

适用环境

  1. 多个对象可以处理同一个请求,具体哪个对象处理请求在运行时刻确定,客户端只需将请求提交到链上.
  2. 不明确指定接收者的情况下向多个对象中的一个提交一个请求.
  3. 可动态指定一组对象处理请求,客户端可以动态创建职责链处理请求,还可改变链中处理者的先后次序.

命令模式

命令模式中将发送者的请求封装在命令对象中,通过命令对象来调用接收者的方法,请求的发送者和接收者完全解耦.别名为动作(Action)模式或事务(Transaction)模式.

结构

抽象命令类(Command): 声明了执行请求的方法,通过这些方法可以调用请求接收者的相关操作.

具体命令类(ConcreteCommand): 是抽象命令类的子类,实现了抽象命令类中声明的方法,对应具体的请求接收者对象,将接收者对象的动作绑定其中.

调用者(Invoker): 即请求发送者,通过命令对象来执行请求,只与抽象命令类之间存在关联关系.

接收者(Receiver): 即请求接收者,执行与请求相关的操作,实现对请求的业务处理.

应用实例

某系统提供了一系列功能键,用户可自定义功能键的功能,如功能键FunctionButton可用于退出系统(由SystemExistClass实现), 也可用于显示帮助文档(由DisplayHelpClass实现).

/
 * 抽象命令类
 *
 * @author lzlg
 * 2023/3/4 15:34
 */
public abstract class Command {
    /
     * 命令操作
     */
    public abstract void execute();
}

/
 * 退出系统命令(具体命令类)
 *
 * @author lzlg
 * 2023/3/4 15:36
 */
public class ExitCommand extends Command {

    private final SystemExitClass exitClass;

    public ExitCommand() {
        this.exitClass = new SystemExitClass();
    }

    /
     * 命令操作
     */
    @Override
    public void execute() {
        this.exitClass.exit();
    }
}

/
 * 系统退出(请求接收者)
 *
 * @author lzlg
 * 2023/3/4 15:37
 */
public class SystemExitClass {

    public void exit() {
        System.out.println("退出系统.");
    }
}

/
 * 帮助命令(具体命令类)
 *
 * @author lzlg
 * 2023/3/4 15:36
 */
public class HelpCommand extends Command {

    private final DisplayHelpClass helpClass;

    public HelpCommand() {
        this.helpClass = new DisplayHelpClass();
    }

    /
     * 命令操作
     */
    @Override
    public void execute() {
        this.helpClass.display();
    }
}

/
 * 显示帮助类(请求接收者)
 *
 * @author lzlg
 * 2023/3/4 15:37
 */
public class DisplayHelpClass {

    public void display() {
        System.out.println("显示帮助文档.");
    }
}

/
 * 功能键类(请求发送者)
 *
 * @author lzlg
 * 2023/3/4 15:33
 */
public class FunctionButton {
    // 命令
    private Command command;

    public FunctionButton(Command command) {
        this.command = command;
    }

    public void click() {
        System.out.println("点击功能键.");
        command.execute();
    }
}

/
 * 客户端
 *
 * @author lzlg
 * 2023/3/4 15:40
 */
public class Client {
    public static void main(String[] args) {
        Command command = new ExitCommand();
        FunctionButton button = new FunctionButton(command);
        button.click();
        System.out.println("================");
        command = new HelpCommand();
        button = new FunctionButton(command);
        button.click();
    }
}

通过组合多个命令形成宏命令(Macro Command).

通过通过记录命令历史信息,来实现命令的撤销功能.

优缺点

优点

  1. 降低系统的耦合度,请求者和接收者不存在直接引用,两者之间具有良好的独立性.
  2. 可以方便的增加新命令到系统中,无须修改原有代码,符合开闭原则.
  3. 可以方便的设计宏命令,为请求的撤销和恢复提供了设计和实现方案.

缺点

  1. 使用命令模式可能会导致系统有过多的具体命令类,因为每个对请求接收者的调用操作都需要一个具体命令类.

适用环境

  1. 系统需要将请求调用者和请求接收者解耦,使得调用者和接收者不直接交互.
  2. 系统需要在不同的时间指定请求,将请求排队和执行请求,命令对象和请求的初始调用者有不同的生命周期.
  3. 系统需要支持命令的撤销(Undo)操作和恢复(Redo)操作.
  4. 系统需要将一组操作组合在一起形成宏命令.

解释器模式

解释器模式用于描述如何使用面向对象语言构成一个简单的语言解释器.在某些情况下,为了更好地描述某些特定类型的问题可以创建一种新的语言,这种语言拥有自己的表达式和结构(文法规则),这些问题的实例将对应为该语言中的句子,此时可以使用解释器模式来设计这种新的语言.

以表达式为例说明文法规则和抽象语法树, 表达式为: 1+5-4

可以使用下面的文法规则来说明上诉表达式:

expression ::= value | operation
operation ::= expression '+' expression | expression '-' expression
value ::= an integer

第一条表示表达式的组成方式,value和operation是后面两个语言单位的定义,每一条语句所定义的字符串称为语言构造成分或语言单位,符合 ::=定义为的意思.语言单位有终结符表达式(value)和非终结符表达式(operation).

在文法规则定义中可以使用一些符号来表示不同的含义, 比如使用 | 表示或, 使用 { } 表示组合, 使用 * 表示出现0次或多次.

抽象语法树: 将终结符表达式和非终结符表达式通过一种树形结构组合起来.

graph TD A[operation + ] --> B[value 1] A[operation + ] --> C[operation -] C[operation -] --> D[value 5] C[operation -] --> E[value 4]

结构

抽象表达式(AbstractExpression): 声明了抽象的解释操作,是所有终结符表达式和非终结符表达式的公共父类.

终结符表达式(TerminalExpression): 实现了抽象表达式的解释操作,每一个终结符是该类的一个实例.

非终结符表达式(NonterminalExpression): 实现了抽象表达式的解释操作,其中可包含终结符表达式,也可包含非终结符表达式.解释操作一般使用递归的方式完成.

环境类(Context): 又称上下文类,存储解释器之外的一些全局信息,临时存储了需要解释的语句.

应用实例

某公司开发一套机器人控制程序,其中包含一些简单的英文控制指令,每一个指令对应一个表达式,表达式可以是简单表达式,也可以是复合表达式,每一个简单表达式由移动方向,移动方式和移动距离3部分组成.移动方向有上下左右,移动方式有移动和快速移动,移动距离为一个正整数.两个表达式直接可以通过与(and)连接形成复合表达式.

/
 * 抽象节点类(抽象表达式)
 *
 * @author lzlg
 * 2023/3/5 11:25
 */
public abstract class AbstractNode {
    /
     * 解释语言文法
     *
     * @return 描述
     */
    public abstract String interpret();
}

/
 * 方向节点类(终结符表达式)
 *
 * @author lzlg
 * 2023/3/5 11:31
 */
public class DirectionNode extends AbstractNode {
    // 方向
    private final Direction direction;

    public DirectionNode(Direction direction) {
        this.direction = direction;
    }

    /
     * 解释语言文法
     *
     * @return 描述
     */
    @Override
    public String interpret() {
        return "向" + this.direction.desc;
    }

    enum Direction {
        UP("up", "上"), DOWN("down", "下"), LEFT("left", "左"), RIGHT("right", "右");

        private final String direct;

        private final String desc;

        Direction(String direct, String desc) {
            this.direct = direct;
            this.desc = desc;
        }

        /
         * 检查方向是否合法
         *
         * @param direct 方向
         * @return 结果
         */
        public static boolean check(String direct) {
            if (direct == null || "".equals(direct)) {
                return false;
            }
            Direction[] values = values();
            for (Direction value : values) {
                if (value.direct.equals(direct)) {
                    return true;
                }
            }
            return false;
        }

        /
         * 根据字符串返回方向
         *
         * @param direct 方向
         * @return 结果
         */
        public static Direction toDirection(String direct) {
            if (direct == null || "".equals(direct)) {
                throw new IllegalArgumentException("方向字符串为空");
            }
            Direction[] values = values();
            for (Direction value : values) {
                if (value.direct.equals(direct)) {
                    return value;
                }
            }
            throw new RuntimeException("无此方向");
        }
    }
}

/
 * 操作节点类(终结符表达式)
 *
 * @author lzlg
 * 2023/3/5 11:37
 */
public class ActionNode extends AbstractNode {
    // 操作
    private final Action action;

    public ActionNode(Action action) {
        this.action = action;
    }

    /
     * 解释语言文法
     *
     * @return 描述
     */
    @Override
    public String interpret() {
        return action.desc;
    }

    enum Action {
        MOVE("move", "移动"), RUN("run", "快速移动");

        private final String action;

        private final String desc;

        Action(String action, String desc) {
            this.action = action;
            this.desc = desc;
        }

        /
         * 检查操作是否合法
         *
         * @param action 操作
         * @return 结果
         */
        public static boolean check(String action) {
            if (action == null || "".equals(action)) {
                return false;
            }
            Action[] values = values();
            for (Action value : values) {
                if (value.action.equals(action)) {
                    return true;
                }
            }
            return false;
        }

        /
         * 根据字符串返回操作
         *
         * @param action 操作
         * @return 结果
         */
        public static Action toAction(String action) {
            if (action == null || "".equals(action)) {
                throw new IllegalArgumentException("操作字符串为空");
            }
            Action[] values = values();
            for (Action value : values) {
                if (value.action.equals(action)) {
                    return value;
                }
            }
            throw new RuntimeException("无此操作");
        }
    }
}

/
 * 距离节点(终结符表达式)
 *
 * @author lzlg
 * 2023/3/5 11:41
 */
public class DistanceNode extends AbstractNode {
    // 距离数字
    private final int distance;

    public DistanceNode(int distance) {
        if (distance < 0) {
            throw new IllegalArgumentException("移动距离不能小于0");
        }
        this.distance = distance;
    }

    /
     * 解释语言文法
     *
     * @return 描述
     */
    @Override
    public String interpret() {
        return String.valueOf(this.distance);
    }
}

/
 * 简单句子节点类(非终结符表达式)
 *
 * @author lzlg
 * 2023/3/5 11:29
 */
public class SentenceNode extends AbstractNode {
    // 方向
    private final AbstractNode direction;
    // 操作
    private final AbstractNode action;
    // 距离
    private final AbstractNode distance;

    public SentenceNode(AbstractNode direction, AbstractNode action, AbstractNode distance) {
        this.direction = direction;
        this.action = action;
        this.distance = distance;
    }

    /
     * 解释语言文法
     *
     * @return 描述
     */
    @Override
    public String interpret() {
        return this.direction.interpret() + this.action.interpret() + this.distance.interpret();
    }
}

/
 * And节点类(非终结符表达式)
 *
 * @author lzlg
 * 2023/3/5 11:27
 */
public class AndNode extends AbstractNode {

    public static final String AND = "and";
    // and的左表达式
    private final AbstractNode left;
    // and的右表达式
    private final AbstractNode right;

    public AndNode(AbstractNode left, AbstractNode right) {
        this.left = left;
        this.right = right;
    }

    /
     * 解释语言文法
     *
     * @return 描述
     */
    @Override
    public String interpret() {
        return this.left.interpret() + "再" + this.right.interpret();
    }
}

import java.util.Arrays;
import java.util.List;
import java.util.Stack;
import java.util.stream.Collectors;

/
 * 解释器
 *
 * @author lzlg
 * 2023/3/5 11:50
 */
public class InterpretHandler {
    /
     * 检查语句是否符合规范
     *
     * @param expression 语句
     * @return 结果
     */
    public boolean check(String expression) {
        if (expression == null || "".equals(expression)) {
            return false;
        }
        // 如果包含有and,则是多个表达式复合
        if (expression.contains(AndNode.AND)) {
            String[] expressions = expression.split(AndNode.AND);
            for (String exp : expressions) {
                // 如果有一个不合法,则直接返回false
                if (!this.checkSingleExpression(exp)) {
                    return false;
                }
            }
            return true;
        } else {
            return this.checkSingleExpression(expression);
        }
    }

    /
     * 检查单条语句是否合法
     *
     * @param expression 单条语句
     * @return 结果
     */
    private boolean checkSingleExpression(String expression) {
        if (expression == null || "".equals(expression)) {
            return false;
        }
        expression = expression.trim();
        String[] array = expression.split(" ");
        if (array.length != 3) {
            return false;
        }
        // 第一个是方向
        String direct = array[0];
        if (!DirectionNode.Direction.check(direct)) {
            return false;
        }
        // 第二个是操作
        String action = array[1];
        if (!ActionNode.Action.check(action)) {
            return false;
        }
        // 第三个是距离
        String distance = array[2];
        try {
            int dis = Integer.parseInt(distance);
            if (dis < 0) {
                return false;
            }
        } catch (NumberFormatException e) {
            System.out.println(e.getMessage());
            return false;
        }
        return true;
    }

    /
     * 解释语句为节点
     *
     * @param expression 语句
     * @return 节点
     */
    public AbstractNode interpret(String expression) {
        if (!this.check(expression)) {
            throw new IllegalArgumentException("语句不合法");
        }
        // 如果包含有and,则是多个表达式复合
        if (expression.contains(AndNode.AND)) {
            // 把语句分割为单条可解析的
            String[] array = expression.split(AndNode.AND);
            // 解析为语句节点列表
            List<SentenceNode> list = Arrays.stream(array).map(this::toSentenceNode)
                    .collect(Collectors.toList());
            // 使用栈来帮助生成最终的节点
            Stack<AbstractNode> stack = new Stack<>();
            for (SentenceNode node : list) {
                // 如果栈容量为空,则将当前节点入栈
                if (stack.isEmpty()) {
                    stack.push(node);
                } else {
                    // 弹出节点
                    AbstractNode left = stack.pop();
                    // 组合成and节点
                    AndNode andNode = new AndNode(left, node);
                    // 然后入栈
                    stack.push(andNode);
                }
            }
            return stack.pop();
        } else {
            return this.toSentenceNode(expression);
        }
    }

    /
     * 将单条语句转换为句子节点
     *
     * @param expression 语句
     * @return 句子节点
     */
    private SentenceNode toSentenceNode(String expression) {
        String exp = expression.trim();
        String[] array = exp.split(" ");
        DirectionNode direction = new DirectionNode(DirectionNode.Direction.toDirection(array[0]));
        ActionNode action = new ActionNode(ActionNode.Action.toAction(array[1]));
        DistanceNode distance = new DistanceNode(Integer.parseInt(array[2]));
        return new SentenceNode(direction, action, distance);
    }
}

/
 * 客户端类
 *
 * @author lzlg
 * 2023/3/5 15:16
 */
public class Client {
    public static void main(String[] args) {
        String andExpression = "up move 5 and down run 10 and left move 5";
        InterpretHandler handler = new InterpretHandler();
        AbstractNode node = handler.interpret(andExpression);
        System.out.println(node.interpret());
        System.out.println("=====================");
        String expression = "down run 10";
        node = handler.interpret(expression);
        System.out.println(node.interpret());
    }
}

优缺点

优点

  1. 易于改变和扩展文法,使用类表示文法规则,可以通过继承等机制来改变或扩展文法.
  2. 每一条文法规则都可以表示为一个类,可以方便地实现一个简单的语言.
  3. 实现文法比较容易,抽象语法树中每个表达式节点类的实现方式是类似的,编写不会特别复杂.
  4. 增加新的解释表达式较为方便,只需对应增加新的终结符表达式或非终结符表达式,原有代码无须修改,复合开闭原则.

缺点

  1. 对于复杂文法难以维护,如果文法规则太多,会造成类个数增加,导致系统难以管理和维护.
  2. 执行效率较低,解释器模式中使用了大量的循环和递归调用,因此在解释复杂文法语句时速度很慢,过程麻烦.

适用环境

  1. 可以将一个需要解释执行的语言中的句子表示为一棵抽象语法树.
  2. 一些重复出现的问题可以用一种简单的语言进行表达.
  3. 一个语言的文法较为简单.如果文法规则复杂,最好使用语法分析程序生成器.
  4. 不太在在乎执行效率.

迭代器模式

迭代器模式中遍历聚合对象无须了解聚合对象内部的结构,还能方便地增加新的遍历方式.将遍历数据的行为从聚合对象中分离出来,封装在迭代器对象中,这样简化了聚合对象的设计,符合单一职责原则.

结构

抽象迭代器(Iterator): 定义访问和遍历元素的接口,声明了用于遍历数据元素的方法.

具体迭代器(ConcreteIterator): 实现了抽象迭代器接口,完成了对聚合对象的遍历,通过游标记录在聚合对象中所处的位置.

抽象聚合类(Aggregate): 用于存储和管理元素对象,声明一个createIterator()方法创建迭代器对象.

具体聚合类(ConcreteAggregate): 实现了抽象聚合类中声明的createIterator()方法,返回和该具体聚合类相对应的具体迭代器的实例.

应用实例

某销售管理系统,开发人员设计了抽象的数据集合AbstractObjectList,将商品和客户等数据的聚合类作为其子类,AbstractObjectList的职责较重,不仅存储和管理数据,还负责遍历数据.

/
 * 抽象迭代器接口
 *
 * @author lzlg
 * 2023/3/5 16:21
 */
public interface AbstractIterator<T> {
    /
     * 移至下一个元素
     */
    void next();

    /
     * 是否为最后一个元素
     *
     * @return 结果
     */
    boolean isLast();

    /
     * 移至上一个元素
     */
    void previous();

    /
     * 是否为第一个元素
     *
     * @return 结果
     */
    boolean isFirst();

    /
     * 获取下一个元素
     *
     * @return 元素
     */
    T nextItem();

    /
     * 获取上一个元素
     *
     * @return 元素
     */
    T prevItem();
}

/
 * 抽象聚合类
 *
 * @author lzlg
 * 2023/3/5 16:18
 */
public abstract class AbstractObjectList<T> {

    protected final List<T> objects;

    public AbstractObjectList(List<T> objects) {
        this.objects = objects;
    }

    public void addObject(T t) {
        this.objects.add(t);
    }

    public void removeObject(T t) {
        this.objects.remove(t);
    }

    public List<T> getObjects() {
        return this.objects;
    }

    /
     * 创建迭代器
     *
     * @return 迭代器
     */
    public abstract AbstractIterator<T> createIterator();
}

import java.math.BigDecimal;

/
 * 产品类
 *
 * @author lzlg
 * 2023/3/5 16:27
 */
public class Product {

    private String name;

    private BigDecimal amount;

    public Product(String name, BigDecimal amount) {
        this.name = name;
        this.amount = amount;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public BigDecimal getAmount() {
        return amount;
    }

    public void setAmount(BigDecimal amount) {
        this.amount = amount;
    }

    @Override
    public String toString() {
        return "[ 产品名称: " + name + ", 产品价格: " + amount + " ]";
    }
}

import java.util.List;

/
 * 产品迭代器类(具体迭代器)
 *
 * @author lzlg
 * 2023/3/5 16:29
 */
public class ProductIterator implements AbstractIterator<Product> {
    // 产品数据列表
    private final List<Product> list;
    // 正向遍历游标
    private int nextCursor;
    // 逆向遍历的游标
    private int prevCursor;

    public ProductIterator(ProductList list) {
        this.list = list.getObjects();
        this.nextCursor = 0;
        this.prevCursor = this.list.size() - 1;
    }

    /
     * 移至下一个元素
     */
    @Override
    public void next() {
        if (nextCursor < list.size()) {
            nextCursor++;
        }
    }

    /
     * 是否为最后一个元素
     *
     * @return 结果
     */
    @Override
    public boolean isLast() {
        return nextCursor == list.size();
    }

    /
     * 移至上一个元素
     */
    @Override
    public void previous() {
        if (prevCursor > -1) {
            prevCursor--;
        }
    }

    /
     * 是否为第一个元素
     *
     * @return 结果
     */
    @Override
    public boolean isFirst() {
        return prevCursor == -1;
    }

    /
     * 获取下一个元素
     *
     * @return 元素
     */
    @Override
    public Product nextItem() {
        return list.get(nextCursor);
    }

    /
     * 获取上一个元素
     *
     * @return 元素
     */
    @Override
    public Product prevItem() {
        return list.get(prevCursor);
    }
}

import java.util.List;

/
 * @author lzlg
 * 2023/3/5 16:25
 */
public class ProductList extends AbstractObjectList<Product> {
    public ProductList(List<Product> products) {
        super(products);
    }

    /
     * 创建迭代器
     *
     * @return 迭代器
     */
    @Override
    public AbstractIterator<Product> createIterator() {
        return new ProductIterator(this);
    }
}

import java.math.BigDecimal;
import java.util.Arrays;
import java.util.List;

/
 * 客户端类
 *
 * @author lzlg
 * 2023/3/5 16:35
 */
public class Client {
    public static void main(String[] args) {
        List<Product> products = Arrays.asList(
                new Product("iPhone5s", new BigDecimal("1999.9")),
                new Product("iPhoneSE3", new BigDecimal("2999.9")),
                new Product("iPhone14ProMax", new BigDecimal("6999.9"))
        );

        AbstractObjectList<Product> list = new ProductList(products);
        AbstractIterator<Product> iterator = list.createIterator();
        System.out.println("正向遍历:");
        while (!iterator.isLast()) {
            System.out.print(iterator.nextItem() + "\t");
            iterator.next();
        }
        System.out.println("\n============================");
        System.out.println("逆向遍历:");
        while (!iterator.isFirst()) {
            System.out.print(iterator.prevItem() + "\t");
            iterator.previous();
        }
    }
}

可将产品迭代器封装在产品聚合类中,即迭代器作为聚合类的内部类.

优缺点

优点

  1. 迭代器模式支持以不同的方式遍历一个聚合对象,替换不同的迭代器实现可改变遍历算法.
  2. 迭代器模式简化了聚合类,聚合类不再提供数据遍历等方法.
  3. 迭代器模式引入了抽象层,增加新的聚合类和迭代器类都很方便,无须修改原有代码,符合开闭原则.

缺点

  1. 增加新的聚合类时需对应增加新的迭代器类,类的个数成对增加,一定程度上增加了系统的复杂性.
  2. 抽象迭代器的设计难度较大,需要充分考虑到系统将来的扩展.

适用环境

  1. 访问一个聚合对象的内容无须暴露它的内部表示,将聚合对象的访问和内部数据的存储分离.
  2. 需要为一个聚合对象提供多种遍历方式.
  3. 为遍历不同的聚合结构提供一个统一的接口,客户端可一致性地操作接口.

中介者模式

中介者模式可以将系统中对象之间的多对多的相互关系,转换为相对简单的一对多关系,这样可降低系统中对象之间的复杂引用关系,可将网状结构变成以中介者为中心的星形结构.

结构

抽象中介者(Mediator): 定义接口用于与各同事对象之间进行通信.

具体中介者(ConcreteMediator): 是抽象中介者的子类,通过协调各个同事对象来实现协作行为,维持了对同事对象的引用.

抽象同事类(Colleague): 定义各个同事类公有的方法,声明需子类实现的抽象方法.维持了一个对抽象中介者的引用,其子类可通过该引用与中介者通信.

具体同事类(ConcreteColleague): 是抽象同事类的子类,实现了抽象方法.每一个同事对象在需要和其他同事对象通信先与中介者通信,通过中介者间接完成与其他同事类的通信.

应用实例

某CRM系统中的客户信息管理窗口中的组件之间存在较为复杂的交互关系,如果删除一个客户,则将从客户列表中删掉对应的项,客户选择组合框中的客户名称也减少一个.如果增加一个客户,则客户列表中将增加一项,客户选择组合框中的客户名称也增加一个.

/
 * 抽象同事类
 *
 * @author lzlg
 * 2023/3/5 17:04
 */
public abstract class Component {

    private Mediator mediator;

    public Component(Mediator mediator) {
        this.mediator = mediator;
    }

    public void setMediator(Mediator mediator) {
        this.mediator = mediator;
    }

    public void changed() {
        if (this.mediator != null) {
            this.mediator.componentChanged(this);
        }
    }

    public abstract void update(String content);
}

/
 * 按钮(具体同事类)
 *
 * @author lzlg
 * 2023/3/5 17:11
 */
public class Button extends Component {

    public Button(Mediator mediator) {
        super(mediator);
    }

    @Override
    public void update(String content) {
        // 不进行响应
    }

    public String getInput() {
        return "张无忌";
    }
}

/
 * 列表框(具体同事类)
 *
 * @author lzlg
 * 2023/3/5 17:12
 */
public class ListBox extends Component {

    public ListBox(Mediator mediator) {
        super(mediator);
    }

    @Override
    public void update(String content) {
        System.out.println("列表框增加: " + content);
    }

    public String select() {
        return "周芷若";
    }

    public void show(String select) {
        System.out.println("列表框选中: " + select);
    }
}

/
 * 组合框(具体同事类)
 *
 * @author lzlg
 * 2023/3/5 17:12
 */
public class ComboBox extends Component {

    public ComboBox(Mediator mediator) {
        super(mediator);
    }

    @Override
    public void update(String content) {
        System.out.println("组合框增加: " + content);
    }

    public String select() {
        return "杨不悔";
    }

    public void show(String content) {
        System.out.println("组合框选中: " + content);
    }
}

/
 * 文本框(具体同事类)
 *
 * @author lzlg
 * 2023/3/5 17:12
 */
public class TextBox extends Component {

    public TextBox(Mediator mediator) {
        super(mediator);
    }

    @Override
    public void update(String content) {
        System.out.println("文本框增加: " + content);
    }

    public void setText(String text) {
        System.out.println("文本框显示: " + text);
    }
}

/
 * 抽象中介者
 *
 * @author lzlg
 * 2023/3/5 17:03
 */
public abstract class Mediator {

    public abstract void componentChanged(Component c);
}

/
 * 具体中介者
 *
 * @author lzlg
 * 2023/3/5 17:16
 */
public class ConcreteMediator extends Mediator {

    private Button button;

    private ListBox listBox;

    private ComboBox comboBox;

    private TextBox textBox;

    public void setComponents(Button button, ListBox listBox, ComboBox comboBox, TextBox textBox) {
        this.button = button;
        this.listBox = listBox;
        this.comboBox = comboBox;
        this.textBox = textBox;
    }

    @Override
    public void componentChanged(Component c) {
        if (c == button) {
            String input = button.getInput();
            System.out.println("=====单击增加按钮=====");
            listBox.update(input);
            comboBox.update(input);
            textBox.update(input);
            return;
        }
        if (c == listBox) {
            String select = listBox.select();
            System.out.println("=====从列表框选择客户=====");
            comboBox.show(select);
            textBox.setText(select);
            return;
        }
        if (c == comboBox) {
            String select = comboBox.select();
            System.out.println("=====从组合框选择客户=====");
            listBox.show(select);
            textBox.setText(select);
        }
    }
}

/
 * 客户端
 *
 * @author lzlg
 * 2023/3/5 17:29
 */
public class Client {
    public static void main(String[] args) {
        ConcreteMediator mediator = new ConcreteMediator();
        Button button = new Button(mediator);
        ListBox listBox = new ListBox(mediator);
        ComboBox comboBox = new ComboBox(mediator);
        TextBox textBox = new TextBox(mediator);
        mediator.setComponents(button, listBox, comboBox, textBox);

        button.changed();
        System.out.println("===========================");
        listBox.changed();
    }
}

优缺点

优点

  1. 中介者模式简化了对象之间的交互,用中介者和同事之间的一对多关系代替了原来同事之间的多对多关系,一对多更容易理解,维护和扩展.
  2. 可将各同事对象解耦,可独立地改变和复用每一个同事和中介者,增加新的同事类和新的中介者类比较方便.
  3. 可以减少子类生成,将原本分布于多个对象间的行为集中,改变这些行为只需生成新的中介者子类.

缺点

具体中介者中包含了大量同事之间的交互细节,可能会导致具体中介者类非常复杂,使得系统难以维护.

适用环境

  1. 系统对象之间存在复杂的引用关系,系统结构混乱且难以理解.
  2. 一个对象由于引用了其他很多对象且之间和这些对象通信,导致难以复用该对象.
  3. 想通过一个中间类封装多个类中的行为,而又不想生成太多子类.

备忘录模式

备忘录模式提供了一种状态恢复的实现机制,使得用户方便地回到一个特定的历史步骤,当新的状态无效或存在问题时可使用暂时存储的备忘录将状态复原,实现撤销操作.

结构

原发器(Originator): 是个普通类,通过创建一个备忘录来存储当前的内部状态,也可以使用备忘录恢复内部状态,一般将系统中需要保存内部状态的类设计为原发器.

备忘录(Memento): 用于存储原发器的内部状态,根据原发器来决定保存那些内部状态.

负责人(Caretaker): 管理者,负责保存备忘录,但不能对备忘录的内容进行操作或检查,只负责存储对象.

应用实例

实现象棋软件中的悔棋功能.

/
 * 象棋棋子类(原发器)
 *
 * @author lzlg
 * 2023/3/7 17:42
 */
public class Chessman {

    private final String name;

    private int x;

    private int y;

    public Chessman(String name, int x, int y) {
        this.name = name;
        this.x = x;
        this.y = y;
    }

    public String getName() {
        return name;
    }

    public int getX() {
        return x;
    }

    public void setX(int x) {
        this.x = x;
    }

    public int getY() {
        return y;
    }

    public void setY(int y) {
        this.y = y;
    }

    /
     * 保存状态
     *
     * @return 备忘录
     */
    public ChessmanMemento save() {
        return new ChessmanMemento(this);
    }

    /
     * 恢复状态
     *
     * @param memento 备忘录
     */
    public void restore(ChessmanMemento memento) {
        this.setX(memento.getX());
        this.setY(memento.getY());
    }

    @Override
    public String toString() {
        return "棋子: " + name + ", 当前位置为: 第" + x + "行,第" + y + "列.";
    }
}

/
 * 象棋棋子备忘录
 *
 * @author lzlg
 * 2023/3/7 17:44
 */
public class ChessmanMemento {

    private String name;

    private int x;

    private int y;

    public ChessmanMemento(Chessman chessman) {
        this(chessman.getName(), chessman.getX(), chessman.getY());
    }

    public ChessmanMemento(String name, int x, int y) {
        this.name = name;
        this.x = x;
        this.y = y;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getX() {
        return x;
    }

    public void setX(int x) {
        this.x = x;
    }

    public int getY() {
        return y;
    }

    public void setY(int y) {
        this.y = y;
    }
}

/
 * 象棋棋子备忘录管理(负责人)
 *
 * @author lzlg
 * 2023/3/7 17:46
 */
public class MementoCaretaker {

    private ChessmanMemento memento;

    public ChessmanMemento getMemento() {
        return memento;
    }

    public void setMemento(ChessmanMemento memento) {
        this.memento = memento;
    }
}

/
 * 客户端类
 *
 * @author lzlg
 * 2023/3/7 17:48
 */
public class Client {
    public static void main(String[] args) {
        MementoCaretaker caretaker = new MementoCaretaker();

        Chessman chessman = new Chessman("车", 1, 1);
        System.out.println(chessman);
        // 保存当前状态
        caretaker.setMemento(chessman.save());
        System.out.println("走棋>>>>>>>>>");
        // 设置新位置
        chessman.setY(4);
        System.out.println(chessman);
        System.out.println("悔棋<<<<<<<<<");
        // 进行悔棋
        chessman.restore(caretaker.getMemento());
        System.out.println(chessman);
    }
}

把MementoCaretaker中的属性改为列表,即可实现多次撤销.

在MementoCaretaker中的添加一个撤销状态的列表,可实现多次恢复.

优缺点

优点

  1. 提供了一种状态恢复的机制,方便用户进行撤回,状态复原.可实现多次撤销和多次恢复的操作.
  2. 备忘录实现了对信息的封装,一个备忘录对象是一种原发器对象状态的表示,不会被其他代码改动.

缺点

资源消耗过大,如果需要保存的原发器类的成员变量太多,会占用大量存储空间.

适用环境

  1. 保存一个对象在某一时刻的全部状态或部分状态,以后需要它时能够恢复到先前的状态,实现撤销操作.
  2. 防止外界对象破坏一个对象历史状态的封装性,避免将对象历史状态的实现细节暴露给外界对象.

观察者模式

观察者模式用于建立一种对象与对象之间的依赖关系,一个对象发生改变时将自动通知其他对象.发生改变的对象是观察目标,而被通知的对象称为观察者.一个观察目标可以对应多个观察者.

结构

目标(Subject): 又称为主题,指被观察的对象.在目标中定义了观察者集合,可接受任意数量的观察者,提供一系列方法来增加或删除观察者对象,同时定义了通知方法.

具体目标(ConcreteSubject): 是目标类的子类,通常包含经常改变的数据,当它的状态改变时将向它的各个观察者发出通知,同时实现了目标类中的抽象方法.如果无须扩展目标类,具体目标类可省略.

观察者(Observer): 观察者对观察目标的改变做出反应.

具体观察者(ConcreteObserver): 维护了一个指向具体目标对象的引用,存储具体观察者的状态,这些状态需要和具体目标的状态保持一致,实现了抽象观察者的更新方法.

应用实例

联机对战游戏中,一个战队成员受到攻击时,其他相同战队成员都能接收到通知并做出反应.

import java.util.ArrayList;
import java.util.List;

/
 * 战队指挥中心(抽象目标类)
 *
 * @author lzlg
 * 2023/3/7 18:15
 */
public abstract class AllyControlCenter {

    protected final String allyName;

    protected final List<AllyObserver> players;

    public AllyControlCenter(String allyName) {
        this.allyName = allyName;
        this.players = new ArrayList<>();
    }

    public String getAllyName() {
        return allyName;
    }

    public void join(AllyObserver player) {
        System.out.println(player.getName() + "加入" + this.allyName + "战队.");
        this.players.add(player);
    }

    public void quit(AllyObserver player) {
        System.out.println(player.getName() + "退出" + this.allyName + "战队.");
        this.players.remove(player);
    }

    /
     * 通知队员遭受攻击
     *
     * @param name 被攻击队员名称
     */
    public abstract void notifyAttack(String name);
}

/
 * 具体战队指挥中心(具体目标类)
 *
 * @author lzlg
 * 2023/3/7 18:22
 */
public class ConcreteAllyControlCenter extends AllyControlCenter {

    public ConcreteAllyControlCenter(String allyName) {
        super(allyName);
    }

    @Override
    public void notifyAttack(String name) {
        System.out.println(this.allyName + "战队紧急通知,队友:" + name + "受到敌人攻击.");
        for (AllyObserver player : players) {
            if (player.getName().equals(name)) {
                continue;
            }
            player.help();
        }
    }
}

/
 * 战队观察者(观察者接口)
 *
 * @author lzlg
 * 2023/3/7 18:16
 */
public interface AllyObserver {
    /
     * 获取队友名称
     *
     * @return 队友名称
     */
    String getName();

    /
     * 帮助队友
     */
    void help();

    /
     * 通知自己遭受攻击
     *
     * @param controlCenter 指挥中心
     */
    void beAttacked(AllyControlCenter controlCenter);
}

/
 * 战队成员(具体观察者)
 *
 * @author lzlg
 * 2023/3/7 18:21
 */
public class AllyPlayer implements AllyObserver {
    // 玩家名称
    private final String name;

    public AllyPlayer(String name) {
        this.name = name;
    }

    @Override
    public String getName() {
        return this.name;
    }

    @Override
    public void help() {
        System.out.println("坚持住," + this.name + "正在前来支援.");
    }

    @Override
    public void beAttacked(AllyControlCenter controlCenter) {
        System.out.println("战队:" + controlCenter.getAllyName() + "中的" + this.name + "受到攻击.");
        controlCenter.notifyAttack(this.name);
    }
}

/
 * 客户端
 *
 * @author lzlg
 * 2023/3/7 18:31
 */
public class Client {
    public static void main(String[] args) {
        AllyControlCenter controlCenter = new ConcreteAllyControlCenter("IKun");
        AllyObserver play1 = new AllyPlayer("菜坤");
        AllyObserver play2 = new AllyPlayer("王只因");
        AllyObserver play3 = new AllyPlayer("张太美");
        controlCenter.join(play1);
        controlCenter.join(play2);
        controlCenter.join(play3);
        System.out.println("=============================");
        play1.beAttacked(controlCenter);
    }
}

JDK中以及存在观察者模式的相关接口,接口Observer是观察者接口,类Observable是观察目标.

MVC架构中应用了观察者模式.

优缺点

优点

  1. 可实现表示层和数据逻辑层的分离,定义了稳定的消息更新传递机制,并抽象了更新接口,可以有不同的表示层充当具体观察者.
  2. 观察目标只需维持一个抽象观察者的集合,无须了解具体观察者.
  3. 支持广播通信,观察目标会向所有已注册的观察者对象发生通知,简化了一对多系统设计的难度.
  4. 符合开闭原则,增加新具体的观察者无须修改原有代码.具体观察者和观察目标之间不存在关联关系的情况下,增加新的观察目标比较方便.

缺点

  1. 如果一个观察目标对象有很多直接或间接的观察者,通知到所有的观察者会花费很多时间.
  2. 如果观察者和观察目标之间存在循环依赖,可能会导致系统崩溃.
  3. 观察者模式没有相应机制让观察者知道所观察的目标对象是怎么变化的,而仅仅只是知道观察目标变化了.

适用环境

  1. 一个抽象模型有两个方面,一个方面依赖于另一个方面,将这两个方面封装在独立的对象中使它们可以各自独立改变和复用.
  2. 一个对象的改变将导致一个或多个其他对象也发生改变,而且不知道具体有多少对象将发生改变,也不知道这些对象是什么.
  3. 需要在系统中创建一个触发链,可使用观察者模式构建一种链式触发机制.

状态模式

状态模式用于解决系统中复杂对象的状态转换以及不同状态下行为的封装问题.当系统中的某个对象存在多个状态,这些状态之间可以相互转换,而且对象在不同状态下的行为不同时可使用状态模式.状态模式将一个对象的状态从该对象中分离出来,封装到专门的状态类中,使得对象状态可以灵活变化.

结构

环境类(Context): 又称上下文类,是拥有多种状态的对象.环境类中维护一个抽象状态类的引用.

抽象状态类(State): 定义接口用以封装与环境类的一个特定状态相关的行为,声明了各种不同状态对应的方法.

具体状态类(ConcreteState): 是抽象状态类的子类,实现与环境类一个状态相关的行为,每个具体状态类对应环境类的一个具体状态.

应用实例

信用卡业务系统根据用户银行账户中的余额和透支情况有3种不同的状态: 1)如果账户余额大于等于0,则为正常状态. 2)如果余额小于0,且没达到透支额度2000元,则为透支状态,能存款,也可取款,但需按天计算利息. 3)如果透支到2000元,则为受限状态,只能存款,按天计算利息.

import java.math.BigDecimal;

/
 * 账户(环境类)
 *
 * @author lzlg
 * 2023/3/7 19:09
 */
public class Account {

    private final String owner;

    private BigDecimal balance;

    private AccountState state;

    public static final BigDecimal NEGATIVE_TWO_THOUSAND = new BigDecimal("-2000");

    public Account(String owner, BigDecimal balance) {
        this.owner = owner;
        this.balance = balance;
        this.state = new NormalState(this);
        this.state.stateCheck();
        System.out.println(owner + "开户,初始金额为:" + balance);
        System.out.println("-------------------------------------------");
    }

    public BigDecimal getBalance() {
        return balance;
    }

    public void setBalance(BigDecimal balance) {
        if (NEGATIVE_TWO_THOUSAND.compareTo(balance) <= 0) {
            this.balance = balance;
        } else {
            throw new RuntimeException("只能透支:" + NEGATIVE_TWO_THOUSAND.abs() + "元.");
        }
    }

    public void setState(AccountState state) {
        this.state = state;
    }

    /
     * 存款
     *
     * @param amount 金额
     */
    public void deposit(BigDecimal amount) {
        System.out.println(this.owner + "存款" + amount);
        System.out.println("存款前余额为:" + this.balance + ",账户状态为:" + this.state.getName());
        this.state.deposit(amount);
        System.out.println("存款后余额为:" + this.balance + ",账户状态为:" + this.state.getName());
        System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");
    }

    /
     * 取款
     *
     * @param amount 金额
     */
    public void withdraw(BigDecimal amount) {
        System.out.println(this.owner + "取款" + amount);
        System.out.println("取款前余额为:" + this.balance + ",账户状态为:" + this.state.getName());
        this.state.withdraw(amount);
        System.out.println("取款后余额为:" + this.balance + ",账户状态为:" + this.state.getName());
        System.out.println("<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<");
    }

    /
     * 计算利息
     */
    public void computeInterest() {
        this.state.computeInterest();
    }
}

import java.math.BigDecimal;

/
 * 账户状态(抽象状态类)
 *
 * @author lzlg
 * 2023/3/7 19:10
 */
public abstract class AccountState {

    private final String name;

    protected Account account;

    public AccountState(String name, Account account) {
        this.name = name;
        this.account = account;
    }

    public String getName() {
        return name;
    }

    /
     * 存款
     *
     * @param amount 金额
     */
    public abstract void deposit(BigDecimal amount);

    /
     * 取款
     *
     * @param amount 金额
     */
    public abstract void withdraw(BigDecimal amount);

    /
     * 计算利息
     */
    public abstract void computeInterest();

    /
     * 状态转换
     */
    public abstract void stateCheck();
}

import java.math.BigDecimal;

import static com.lzlg.study.design.state.Account.NEGATIVE_TWO_THOUSAND;

/
 * 正常状态(具体状态类)
 *
 * @author lzlg
 * 2023/3/7 19:12
 */
public class NormalState extends AccountState {
    public NormalState(Account account) {
        super("正常", account);
    }

    /
     * 存款
     *
     * @param amount 金额
     */
    @Override
    public void deposit(BigDecimal amount) {
        account.setBalance(account.getBalance().add(amount));
        this.stateCheck();
    }

    /
     * 取款
     *
     * @param amount 金额
     */
    @Override
    public void withdraw(BigDecimal amount) {
        account.setBalance(account.getBalance().subtract(amount));
        this.stateCheck();
    }

    /
     * 计算利息
     */
    @Override
    public void computeInterest() {
        System.out.println(this.getName() + "状态,无须支付利息.");
    }

    /
     * 状态转换
     */
    @Override
    public void stateCheck() {
        BigDecimal balance = account.getBalance();
        // 如果小于0且大于-2000则为透支状态
        if (balance.compareTo(NEGATIVE_TWO_THOUSAND) > 0 && balance.compareTo(BigDecimal.ZERO) <= 0) {
            account.setState(new OverdraftState(account));
        } else if (balance.compareTo(NEGATIVE_TWO_THOUSAND) == 0) {
            account.setState(new RestrictedState(account));
        }
    }
}

import java.math.BigDecimal;

import static com.lzlg.study.design.state.Account.NEGATIVE_TWO_THOUSAND;

/
 * 透支状态(具体状态类)
 *
 * @author lzlg
 * 2023/3/7 19:12
 */
public class OverdraftState extends AccountState {
    public OverdraftState(Account account) {
        super("透支", account);
    }

    /
     * 存款
     *
     * @param amount 金额
     */
    @Override
    public void deposit(BigDecimal amount) {
        account.setBalance(account.getBalance().add(amount));
        this.stateCheck();
    }

    /
     * 取款
     *
     * @param amount 金额
     */
    @Override
    public void withdraw(BigDecimal amount) {
        account.setBalance(account.getBalance().subtract(amount));
        this.stateCheck();
    }

    /
     * 计算利息
     */
    @Override
    public void computeInterest() {
        System.out.println("按天计算利息.利率为: 千分之6");
    }

    /
     * 状态转换
     */
    @Override
    public void stateCheck() {
        BigDecimal balance = account.getBalance();
        if (balance.compareTo(BigDecimal.ZERO) >= 0) {
            account.setState(new NormalState(account));
        } else if (balance.compareTo(NEGATIVE_TWO_THOUSAND) == 0) {
            account.setState(new RestrictedState(account));
        }
    }
}

import java.math.BigDecimal;

import static com.lzlg.study.design.state.Account.NEGATIVE_TWO_THOUSAND;

/
 * 受限状态(具体状态类)
 *
 * @author lzlg
 * 2023/3/7 19:12
 */
public class RestrictedState extends AccountState {
    public RestrictedState(Account account) {
        super("受限", account);
    }

    /
     * 存款
     *
     * @param amount 金额
     */
    @Override
    public void deposit(BigDecimal amount) {
        account.setBalance(account.getBalance().add(amount));
        this.stateCheck();
    }

    /
     * 取款
     *
     * @param amount 金额
     */
    @Override
    public void withdraw(BigDecimal amount) {
        account.setBalance(account.getBalance().subtract(amount));
        this.stateCheck();
    }

    /
     * 计算利息
     */
    @Override
    public void computeInterest() {
        System.out.println("按天计算利息.利率为: 千分之8");
    }

    /
     * 状态转换
     */
    @Override
    public void stateCheck() {
        BigDecimal balance = account.getBalance();
        if (balance.compareTo(BigDecimal.ZERO) >= 0) {
            account.setState(new NormalState(account));
        } else if (balance.compareTo(NEGATIVE_TWO_THOUSAND) > 0) {
            account.setState(new OverdraftState(account));
        }
    }
}

import java.math.BigDecimal;

/
 * 客户端
 *
 * @author lzlg
 * 2023/3/7 19:49
 */
public class Client {
    public static void main(String[] args) {
        Account account = new Account("小李", new BigDecimal("888"));
        account.withdraw(new BigDecimal("1888"));
        account.computeInterest();
        account.withdraw(new BigDecimal("1000"));
        account.computeInterest();
        account.deposit(new BigDecimal("5000"));
        account.computeInterest();
    }
}

可在环境类中使用静态变量的状态成员,并提供静态的修改状态的方法,可实现状态的共享.

可将上诉状态转换的代码移动到环境类(Account)中,代码能够集中重用.如果这样做,增加新的状态类时就不得不修改环境类的代码,违背开闭原则,有得必有失.

优缺点

优点

  1. 封装了状态转换规则,可将状态的转换代码封装在环境类或具体状态类中(也可在抽象状态类中),可对状态转换代码进行集中管理.
  2. 状态模式将所有与状态相关的行为放到一个类中,只需要注入一个不同状态的对象,就能使环境对象有不同的行为.
  3. 状态模式允许状态转换逻辑与状态对象合成一体,可避免庞大的条件语句将业务方法和状态转换代码交织在一起.
  4. 状态模式可让多个环境对象共享一个状态对象,从而减少系统中对象的个数.

缺点

  1. 状态模式会增加系统中类和对象的个数,导致系统运行开销增大.
  2. 状态模式的实现和结构都较为复杂,使用不当会造成程序结构和代码的混乱,增加系统设计的难度.
  3. 状态模式对开闭原则的支持不太友好,新增状态会修改原状态转换代码.修改某个状态类的行为也需要修改对应的原代码.

适用环境

  1. 对象的行为依赖于它的状态,状态的改变会导致行为的变化.
  2. 在代码中包含大量与对象状态相关的条件语句,通过状态模式能够比较方便的管理这些状态,降低耦合度.

策略模式

策略模式中每一个类封装一种具体的算法,封装算法的类称为一种策略,一般使用抽象的策略类来实现行为的一致性,而每种算法对应一个具体的策略类.

结构

环境类(Context): 是使用算法的角色,实现功能时可采用多种策略,引用一个抽象策略类的实例.

抽象策略类(Strategy): 为所支持的算法声明了抽象方法,是具体策略类的父类.

具体策略类(ConcreteStrategy): 实现了抽象策略类声明的方法,使用一种具体的算法实现业务功能.

应用实例

售票系统根据不同类型的用户提供不同的电影票打折方式: 1)学生凭学生证可享受票价8折优惠; 2)年龄在10岁下的儿童可每张票减免10元的优惠(票价需大于等于20元); 3)VIP用户享受票价半价,且累计积分,积分到一定额度能兑换相应的礼品.

import java.math.BigDecimal;

/
 * 电影票(环境类)
 *
 * @author lzlg
 * 2023/3/8 14:45
 */
public class MovieTicket {
    // 电影名称
    private final String movie;
    // 票价
    private final BigDecimal price;
    // 折扣
    private Discount discount;

    public MovieTicket(String movie, BigDecimal price) {
        this.movie = movie;
        this.price = price;
    }

    public String getMovie() {
        return movie;
    }

    /
     * 获取票价
     *
     * @return 票价
     */
    public BigDecimal getPrice() {
        if (discount == null) {
            return price;
        }
        return discount.calculate(this.price);
    }

    /
     * 设置折扣信息
     *
     * @param discount 折扣
     */
    public void setDiscount(Discount discount) {
        this.discount = discount;
    }

    @Override
    public String toString() {
        return "电影: " + movie + " 的票价为: " + this.getPrice() + "元.";
    }
}

import java.math.BigDecimal;

/
 * 折扣接口(抽象策略类)
 *
 * @author lzlg
 * 2023/3/8 14:45
 */
public interface Discount {
    /
     * 折扣计算
     *
     * @param price 票价
     * @return 折扣后票价
     */
    BigDecimal calculate(BigDecimal price);
}

import java.math.BigDecimal;

/
 * 学生折扣(具体策略类)
 *
 * @author lzlg
 * 2023/3/8 14:52
 */
public class StudentDiscount implements Discount {
    // 8折
    private static final BigDecimal TWENTY_OFF = new BigDecimal("0.8");

    /
     * 折扣计算
     *
     * @param price 票价
     * @return 折扣后票价
     */
    @Override
    public BigDecimal calculate(BigDecimal price) {
        System.out.println("学生票: ");
        return TWENTY_OFF.multiply(price);
    }
}

import java.math.BigDecimal;

/
 * 儿童折扣(具体策略类)
 *
 * @author lzlg
 * 2023/3/8 14:54
 */
public class ChildrenDiscount implements Discount {

    private static final BigDecimal DISCOUNT = new BigDecimal("10");

    private static final BigDecimal TWENTY_PRICE = new BigDecimal("20");

    /
     * 折扣计算
     *
     * @param price 票价
     * @return 折扣后票价
     */
    @Override
    public BigDecimal calculate(BigDecimal price) {
        System.out.println("儿童票: ");
        // 如果票价小于20元,则不进行优惠
        if (TWENTY_PRICE.compareTo(price) > 0) {
            return price;
        }
        // 否则优惠10元
        return price.subtract(DISCOUNT);
    }
}

import java.math.BigDecimal;

/
 * VIP用户折扣(具体策略类)
 *
 * @author lzlg
 * 2023/3/8 14:54
 */
public class VIPDiscount implements Discount {

    private static final BigDecimal DISCOUNT = new BigDecimal("0.5");

    /
     * 折扣计算
     *
     * @param price 票价
     * @return 折扣后票价
     */
    @Override
    public BigDecimal calculate(BigDecimal price) {
        System.out.println("VIP票: ");
        System.out.println("增加积分: " + price.intValue());
        // 优惠半价
        return price.multiply(DISCOUNT);
    }
}

import java.math.BigDecimal;

/
 * 客户端
 *
 * @author lzlg
 * 2023/3/8 15:02
 */
public class Client {
    public static void main(String[] args) {
        MovieTicket movieTicket = new MovieTicket("流浪地球2", new BigDecimal(32));
        System.out.println(movieTicket);
        System.out.println("============================");
        Discount discount = new StudentDiscount();
        movieTicket.setDiscount(discount);
        System.out.println(movieTicket);
        System.out.println("============================");
        discount = new ChildrenDiscount();
        movieTicket.setDiscount(discount);
        System.out.println(movieTicket);
        System.out.println("============================");
        discount = new VIPDiscount();
        movieTicket.setDiscount(discount);
        System.out.println(movieTicket);
    }
}

优缺点

优点

  1. 对开闭原则的完美支持,可在不修改原有代码上选择算法或增加删除算法.
  2. 策略模式提供了管理相关算法族的办法,可以将通用的步骤放在抽象策略类中,避免重复代码.
  3. 策略模式提供了替代继承实现算法动态切换且算法使用和算法本身分离的办法.
  4. 使用策略模式可避免多重条件选择语句,多重条件语句不易维护.
  5. 策略模式提供了一种算法的复用机制,可将算法单独提取出来封装在策略类中,不同的环境类都可使用.

缺点

  1. 客户端必须知道所有的策略类,并自行决定使用哪一个策略类.
  2. 策略模式会造成系统产生很多策略类,任何细小的变化都将导致系统要增加新的具体策略类.
  3. 无法同时在客户端使用多个策略类.

适用环境

  1. 一个系统需要动态地在几种算法中选择一种,可使用策略模式.
  2. 使用策略模式可避免使用难以维护的多重条件语句.
  3. 不希望客户端知道复杂的,与算法相关的数据结构,可提高算法的保密性和安全性.

模板方法模式

模板方法模式中抽象父类中提供了一个称为模板方法的方法来定义算法(操作)的次序,通过子类覆盖某些步骤,从而使得相同的算法框架可以有不同的执行结构.

结构

抽象模板类(AbstractTemplate): 定义一系列基本操作,操作可具体可抽象,每一个操作对应算法的一个步骤,子类可重写或实现这些步骤.抽象模板类中实现了一个模板方法,定义算法的框架,调用基本操作完成算法.

具体模板类(ConcreteTemplate): 是抽象模板类的子类,实现父类声明的抽象基本操作,也可覆盖父类已实现的.

应用实例

某银行利息计算功能流程: 1)系统根据账号和密码验证用户信息, 用户信息错误则进行提示; 2)如果用户信息正确,根据用户类型的不同使用不同的利息计算公式计算利息; 3)系统显示利息.

import java.math.BigDecimal;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

/
 * 账户类(抽象模板类)
 *
 * @author lzlg
 * 2023/3/8 15:31
 */
public abstract class Account {
    // 账户数据Map
    static final Map<String, String> accountMap;

    static {
        accountMap = new HashMap<>();
        accountMap.put("小李", "123000");
    }

    /
     * 计算利息(模板方法)
     *
     * @param account  账户名
     * @param password 账户密码
     */
    public void handle(String account, String password) {
        if (!this.validate(account, password)) {
            System.out.println("账户或密码错误.");
            return;
        }
        // 计算利息
        BigDecimal interest = this.calculateInterest();
        // 如果需要显示利息,则进行显示
        if (this.isShowInterest()) {
            // 显示利息
            this.displayInterest(interest);
        }
    }

    /
     * 验证账户(通用方法,不能让子类重写)
     *
     * @param account  账户
     * @param password 密码
     * @return 结果
     */
    public final boolean validate(String account, String password) {
        System.out.println("账户: " + account + ", 密码: " + password);
        // 从数据库中取账户信息
        String storePassword = accountMap.get(account);
        // 如果为空,则没有此账户
        if (Objects.isNull(storePassword)) {
            return false;
        }
        // 比对密码
        return storePassword.equals(password);
    }

    /
     * 计算利息(留给子类实现)
     *
     * @return 利息
     */
    public abstract BigDecimal calculateInterest();

    /
     * 是否显示利息(钩子方法,子类可通过钩子方法控制显示利息)
     *
     * @return 结果
     */
    public boolean isShowInterest() {
        return true;
    }

    /
     * 显示利息(子类可覆盖,自定义显示办法)
     *
     * @param interest 利息
     */
    public void displayInterest(BigDecimal interest) {
        System.out.println("利息为: " + interest);
    }
}

import java.math.BigDecimal;

/
 * 活期账户(具体模板类)
 *
 * @author lzlg
 * 2023/3/8 15:42
 */
public class CurrentAccount extends Account {
    /
     * 计算利息(留给子类实现)
     *
     * @return 利息
     */
    @Override
    public BigDecimal calculateInterest() {
        System.out.println("按活期计算利息: ");
        return new BigDecimal("2.33");
    }

    /
     * 不显示利息(钩子方法,子类可通过钩子方法控制显示利息)
     *
     * @return 结果
     */
    @Override
    public boolean isShowInterest() {
        return false;
    }
}

import java.math.BigDecimal;

/
 * 定期账户(具体模板类)
 *
 * @author lzlg
 * 2023/3/8 15:42
 */
public class SavingAccount extends Account {
    /
     * 计算利息(留给子类实现)
     *
     * @return 利息
     */
    @Override
    public BigDecimal calculateInterest() {
        System.out.println("按定期计算利息: ");
        return new BigDecimal("11.11");
    }

    /
     * 显示利息(子类可覆盖,自定义显示办法)
     *
     * @param interest 利息
     */
    @Override
    public void displayInterest(BigDecimal interest) {
        System.out.println("定期利息为: " + interest);
    }
}

/
 * 客户端
 *
 * @author lzlg
 * 2023/3/8 15:46
 */
public class Client {
    public static void main(String[] args) {
        Account account = new CurrentAccount();
        account.handle("小李", "123456");
        System.out.println("===========================");
        account.handle("小李", "123000");
        System.out.println("===========================");
        account = new SavingAccount();
        account.handle("小李", "123000");
    }
}

优缺点

优点

  1. 父类定义了算法框架,子类实现算法细节且不会改变算法中的步骤次序.
  2. 模板方法模式是一种代码复用技术,鼓励用户恰当地使用继承来实现代码复用.
  3. 模板方法模式可实现反向控制结构,通过子类覆盖父类的钩子方法决定某特定步骤是否执行.
  4. 不同的子类可提供基本方法的不同实现,更换和增加子类都很方便,符合单一职责原则和开闭原则.

缺点

模板方法模式中每一个基本方法的不同实现都需要一个子类,如果可变的基本方法太多,会导致子类的个数增加,系统更加庞大,设计更加抽象,可结合桥接模式进行设计.

适用环境

  1. 对一些复杂的算法进行分割,将算法中固定不变的部分设计为模板方法和父类的具体方法,可以改变的细节由子类实现.
  2. 各子类中公共的行为被提取出来并集中到公共父类中以避免代码重复.
  3. 需要通过子类来决定父类算法中的某个步骤是否执行,实现子类对父类的反向控制.

访问者模式

访问者模式包含访问者和访问元素两个主要部分,被访问的元素通常具有不同的类型,且不同的访问者可以对它们进行不同的访问操作.访问者模式使得用户在不修改原有系统的情况下扩展系统的功能,为这些不同类型的元素增加新的操作.

结构

抽象访问者(Visitor): 为对象结构中的每一个具体元素类声明一个访问方法,从方法的名称或参数可知道需要访问的具体元素的类型.

具体访问者(ConcreteVisitor): 实现类每个由抽象访问者声明的操作,每一个操作用于访问对象结构中的一种类型元素.

抽象元素(Element): 一般是抽象类或接口,声明了accept()方法用于接受访问者的访问操作,accept()方法通常用一个抽象访问者作为参数.

具体元素(ConcreteElement): 实现了accept()方法,在accept()方法中调用访问者的访问方法用以完成一个元素的操作.

对象结构(ObjectStructure): 是一个元素的集合,用于存放元素对象,并提供了遍历内部元素的方法.可结合组合模式进行实现,也可以是一个简单的集合对象.

应用实例

某OA系统包含一个员工管理系统,公司员工有正式员工和临时工,每周人力资源部和财务部等部门需要对员工数据进行汇总,汇总数据包括员工工作时间,员工工资等.基本制度是:

1)正式员工每周工作40小时,不同级别,不同部门的员工每周基本工资不同,如果超过40小时,超出部分按100元每小时作为加班费;如果少于40小时,所缺时间按请假处理,请假所扣工资以80元每小时计算,直到基本工资扣完.人力资源部需记录加班时长或请假时长,作为员工平时表现的一项依据.

2)临时工每周工作时间不固定,基本工资按小时算,不同岗位的临时工小时工资不同.人力资源部只记录实际工作时间.

人力资源部财务部人员可根据需要对员工数据进行汇总处理,人力资源部负责汇总每周员工工作时间,财务部负责计算每周员工工资.

/
 * 员工类(抽象元素类)
 *
 * @author lzlg
 * 2023/3/8 16:20
 */
public interface Employee {
    /
     * 接受部门访问者的访问
     *
     * @param department 部门访问者
     */
    void accept(Department department);
}

import java.math.BigDecimal;
import java.util.Objects;

/
 * 抽象员工类
 *
 * @author lzlg
 * 2023/3/8 16:24
 */
public abstract class AbstractEmployee implements Employee {
    // 员工名称
    private final String name;
    // 员工工资(正式员工是周薪,临时工是时薪)
    private final BigDecimal wage;
    // 工作时长(单位小时)
    private final int workTime;

    public AbstractEmployee(String name, BigDecimal wage, int workTime) {
        Objects.requireNonNull(name);
        Objects.requireNonNull(wage);
        if (BigDecimal.ZERO.compareTo(wage) > 0) {
            throw new IllegalArgumentException("薪资不能为负数");
        }
        if (workTime < 0) {
            throw new IllegalArgumentException("工作时长不能为负数");
        }
        this.name = name;
        this.wage = wage;
        this.workTime = workTime;
    }

    public String getName() {
        return name;
    }

    public BigDecimal getWage() {
        return wage;
    }

    public int getWorkTime() {
        return workTime;
    }
}

import java.math.BigDecimal;

/
 * 正式员工(具体元素类)
 *
 * @author lzlg
 * 2023/3/8 16:22
 */
public class FullTimeEmployee extends AbstractEmployee {

    public FullTimeEmployee(String name, BigDecimal wage, int workTime) {
        super(name, wage, workTime);
    }

    /
     * 接受部门访问者的访问
     *
     * @param department 部门访问者
     */
    @Override
    public void accept(Department department) {
        department.visit(this);
    }
}

import java.math.BigDecimal;

/
 * 临时工(具体元素类)
 *
 * @author lzlg
 * 2023/3/8 16:22
 */
public class PartTimeEmployee extends AbstractEmployee {

    public PartTimeEmployee(String name, BigDecimal wage, int workTime) {
        super(name, wage, workTime);
    }

    /
     * 接受部门访问者的访问
     *
     * @param department 部门访问者
     */
    @Override
    public void accept(Department department) {
        department.visit(this);
    }
}

/
 * 部门类(抽象访问者)
 *
 * @author lzlg
 * 2023/3/8 16:20
 */
public abstract class Department {
    /
     * 访问正式员工的方法
     *
     * @param employee 正式员工
     */
    public abstract void visit(FullTimeEmployee employee);

    /
     * 访问临时工的方法
     *
     * @param employee 临时工
     */
    public abstract void visit(PartTimeEmployee employee);
}

import java.math.BigDecimal;

/
 * 财务部门(具体访问者)
 *
 * @author lzlg
 * 2023/3/8 16:31
 */
public class FinanceDepartment extends Department {
    /
     * 访问正式员工的方法
     *
     * @param employee 正式员工
     */
    @Override
    public void visit(FullTimeEmployee employee) {
        // 工作时长
        int workTime = employee.getWorkTime();
        // 周薪
        BigDecimal wage = employee.getWage();
        // 最终工资
        BigDecimal finalWage = wage;
        // 如果工作时间大于40小时
        if (workTime > 40) {
            // 超出40小时的部分乘以100加上原周薪
            finalWage = wage.add(new BigDecimal(100 * (workTime - 40)));
            // 如果工作时间小于40小时
        } else if (workTime < 40) {
            // 原周薪减去未满工时(40 - workTime)乘以80
            finalWage = wage.subtract(new BigDecimal((40 - workTime) * 80));
            // 如果计算出的薪资比零小,则工资直接为0
            if (finalWage.compareTo(BigDecimal.ZERO) < 0) {
                finalWage = BigDecimal.ZERO;
            }
        }
        System.out.println("正式员工: " + employee.getName() + ", 本周实际工资为: " + finalWage + "元.");
    }

    /
     * 访问临时工的方法
     *
     * @param employee 临时工
     */
    @Override
    public void visit(PartTimeEmployee employee) {
        // 工作时长
        int workTime = employee.getWorkTime();
        // 时薪
        BigDecimal wage = employee.getWage();
        // 最终工资=时薪乘以工作时长
        BigDecimal finalWage = wage.multiply(new BigDecimal(workTime));
        System.out.println("临时工: " + employee.getName() + ", 本周实际工资为: " + finalWage + "元.");
    }
}

/
 * 人力资源部门(具体访问者)
 *
 * @author lzlg
 * 2023/3/8 16:31
 */
public class HRDepartment extends Department {
    /
     * 访问正式员工的方法
     *
     * @param employee 正式员工
     */
    @Override
    public void visit(FullTimeEmployee employee) {
        int workTime = employee.getWorkTime();
        System.out.println("正式员工: " + employee.getName() + ", 本周工作时间: " + workTime + "小时.");
        if (workTime > 40) {
            System.out.println("正式员工: " + employee.getName() + ", 本周加班时间: " + (workTime - 40) + "小时.");
        } else if (workTime < 40) {
            System.out.println("正式员工: " + employee.getName() + ", 本周请假时间: " + (40 - workTime) + "小时.");
        }
    }

    /
     * 访问临时工的方法
     *
     * @param employee 临时工
     */
    @Override
    public void visit(PartTimeEmployee employee) {
        System.out.println("临时工: " + employee.getName() + ", 本周工作时间: " + employee.getWorkTime() + "小时.");
    }
}

import java.math.BigDecimal;
import java.util.Arrays;
import java.util.List;

/
 * 客户端
 *
 * @author lzlg
 * 2023/3/8 16:49
 */
public class Client {
    public static void main(String[] args) {
        List<Employee> employees = Arrays.asList(
                new FullTimeEmployee("小李", new BigDecimal(5000), 48),
                new FullTimeEmployee("小张", new BigDecimal(4000), 40),
                new FullTimeEmployee("小王", new BigDecimal(2500), 35),
                new PartTimeEmployee("小菜", new BigDecimal(100), 60),
                new PartTimeEmployee("小鸡", new BigDecimal(200), 22)
        );
        System.out.println("财务部统计员工工资: ");
        Department department = new FinanceDepartment();
        for (Employee employee : employees) {
            employee.accept(department);
        }
        System.out.println("=============================");
        System.out.println("人力资源部统计员工工作时长: ");
        department = new HRDepartment();
        for (Employee employee : employees) {
            employee.accept(department);
        }

    }
}

优缺点

优点

  1. 访问者模式中增加新的访问操作很方便,只需增加一个新的具体访问者类,无须修改原有代码,符合开闭原则.
  2. 访问者模式将有关元素对象的访问行为集中到一个访问者对象中,类的职责更加清晰,有利于对象结构中元素对象的复用,相同的对象结构可以供多个不同的访问者访问.
  3. 访问者模式让用户在不修改现有元素层次结构的情况下定义作用于该层次结构的操作.

缺点

  1. 访问者模式中增加新的元素类很困难,如果增加新的元素类,则须在抽象访问者中增加新的访问操作,具体访问者都得进行实现,违背了开闭原则.
  2. 访问者模式破坏了对象的封装性,有时候元素对象必须暴露自己的内部操作和状态,否则访问者无法访问.

适用环境

  1. 一个对象结构中包含多个类型的对象,希望对这些对象实施一些依赖具体类型的操作.
  2. 需要对一个对象结构中的对象进行很多不同且不相关的操作,且需要避免让这些操作和对象混合,也不希望增加新操作时修改这些类.
  3. 对象结构中对象对应的类很少改变,但经常需要在此对象结构上定义新的操作.
程序员内功
码出好代码
  • 作者:lzlg520
  • 发表时间:2023-03-08 17:10
  • 版权声明:自由转载-非商用-非衍生-保持署名
  • 公众号转载:请在文末添加作者公众号二维码