面向对象设计原则

开闭原则
定义:一个软件实体应当对扩展开放,对修改关闭。即软件实体应尽量在不 修改原有代码的情况下进行扩展。
为了满足开闭原则,需要对系统进行抽象化设计,抽象化是开闭原则的关键。在Java、C#等编程语言中,可以为系 统定义一个相对稳定的抽象层,而将不同的实现行为移至具体的实现层中完成。在很多面向对象编程语言中都提供了 接口、抽象类等机制,可以通过它们定义系统的抽象层,再通过具体类来进行扩展。如果需要修改系统的行为,无须 对抽象层进行任何改动,只需要增加新的具体类来实现新的业务功能即可,实现在不修改已有代码的基础上扩展系统 的功能,达到开闭原则的要求。
开闭原则是面向对象程序设计的终极目标,它使软件实体拥有一定的适应性和灵活性的同时具备稳定性和延续性。 具体来说,其作用如下。
- 对软件测试的影响
软件遵守开闭原则的话,软件测试时只需要对扩展的代码进行测试就可以了,因为原有的测试代码仍然能够正常运 行。 - 可以提高代码的可复用性
粒度越小,被复用的可能性就越大;在面向对象的程序设计中,根据原子和抽象编程可以提高代码的可复用性。 - 可以提高软件的可维护性
遵守开闭原则的软件,其稳定性高和延续性强,从而易于扩展和维护。
单一职责原则
定义:一个类只负责一个功能领域中的相应职责,或者可 以定义为:就一个类而言,应该只有一个引起它变化的原因。
单一职责原则的核心就是控制类的粒度大小、将对象解耦、提高其内聚性。如果遵循单一职责原则将有以下优点。
- 降低类的复杂度。一个类只负责一项职责,其逻辑肯定要比负责多项职责简单得多。
- 提高类的可读性。复杂性降低,自然其可读性会提高。
- 提高系统的可维护性。可读性提高,那自然更容易维护了。
- 变更引起的风险降低。变更是必然的,如果单一职责原则遵守得好,当修改一个功能时,可以显著降低对其他功能的影响。
里式替换原则
定义:所有引用基类(父类)的地方必须能透明地使用其子 类的对象。
通俗地讲,就是任何一个使用父类的地方,你都可以把它替换成它的子类,而不会发生任何错误或异常,使用者可能根本就不需要知道是父类还是子类。但是,反过 来就不行了,有子类出现的地方,父类未必可以替换。
里氏替换原则是继承复用的基石,它为良好的继承定义了一个规范,定义中包含了4层含义:
- 子类必须完全实现父类的方法。
- 子类中可以增加自己特有的方法
- 当子类覆盖或实现父类的方法时,方法的输入参数(方法的形参)要比父类方法的输入参数更宽松。
- 当子类的方法实现父类的抽象方法时,方法的后置条件(即方法的返回值)要比父类更严格。
里氏替换原则的主要作用如下。
- 里氏替换原则是实现开闭原则的重要方式之一。
- 它克服了继承中重写父类造成的可复用性变差的缺点。
- 它是动作正确性的保证。即类的扩展不会给已有的系统引入新的错误,降低了代码出错的可能性。
依赖倒置原则
如果说开闭原则是面向对象设计的目标的话,那么依赖倒转原则就是面向对象设计的主要实现机制之一,它是系统抽象化的具体实现
**定义:**抽象不应该依赖于细节,细节应当依赖于抽象。换言之,要针对接口编程,而不是针对实现编程。
作用
- 依赖倒置原则的作用 依赖倒置原则可以降低类间的耦合性。
- 依赖倒置原则可以提高系统的稳定性。
- 依赖倒置原则可以减少并行开发引起的风险。
- 依赖倒置原则可以提高代码的可读性和可维护性。
开闭原则是目标,里氏代换原则是基础,依赖倒转原则是手段,它们相辅相成,相互补充,目标一致,只是 分析问题时所站角度不同而已。
依赖倒置原则的实现方法
依赖倒置原则的目的是通过要面向接口的编程来降低类间的耦合性,所以我们在实际编程中只要遵循以下5点,就能在项目中满足这个规则。
- 每个类尽量提供接口或抽象类,或者两者都具备。
- 变量的声明类型尽量是接口或者是抽象类。
- 任何类都不应该从具体类派生。
- 尽量不要覆写基类的方法。
- 使用继承时结合里氏替换原则。
接口隔离原则
**定义:**使用多个专门的接口,而不使用单一的总接口, 即客户端不应该依赖那些它不需要的接口
接口隔离原则是为了约束接口、降低类对接口的依赖性,遵循接口隔离原则有以下 5 个优点。
- 将臃肿庞大的接口分解为多个粒度小的接口,可以预防外来变更的扩散,提高系统的灵活性和可维护性。
- 接口隔离提高了系统的内聚性,减少了对外交互,降低了系统的耦合性。
- 如果接口的粒度大小定义合理,能够保证系统的稳定性;但是,如果定义过小,则会造成接口数量过多,使设计复杂化;如果定义太大,灵活性降低,无法提供定制服务,给整体项目带来无法预料的风险。
- 使用多个专门的接口还能够体现对象的层次,因为可以通过接口的继承,实现对总接口的定义。
- 能减少项目工程中的代码冗余。过大的大接口里面通常放置许多不用的方法,当实现这个接口的时候,被迫 设计冗余的代码。
在具体应用接口隔离原则时,应该根据以下几个规则来衡量。 接口尽量小,但是要有限度。
- 一个接口只服务于一个子模块或业务逻辑。
- 为依赖接口的类定制服务。只提供调用者需要的方法,屏蔽不需要的方法。
- 了解环境,拒绝盲从。每个项目或产品都有选定的环境因素,环境不同,接口拆分的标准就不同深入了解业务逻辑。
- 提高内聚,减少对外交互。使接口用最少的方法去完成最多的事情。
合成复用原则
定义:尽量使用对象组合,而不是继承来达到复用的目的。
合成复用原则就是在一个新的对象里通过关联关系(包括组合关系和聚合关系)来使用一些已有的对象,使之成为新 对象的一部分;新对象通过委派调用已有对象的方法达到复用功能的目的。简言之:复用时要尽量使用组合/聚合关 系(关联关系),少用继承。
在面向对象设计中,可以通过两种方法在不同的环境中复用已有的设计和实现,即通过组合/聚合关系或通过继承, 但首先应该考虑使用组合/聚合,组合/聚合可以使系统更加灵活,降低类与类之间的耦合度,一个类的变化对其他类 造成的影响相对较少;其次才考虑继承,在使用继承时,需要严格遵循里氏代换原则,有效使用继承会有助于对问题的理解,降低复杂度,而滥用继承反而会增加系统构建和维护的难度以及系统的复杂度,因此需要慎重使用继承复 用。 通过继承来进行复用的主要问题在于继承复用会破坏系统的封装性,因为继承会将基类的实现细节暴露给子类,由于基类的内部细节通常对子类来说是可见的,所以这种复用又称“白箱”复用,如果基类发生改变,那么子类的实现也 不得不发生改变;从基类继承而来的实现是静态的,不可能在运行时发生改变,没有足够的灵活性;而且继承只能在有限的环境中使用(如类没有声明为不能被继承)。 由于组合或聚合关系可以将已有的对象(也可称为成员对象)纳入到新对象中,使之成为新对象的一部分,因此新对象可以调用已有对象的功能,这样做可以使得成员对象的内部实现细节对于新对象不可见,所以这种复用又称为“黑 箱”复用,相对继承关系而言,其耦合度相对较低,成员对象的变化对新对象的影响不大,可以在新对象中根据实际 需要有选择性地调用成员对象的操作;合成复用可以在运行时动态进行,新对象可以动态地引用与成员对象类型相同 的其他对象。
一般而言,如果两个类之间是“Has-A”的关系应使用组合或聚合,如果是“Is-A”关系可使用继承。“Is-A"是严 格的分类学意义上的定义,意思是一个类是另一个类的"一种”;而"Has-A"则不同,它表示某一个角色具有某一项责任。
迪米特法则
定义:又叫作最少知识原则,一个软件实体应当尽可能少地与其他实体发生相互作用
通俗的说,一个类应该对自己需要耦合或调用的 类知道的最少,被耦合或调用的类的内部是如何复杂都与我无关,我就知道你提供的public方法就好。
迪米特法则还有一个定义是:只与你的直接朋友交谈,不跟“陌生人”说话。其含义是:如果两个软件实体无须直接通信,那么就不应当发生直接的相互调用,可以通过第三方转发该调用。其目的是降低类之间的耦合度,提高模块的相对独立性。
什么叫做直接的朋友呢?每个对象都必然会和其他对象有耦合关系,两个对象之间的耦合就 成为朋友关系,这种关系有很多比如组合、聚合、依赖等等。包括以下几类:
- 当前对象本身(this)
- 当前对象的方法参数(以参数形式传入到当前对象方法中的对象)
- 当前对象的成员对象
- 如果当前对象的成员对象是一个集合,那么集合中的元素也都是朋友
- 当前对象所创建的对象
迪米特法则的优点
降低了类之间的耦合度,提高了模块的相对独立性。 由于亲合度降低,从而提高了类的可复用率和系统的扩展性。
但是,过度使用迪米特法则会使系统产生大量的中介类,从而增加系统的复杂性,使模块之间的通信效率降低。所 以,在釆用迪米特法则时需要反复权衡,确保高内聚和低耦合的同时,保证系统的结构清晰。
迪米特法则的实现方法
从迪米特法则的定义和特点可知,它强调以下两点:
- 从依赖者的角度来说,只依赖应该依赖的对象。
- 从被依赖者的角度说,只暴露应该暴露的方法。
所以,在运用迪米特法则时要注意以下 6 点。
- 在类的划分上,应该创建弱耦合的类。类与类之间的耦合越弱,就越有利于实现可复用的目标。
- 在类的结构设计上,尽量降低类成员的访问权限。
- 在类的设计上,优先考虑将一个类设置成不变类。
- 在对其他类的引用上,将引用其他对象的次数降到最低。
- 不暴露类的属性成员,而应该提供相应的访问器(set 和 get 方法)。
- 谨慎使用序列化(Serializable)功能。
这 7 种设计原则是软件设计模式必须尽量遵循的原则,各种原则要求的侧重点不同。其中,【开闭原则】是总纲, 它告诉我们要【对扩展开放,对修改关闭】;【里氏替换原则】告诉我们【不要破坏继承体系】;【依赖倒置原则】 告诉我们要【面向接口编程】;【单一职责原则】告诉我们实现【类】要【职责单一】;【接口隔离原则】告诉我们 在设计【接口】的时候要【精简单一】;【迪米特法则】告诉我们要【降低耦合度】;【合成复用原则】告诉我们要 【优先使用组合或者聚合关系复用,少用继承关系复用】。
设计模式
软件设计模式(Software Design Pattern),又称设计模式,是一套被反复使用、多数人知晓的、经过分类编目的、 代码设计经验的总结。它描述了在软件设计过程中的一些不断重复发生的问题,以及该问题的解决方案。也就是说,它 是解决特定问题的一系列套路,是前辈们的代码设计经验的总结,具有一定的普遍性,可以反复使用。其目的是为了提 高代码的可重用性、代码的可读性和代码的可靠性。
设计模式的基石
封装、继承、多态
顺序、判断、巡检
总体来说,设计模式分为三类23种
创建型(5种) : 工厂模式、抽象工厂模式、单例模式、原型模式、构建者模式
结构型(7种): 适配器模式、装饰模式、代理模式 、外观模式、桥接模式、组合模式、享元模式
行为型(11种): 模板方法模式、策略模式 、观察者模式、中介者模式、状态模式、责任链模式、命令模式、迭代器模式、访问者模式、解释器模式、备忘录模式

创建型设计模式
关注怎么创建出对象,将对象的创建和使用分离,降低系统的耦合度
使用者无需关注对象的创建细节
- 对象的创建由相关的工厂来完成;(各种工厂模式)
- 对象的创建由一个建造者来完成;(建造者模式)
- 对象的创建由原来对象克隆完成;(原型模式)
- 对象始终在系统中只有一个实例;(单例模式)
简单工厂模式
工厂类拥有一个工厂方法(create),接受了一个参数,通过不同的参数实例化不同的产品类。

优点: 很明显,简单工厂的特点就是“简单粗暴”,通过一个含参的工厂方法,我们可以实例化任何产品类,上 至飞机火箭,下至土豆面条,无所不能。 所以简单工厂有一个别名:上帝类。
缺点: 任何”东西“的子类都可以被生产,负担太重。当所要生产产品种类非常多时,工厂方法的代码量可能会 很庞大。 在遵循开闭原则(对拓展开放,对修改关闭)的条件下,简单工厂对于增加新的产品,无能为力。因为增 加新产品只能通过修改工厂方法来实现。
//简单工厂,也称为上帝工厂
public class AnimalFactory {
// 简单工厂设计模式(负担太重、不符合开闭原则)
public static Animal createAnimal(String name) {
if ("cat".equals(name)) {
return new Cat();
} else if ("dog".equals(name)) {
return new Dog();
} else if ("cow".equals(name)) {
return new Dog();
} else {
return null;
}
}静态方法工厂
//该简单工厂,也称为静态方法工厂
public class AnimalFactory2 {
public static Dog createDog(){
return new Dog(); }
public static Cat createCat(){
return new Cat();
}
}工厂方法模式
定义: 定义一个用户创建对象的接口,让子类决定实例化哪一个类。工厂方法使一个类的实例化延迟到其子类。
工厂方法是针对每一种产品提供一个工厂类。 通过不同的工厂实例来创建不同的产品实例。
工厂方法模式的主要角色如下:
- 抽象工厂(Abstract Factory):提供了创建产品的接口,调用者通过它访问具体工厂的工厂方法 newProduct() 来创建产品。
- 具体工厂(ConcreteFactory):主要是实现抽象工厂中的抽象方法,完成具体产品的创建。
- 抽象产品(Product):定义了产品的规范,描述了产品的主要特性和功能。
- 具体产品(ConcreteProduct):实现了抽象产品角色所定义的接口,由具体工厂来创建,它同具体工厂之间一一对 应。

优点: 工厂方法模式就很好的减轻了工厂类的负担,把某一类/某一种东西交由一个工厂生产;(对应简单工厂的缺点 1) 同时增加某一类”东西“并不需要修改工厂类,只需要添加生产这类”东西“的工厂即可,使得工厂类符合开 放-封闭原则。
缺点: 对于某些可以形成产品族(一组产品)的情况处理比较复杂。
// 抽象出来的动物工厂----它只负责生产一种产品
public abstract class AnimalFactory {
// 工厂方法
public abstract Animal createAnimal();
}
// 具体的工厂实现类
public class CatFactory extends AnimalFactory {
@Override
public Animal createAnimal() {
return new Cat();
}
}
//具体的工厂实现类
public class DogFactory extends AnimalFactory {
@Override
public Animal createAnimal() {
return new Dog();
}
}抽象工厂模式
1、抽象工厂是应对产品族概念的。
例如,汽车可以分为轿车、SUV、MPV等,也分为奔驰、宝马等。我们可以将奔驰的所有车看作是一个产品族,而将宝马的所有车看作是另一个产品族。分别对应两个工厂,一个是奔驰的工厂,另一个是宝马的工厂。与工厂方法不同,奔驰的工厂不只是生产具体的某一个产品,而是一族产品(奔驰轿车、奔驰 SUV、奔驰MPV)。“抽象工厂”的“抽象”指的是就是这个意思。
2、上边的工厂方法模式是一种极端情况的抽象工厂模式(即只生产一种产品的抽象工厂模式),而抽象工厂模式 可以看成是工厂方法模式的一种推广。

//车公共接口
public interface Car {
void run();
}
//奔驰抽象类
public abstract class BenChi implements Car {
@Override
public abstract void run();
}
//宝马抽象类
public abstract class BaoMa implements Car {
@Override
public abstract void run();
}
//具体类型奔驰的实现
public class BenChiQiaoChe extends BenChi {
@Override
public void run() {
System.out.println("这是奔驰轿车");
}
}
------------------------------------------------------------------------------------------------------------
//车抽象工厂
public interface CarFactory {
Car getBenChi();
Car getBaoMa();
}
//桥车抽象工厂
public class QiaoCheFactory implements CarFactory{
@Override
public Car getBenChi() {
return new BenChiQiaoChe();
}
@Override
public Car getBaoMa() {
return new BaoMaQiaoChe();
}
}
//SUV抽象工厂
public class SUVFactory implements CarFactory{
@Override
public Car getBenChi() {
return new BenChiSUV();
}
@Override
public Car getBaoMa() {
return new BaoMaSUV();
}
}工厂模式区别
简单工厂 : 使用一个工厂对象用来生产同一等级结构中的任意产品。(不支持拓展增加产品)
工厂方法 : 使用多个工厂对象用来生产同一等级结构中对应的固定产品。(支持拓展增加产品)
抽象工厂 : 使用多个工厂对象用来生产不同产品族的全部产品。(不支持拓展增加产品;支持增加产品族)
单例模式
线程安全问题,判断依据
1:是否存在多线程
2:是否有共享数据
3:是否存在非原子性操作
饿汉式
public class User{
private static User user=new User();
private User(){}
public User getInstance(){
return user;
}
}懒汉式
静态内部类
public class Student5 {
private Student5() {
}
/*
* 此处使用一个内部类来维护单例 JVM在类加载的时候,是互斥的,所以可以由此保证线程安全问题
*/
private static class SingletonFactory {
private static Student5 student = new Student5();
}
/* 获取实例 */
public static Student5 getSingletonInstance() {
return SingletonFactory.student;
}
}volatile双重检验锁
public class Student4 {
private Student4() {
}
private static volatile Student4 student = null;
// 此处考验对synchronized知识点的掌握情况
public static Student4 getSingletonInstance() {
if (student == null) {
// 采用这种方式,对于对象的选择会有问题
// JVM优化机制:先分配内存空间,再初始化
synchronized (Student4.class) {
if (student == null) {
student = new Student4();
//new ---- 开辟JVM中堆空间---产生堆内存地址保存到栈内存的student引用中---创建对象
// 存在的问题:
}
}
}
return student;
}
}
单例模式的扩展
单例模式可扩展为有限的多例(Multitcm)模式,这种模式可生成有限个实例并保存在 ArmyList 中,客户需要时 可随机获取。
public class Emperor {
private String name;
private static final int maxNum=2;
private static ArrayList<Emperor> list=new ArrayList<>(2);
static {
list.add(new Emperor("皇帝:朱祁镇"));
list.add(new Emperor("皇帝:朱祁钰"));
//list.add(new Emperor(Emperors.foreach_Emperors(i).getName()));
}
//构造方法私有,避免在类的外部创建对象
private Emperor() {
}
private Emperor(String name) {
= name;
}
//提供一个产生实例的方法
public static Emperor getInstance(){
int index=new Random().nextInt(maxNum);
return list.get(index);
}
public void work(){
System.out.println("我是"+name+":有事起奏,无事退朝!");
}
}
//可通过枚举类管理多例信息
public enum Emperors {
ONE(1, "朱祁镇"), TWO(2, "朱祁钰");
private int index;
private String name;
Emperors(int index, String name) {
this.index = index;
= name;
}
public int getIndex() {
return index;
}
public String getName() {
return name;
}
public static Emperors foreach_Emperors(int index) {
Emperors[] values = Emperors.values();
for (Emperors value : values) {
if (index == value.getIndex()) {
return value;
}
}
return null;
}
}原型模式
原型模式虽然是创建型的模式,但是与工厂模式没有关系,从名字即可看出,该模式的思想就是将一个对象作为原型,对其进行复制、克隆,产生一个和原对象类似的新对象。
先创建一个原型类
public class Prototype implements Cloneable {
public Object clone() throws CloneNotSupportedException {
Prototype proto = (Prototype) super.clone();
return proto;
}
}很简单,一个原型类,只需要实现Cloneable接口,覆写clone方法,此处clone方法可以改成任意的名称,因 为Cloneable接口是个空接口,你可以任意定义实现类的方法名,如cloneA或者cloneB,因为此处的重点是 super.clone()这句话,super.clone()调用的是Object的clone()方法
深浅复制
浅复制:将一个对象复制后,基本数据类型的变量都会重新创建,而引用类型,指向的还是原对象所指 向的。
深复制:将一个对象复制后,不论是基本数据类型还有引用类型,都是重新创建的。简单来说,就是深复制进行了完全彻底的复制,而浅复制不彻底。
写一个深浅复制的例子
public class Prototype implements Cloneable, Serializable {
private static final long serialVersionUID = 1L;
// 简单类型或者String类型
private String name;
// 引用类型
private SerializableObject object;
/* 浅复制 */
public Object shallowClone() throws CloneNotSupportedException {
// super.clone()其实就是调用了Object对象的clone方法
// Object对象的clone方法是调用了native方法去在JVM中实现对象复制。
Prototype proto = (Prototype) super.clone();
return proto;
}
/*
* 深复制
*
* 要实现深复制,需要采用流的形式读入当前对象的二进制输入,再写出二进制数据对应的对象。
*/
public Object deepClone() throws IOException, ClassNotFoundException {
/* 将对象序列化到二进制流 */
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(this);
/* 从二进制流中读出产生的新对象 */
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
return ois.readObject();
}
class SerializableObject implements Serializable {
private static final long serialVersionUID = 1L;
}建造者模式
定义:将一个复杂对象的构造与它的表示分离,使同样的构建过程可以创建不同的表示,这样的设计模式被称为建造者模式。
在建造者模式中存在以下4个角色:
- 产品(Product)类:它是包含多个组成部件的复杂对象,由具体建造者来创建其各个部件。
- 抽象建造者(Builder):它是一个包含创建产品各个子部件的抽象方法的接口,通常还包含一个返回复杂产品的方法 getResult()。
- 具体建造者(Concrete Builder):实现 Builder 接口,完成复杂产品的各个部件的具体创建方法。
- 导演(Director)类:它调用建造者对象中的部件构造与装配方法完成复杂对象的创建,在指挥者中不涉及具体产品的信息。
建造者(Builder)模式和工厂模式的关注点不同:建造者模式注重零部件的组装过程,而工厂方法模式更注重零部件的创建过程,但两者可以结合使用。

导演类:按照一定的顺序或者一定的需求去组装一个产品。 构造者类:提供对产品的不同个性化定制,最终创建出产品。 产品类:最终的产品
使用建造者模式DIY手机
产品角色(Product):Phone
抽象建造者(Builder):AbstracPhoneBuilder
具体建造者(Concrete Builder):PhoneBuilder
创建的东西细节复杂,还必须暴露给使用者。屏蔽过程而不屏蔽细节
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AMd2q3sT-1655302776622)(https://cyb-xh.oss-cn-hangzhou.aliyuncs.com/img/image-20220615212743433.png)]
//定义产品Phone
public class Phone {
protected String cpu;
protected String mem;
protected String disk;
protected String cam;
public String getCpu() {
return cpu;
}
public String getMem() {
return mem;
}
public String getDisk() {
return disk;
}
public String getCam() {
return cam;
}
@Override
public String toString() {
return "Phone{" +
"cpu='" + cpu + '\'' +
", mem='" + mem + '\'' +
", disk='" + disk + '\'' +
", cam='" + cam + '\'' +
'}';
}
}//定义抽象建造者,定义抽象方法和最终返回对象的方法get()
public abstract class AbstractBuilder {
Phone phone;
//返回本体可实现链式调用,当然你也可以返回void
abstract AbstractBuilder customCpu(String cpu);
abstract AbstractBuilder customMem(String mem);
abstract AbstractBuilder customDisk(String disc);
abstract AbstractBuilder customCam(String cam);
public Phone get(){
return phone;
}
}//具体建造者定义建造细节
public class XiaomiBuilder extends AbstractBuilder {
public XiaomiBuilder() {
phone = new Phone();
}
@Override
AbstractBuilder customCpu(String cpu) {
phone.cpu = cpu;
return this;
}
@Override
AbstractBuilder customMem(String mem) {
phone.mem = mem;
return this;
}
@Override
AbstractBuilder customDisk(String disc) {
phone.disk = disc;
return this;
}
@Override
AbstractBuilder customCam(String cam) {
phone.cam = cam;
return this;
}
}最终实现链式调用DIV你的手机了
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vmvRFbOZ-1655302776623)(https://cyb-xh.oss-cn-hangzhou.aliyuncs.com/img/image-20220615214814231.png)]
结构型设计模式
结构型模式关注点“怎样组合对象/类?”所以我们关注下类的组合关系
类结构型模式关心类的组合,由多个类可以组合成一个更大的(继承)
对象结构型模式关心类与对象的组合,通过关联关系在一个类中定义另一个类的实例对象(组合)
根据“合成复用原则”,在系统中尽量使用关联关系来替代继承关系,因此大部分结构型模式都是对象结构型模式。
- 适配器模式(Adapter Pattern):两个不兼容接口之间适配的桥梁
- 桥接模式(Bridge Pattern):相同功能抽象化与实现化解耦,抽象与实现可以独立升级。
- 过滤器模式(Filter、Criteria Pattern):使用不同的标准来过滤一组对象
- 组合模式(Composite Pattern):相似对象进行组合,形成树形结构
- 装饰器模式(Decorator Pattern):向一个现有的对象添加新的功能,同时又不改变其结构
- 外观模式(Facade Pattern):向现有的系统添加一个接口,客户端访问此接口来隐藏系统的复杂性。
- 享元模式(Flyweight Pattern):尝试重用现有的同类对象,如果未找到匹配的对象,则创建新对象
- 代理模式(Proxy Pattern):一个类代表另一个类的功能
代理模式
定义: 为其他对象提供一种代理以控制这个对象的访问。
静态代理
代理类和被代理类实现同一个接口,代理类有一个被代理类的成员变量

public interface Sourceable {
public void method();
}
public class Source implements Sourceable {
@Override
public void method() {
System.out.println("the original method!");
}
}
public class Proxy implements Sourceable {
private Source source;
public Proxy(){
super();
this.source = new Source();
}
@Override
public void method() {
before();
source.method();
atfer();
}
private void atfer() {
System.out.println("after proxy!");
}
private void before() {
System.out.println("before proxy!");
}
}动态代理
JDK动态代理
基于接口去实现的动态代理,实现InvocationHandler接口,重写invoke()方法
public class JDKProxyFactory implements InvocationHandler {
// 目标对象的引用
private Object target;
// 通过构造方法将目标对象注入到代理对象中
public JDKProxyFactory(Object target) {
super();
this.target = target;
}
/**
* @return
*/
public Object getProxy() {
// 如何生成一个代理类呢?
// 1、编写源文件(java文件)----目录类接口interface实现类(调用了目标对象的方法)
// 2、编译源文件为class文件
// 3、将class文件加载到JVM中(ClassLoader)
// 4、将class文件对应的对象进行实例化(反射)
// Proxy是JDK中的API类
// 第一个参数:目标对象的类加载器
// 第二个参数:目标对象的接口
// 第二个参数:代理对象的执行处理器
Object object = Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(),
this);
return object;
}
/**
* 代理对象会执行的方法
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("这是jdk的代理方法");
// 下面的代码,是反射中的API用法
// 该行代码,实际调用的是[目标对象]的方法
// 利用反射,调用[目标对象]的方法
Object returnValue = method.invoke(target, args);
//增强的部分
return returnValue;
}CGLib动态代理
是通过子类继承父类的方式去实现的动态代理,不需要接口。实现MethodInterceptor接口,重写intercept()方法
public class CgLibProxyFactory implements MethodInterceptor {
/**
* @param clazz
* @return
*/
public Object getProxyByCgLib(Class clazz) {
// 创建增强器
Enhancer enhancer = new Enhancer();
// 设置需要增强的类的类对象
enhancer.setSuperclass(clazz);
// 设置回调函数
enhancer.setCallback(this);
// 获取增强之后的代理对象
return enhancer.create();
}
/***
* Object proxy:这是代理对象,也就是[目标对象]的子类
* Method method:[目标对象]的方法
* Object[] arg:参数
* MethodProxy methodProxy:代理对象的方法
*/
@Override
public Object intercept(Object proxy, Method method, Object[] arg, MethodProxy methodProxy) throws Throwable {
// 因为代理对象是目标对象的子类
// 该行代码,实际调用的是父类目标对象的方法
System.out.println("这是cglib的代理方法");
// 通过调用子类[代理类]的invokeSuper方法,去实际调用[目标对象]的方法
Object returnValue = methodProxy.invokeSuper(proxy, arg);
// 代理对象调用代理对象的invokeSuper方法,而invokeSuper方法会去调用目标类的invoke方法完成目标对象的调用
return returnValue;
}
}装饰器模式
定义: 指在不改变现有对象结构的情况下,动态地给该对象增加一些职责(即增加其额外功能)的模式
适配器是连接两个类,可以增强一个类,装饰器是增强一个类
向一个现有的对象添加新的功能,同时又不改变其结构。属于对象结构型模式。
创建了一个装饰类,用来包装原有的类,并在保持类方法签名完整性的前提下,提供了额外的功能。

Source类是被装饰类,Decorator类是一个装饰类,可以为Source类动态的添加一些功能
装饰模式主要包含以下角色
- 抽象构件(Component)角色:是一个抽象类或者接口,定义最核心的对象,也就是最原始的对象,例如上 面的成绩单。
- 具体构件(Concrete Component)角色:实现抽象构件,通过装饰角色为其添加一些职责。
- 抽象装饰(Decorator)角色:一般是一个抽象类,继承抽象构件,实现其抽象方法,里面不一定有抽象的方 法,在他的属性里一般都会有一个private变量指向Component抽象构件 。
- 具体装饰(ConcreteDecorator)角色:实现抽象装饰的相关方法,并给具体构件对象添加附加的责任。
优点: 采用装饰模式扩展对象的功能比采用继承方式更加灵活。 可以设计出多个不同的具体装饰类,创造出多个不同行为的组合。 其缺点:装饰模式增加了许多子类,如果过度使用会使程序变得很复杂。
应用场景
- 当需要给一个现有类添加附加职责,而又不能采用生成子类的方法进行扩充时。例如,该类被隐藏或者该类 是终极类或者采用继承方式会产生大量的子类。
- 当需要通过对现有的一组基本功能进行排列组合而产生非常多的功能时,采用继承关系很难实现,而采用装饰模式却很好实现。
- 当对象的功能要求可以动态地添加,也可以再动态地撤销时。
- 需要为一批的兄弟类进行改装或者加装功能的时候,可以首选装饰模式。
装饰模式和代理模式的区别
对装饰器模式来说,装饰者(decorator)和被装饰者(decoratee)都实现同一个 接口。对代理模式来说,代理类(proxy class)和真实处理的类(real class)都实现同一个接口。他们之间的边界确实比较模糊,两者都是对类的方法进行扩展,具体区别如下:
1、装饰器模式强调的是增强自身,在被装饰之后你能够在被增强的类上使用增强后的功能。增强后你还是你,只不过能力更强了而已;代理模式强调要让别人帮你去做一些本身与你业务没有太多关系的职责(记录日志、设置缓存)。代理模式是为了实现对象的控制,因为被代理的对象往往难以直接获得或者是其内部不想暴露出来。
2、装饰模式是以对客户端透明的方式扩展对象的功能,是继承方案的一个替代方案;代理模式则是给一个对象提供一个代理对象,并由代理对象来控制对原有对象的引用;
3、装饰模式是为装饰的对象增强功能;而代理模式对代理的对象施加控制,但不对对象本身的功能进行增强;
亨元模式
定义:运用共享技术有效地支持大量细粒度对象的复用。系统只使用少量的对象,而这些对象都很相似,状态变化很小,可以实现对象的多次复用。对象结构型
在享元模式中可以共享的相同内容称为内部状态(IntrinsicState),而那些需要外部环境来设置的不能共享的内容称为外部状态****(Extrinsic State),由于区分了内部状态和外部状态,因此可以通过设置不同的外部状态使得相同的对象可以具有一些不同的特征,而相同的内部状态是可以共享的。
在享元模式中通常会出现工厂模式,需要创建一个享元工厂来负责维护一个享元池(Flyweight Pool)用于存储具有相同内部状态的享元对象。
享元模式的结构
享元模式中存在以下两种状态:
内部状态,即不会随着环境的改变而改变的可共享部分;
外部状态,指随环境改变而改变的不可以共享的部分。
享元模式的实现要领就是区分应用中的这两种状态, 并将外部状态外部化。

- 抽象享元角色(Flyweight):简单理解就是一个产品的抽象类,同时定义出对象的外部状态和内部状态的接口 或者实现。
- 具体享元(Concrete Flyweight)角色:实现抽象享元角色中所规定的接口。
- 非享元(Unsharable Flyweight)角色:是不可以共享的外部状态,它以参数的形式注入具体享元的相关方法 中。
- 享元工厂(Flyweight Factory)角色:负责创建和管理享元角色,一般是简单工厂。当客户对象请求一个享元对象时,享元工厂检査系统中是否存在符合要求的享元对象,如果存在则提供给客户;如果不存在的话,则创建一个新的享元对象。
享元模式的优缺点:
优点: 大大减少应用程序创建的对象,相同对象只要保存一份,,降低程序内存的占用,这降低了系统中对象的数量,从 而降低了系统中细粒度对象给内存带来的压力。
缺点:
- 为了使对象可以共享,需要将一些不能共享的状态外部化,这将增加程序的复杂性。
- 读取享元模式的外部状态会使得运行时间稍微变长。
享元模式和池化技术
适配器模式
定义:将某个类的接口转换成客户端期望的另一个接口表示,目的是消除由于接口不匹配所造成的类的兼容性问题
模式的结构
- 目标(Target)角色:该角色定义把其他类转换为何种接口,也就是我们的期望接口。它可以是抽象类或接 口。
- 源(Adaptee)角色:你想把谁转换为目标角色,这个“谁”就是源角色,他是已经存在的、运行良好的类或者 对象,经过适配器角色的包装,他会成为一个新的角色。
- 适配器(Adapter)角色:是适配器模式的核心角色,其他两个角色都是已经存在的角色,而适配器角色是需要新建立的,他的职责很简单:把原角色转换为目标角色。如何转换?通过继承或者类关联的方式。
适配器模式的优缺点
该模式的主要优点如下。
客户端通过适配器可以透明地调用目标接口。 复用了现存的类,程序员不需要修改原有代码而重用现有的适配者类。 将目标类和适配者类解耦,解决了目标类和适配者类接口不一致的问题。
其缺点是:对类适配器来说,更换适配器的实现过程比较复杂。

桥接模式
- 将抽象与实现解耦,使两者都可以独立变化
- 在现实生活中,某些类具有两个或多个维度的变化,如图形既可按形状分,又可按颜色分。如何设计类似于 Photoshop 这样的软件,能画不同形状和不同颜色的图形呢?如果用继承方式,m 种形状和 n 种颜色的图形就有 m×n 种,不但对应的子类很多,而且扩展困难。不同颜色和字体的文字、不同品牌和功率的汽车
- 桥接将继承转为关联,降低类之间的耦合度,减少代码量
桥接(Bridge)模式包含以下主要角色。
- 抽象化(Abstraction)角色:定义抽象类,并包含一个对实现化对象的引用。
- 扩展抽象化(Refined Abstraction)角色:是抽象化角色的子类,实现父类中的业务方法,并通过组合关系调用实现化角色中的业务方法。
- 实现化(Implementor)角色:定义实现化角色的接口,供扩展抽象化角色调用。
- 具体实现化(Concrete Implementor)角色:给出实现化角色接口的具体实现
外观模式
又叫作门面模式,是一种通过为多个复杂的子系统提供一个一致的接口,而使这些子系统更加容易被访问的模式。

应用场景
- JAVA 的三层开发模式。
- 分布式系统的网关
- Tomcat源码中的RequestFacade
组合模式
把一组相似的对象当作一个单一的对象。如:树形菜单

行为型设计模式
行为型模式关注点"怎样运行对象/类?"
行为型模式用于描述程序在运行时复杂的流程控制,
描述多个类或对象之间怎样相互协作共同完成单个对象都无法单独完成的任务,它涉及算法与对象间职责的分配。
行为型模式分为类行为模式和对象行为模式,前者采用继承机制来在类间分派行为,后者采用组合或聚合在对象间分配行为。由于组合关系或聚合关系比继承关系耦合度低,满足“合成复用原则”,所以对象行为模式比类行为模式具有更大的灵活性。
- 模板方法(Template Method)模式:父类定义算法骨架,某些实现放在子类
- 策略(Strategy)模式:每种算法独立封装,根据不同情况使用不同算法策略
- 状态(State)模式:每种状态独立封装,不同状态内部封装了不同行为
- 命令(Command)模式:将一个请求封装为一个对象,使发出请求的责任和执行请求的责任分割开
- 职责链(Chain of Responsibility)模式:所有处理者封装为链式结构,依次调用
- 备忘录(Memento)模式:把核心信息抽取出来,可以进行保存
- 解释器(Interpreter)模式:定义语法解析规则
- 观察者(Observer)模式:维护多个观察者依赖,状态变化通知所有观察者
- 中介者(Mediator)模式:取消类/对象的直接调用关系,使用中介者维护
- 迭代器(Iterator)模式:定义集合数据的遍历规则
- 访问者(Visitor)模式:分离对象结构,与元素的执行算法
除了模板方法模式和解释器模式是类行为型模式,其他的全部属于对象行为型模式
模板方法模式
在模板模式中,一个抽象类公开定义了执行它的方法的方式模板。它的子类可以按需要重写方法实现,但调用将以抽象类中定义的方式进行。
应用场景
- Spring的整个继承体系都基本用到模板方法;
- BeanFactory.getBean(1,2,3,4)–A1—A2—A3—A4(全部被完成)
- JdbcTemplate、RedisTemplate都允许我们再扩展…
策略模式
定义了一系列算法,并将每个算法封装起来,使它们可以相互替换,且算法的变化不会影响使用算法的客户。属于对象行为模式。

策略模式的主要角色如下。
抽象策略(Strategy)类:公共接口,各种不同的算法以不同的方式实现这个接口,环境角色使用这个接口调用不同的算法,一般使用接口或抽象类实现。
具体策略(Concrete Strategy)类:实现了抽象策略定义的接口,提供具体的算法实现。
环境(Context)类:持有一个策略类的引用,最终给客户端调用。
应用场景
- 使用策略模式可以避免使用多重条件语句,如 if…else 语句、switch…case 语句
- 线程池拒绝策略
状态模式
对有状态的对象,把复杂的“判断逻辑”提取到不同的状态对象中,允许状态对象在其内部状态发生改变时改变其行为

状态模式包含以下主要角色。
环境类(Context)角色:也称为上下文,它定义了客户端需要的接口,内部维护一个当前状态,并负责具体状态的切换。
抽象状态(State)角色:定义一个接口,用以封装环境对象中的特定状态所对应的行为,可以有一个或多个行为。
具体状态(Concrete State)角色:实现抽象状态所对应的行为,并且在需要的情况下进行状态切换。
应用场景
- 状态模式核心需要具体状态类能在必要的时候切换状态
- 流程框架与状态机
中介者模式
- 用一个中介对象来封装一系列的对象交互,中介者使各对象不需要显式地相互引用,减少对象间混乱的依赖关系,从而使其耦合松散,而且可以独立地改变它们之间的交互。
- 对象行为型模式
- 将网状转化为星状交互关系

应用场景
- SpringMVC 的 DispatcherServlet是一个中介者,他会提取Controller、Model、View来进行调用。而无需controller直接调用view之类的渲染方法
- 分布式系统中的网关
- 迪米特法则的一个典型应用
- 中介者和外观(门面)模式区别?
- 中介者双向操作,门面偏向于封装某一方
观察者模式
定义对象间的一种一对多依赖关系,使得每当一个对象状态发生改变时,其相关依赖对象皆得到通知并被自动更新。观察者模式又叫做发布-订阅(Publish/Subscribe)模式、模型-视图(Model/View)模式、源-监听器(Source/Listener)模式或从属者(Dependents)模式。对象行为型模式

应用场景
- Spring事件机制
- Vue的双向绑定核心
- 响应式编程核心思想
备忘录模式
在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,以便以后当需要时能将该对象恢复到原先保存的状态。该模式又叫快照模式。对象行为型模式

发起人(Originator)角色:记录当前时刻的内部状态信息,提供创建备忘录和恢复备忘录数据的功能,实现其他业务功能,它可以访问备忘录里的所有信息。
备忘录(Memento)角色:负责存储发起人的内部状态,在需要的时候提供这些内部状态给发起人。
管理者(Caretaker)角色:对备忘录进行管理,提供保存与获取备忘录的功能,但其不能对备忘录的内容进行访问与修改。
应用场景
- 游戏存档
- 数据库保存点事务(savepoint)
- session活化钝化
解释器模式
给分析对象定义一个语言,并定义该语言的文法表示,再设计一个解析器来解释语言中的句子。也就是说,用编译语言的方式来分析应用中的实例。这种模式实现了文法表达式处理的接口,该接口解释一个特定的上下文。类行为型模式
- 抽象表达式(Abstract Expression)角色:
- 定义解释器的接口,约定解释器的解释操作,主要包含解释方法 interpret()。
- 终结符表达式(Terminal Expression)角色:
- 是抽象表达式的子类,用来实现文法中与终结符相关的操作,文法中的每一个终结符都有一个具体终结表达式与之相对应。
- 非终结符表达式(Nonterminal Expression)角色:
- 也是抽象表达式的子类,用来实现文法中与非终结符相关的操作,文法中的每条规则都对应于一个非终结符表达式。
- 环境(Context)角色:
- 通常包含各个解释器需要的数据或是公共的功能,一般用来传递被所有解释器共享的数据,后面的解释器可以从这里获取这些值。
- 客户端(Client):
- 主要任务是将需要分析的句子或表达式转换成使用解释器对象描述的抽象语法树,然后调用解释器的解释方法,当然也可以通过环境角色间接访问解释器的解释方法。
应用场景
- Spring的表达式解析:#{}
- Thymeleaf等模板引擎的语法解析
- 编译原理
- 编译器…
- execution(* com.atguigu…(int,…))
命令模式
将一个请求封装为一个对象,使发出请求的责任和执行请求的责任分割开。这样两者之间通过命令对象进行沟通,这样方便将命令对象进行储存、传递、调用、增加与管理。
- 抽象命令类(Command)角色:
- 声明执行命令的接口,拥有执行命令的抽象方法 execute()。
- 具体命令类(Concrete Command)角色:
- 是抽象命令类的具体实现类,它拥有接收者对象,并通过调用接收者的功能来完成命令要执行的操作。
- 实现者/接收者(Receiver)角色:
- 执行命令功能的相关操作,是具体命令对象业务的真正实现者。
- 调用者/请求者(Invoker)角色:
- 是请求的发送者,它通常拥有很多的命令对象,并通过访问命令对象来执行相关请求,它不直接访问接收者。
应用场景
- mvc就是典型的命令模式
- 当系统需要执行一组操作时,命令模式可以定义宏命令(一个命令组合了多个命令)来实现该功能。
- 结合备忘录模式还可以实现命令的撤销和恢复
迭代器(Iterator)模式
提供一个对象(迭代器)来顺序访问聚合对象(迭代数据)中的一系列数据,而不暴露聚合对象的内部表示。对象行为型模式
应用场景
- jdk容器接口的Iterator定义
- 现实开发中,我们几乎无需编写迭代器,基本数据结构链表、树、图的迭代器已经都有了。除非要重写迭代逻辑
访问者模式
将作用于某种数据结构中的各元素的操作分离出来封装成独立的类,使其在不改变数据结构的前提下可以添加作用于这些元素的新的操作,为数据结构中的每个元素提供多种访问方式。它将对数据的操作与数据结构进行分离,是类行为模式中最复杂的一种模式。
- 抽象访问者(Visitor)角色:
- 定义一个访问具体元素的接口,为每个具体元素类对应一个访问操作 visit() ,该操作中的参数类型标识了被访问的具体元素。
- 具体访问者(ConcreteVisitor)角色:
- 实现抽象访问者角色中声明的各个访问操作,确定访问者访问一个元素时该做什么。
- 抽象元素(Element)角色:
- 声明一个包含接受操作 accept() 的接口,被接受的访问者对象作为 accept() 方法的参数。
- 具体元素(ConcreteElement)角色:
- 实现抽象元素角色提供的 accept() 操作,其方法体通常都是 visitor.visit(this) ,另外具体元素中可能还包含本身业务逻辑的相关操作。
- 对象结构(Object Structure)角色:
- 是一个包含元素角色的容器,提供让访问者对象遍历容器中的所有元素的方法,通常由 List、Set、Map 等聚合类实现。

- 在访问者模式中,每增加一个新的元素类,都要在每一个具体访问者类中增加相应的具体操作,这违背了“开闭原则”。
- 违反依赖倒置原则。访问者模式依赖了具体类,而没有依赖抽象类
- 破坏封装。访问者模式中具体元素对访问者公布细节,这破坏了对象的封装性
- 应用于对象结构相对稳定,但其操作算法经常变化的程序。
职责链模式
为了避免请求发送者与多个请求处理者耦合在一起,于是将所有请求的处理者通过前一对象记住其下一个对象的引用而连成一条链;当有请求发生时,可将请求沿着这条链传递,直到有对象处理它为止。属于对象行为型模式
- 抽象处理者(Handler)角色:
- 定义一个处理请求的接口,包含抽象处理方法和一个后继连接。
- 具体处理者(Concrete Handler)角色:
- 实现抽象处理者的处理方法,判断能否处理本次请求,如果可以处理请求则处理,否则将该请求转给它的后继者。
- 客户类(Client)角色:
- 创建处理链,并向链头的具体处理者对象提交请求,它不关心处理细节和请求的传递过程。
应用场景
- Tomcat的Pipeline、Valve
- Filter链
- Aop责任链
















