- 设计原则
- 创建型
- 单例模式
- 原型模式
- 工厂方法模式
- 抽象工厂模式
- 建造者模式
- 结构型
- 代理模式
- 适配器模式
- 桥接模式
- 装饰器模式
- 外观模式
- 享元模式
- 组合模式
- 行为型
- 模板方法模式
- 策略模式
- 命令模式
- 职责链模式
- 状态模式
- 观察者模式
- 中介者模式
- 迭代器模式
- 访问者模式
- 备忘录模式
- 解释器模式
设计原则
单一职责原则:一个类应该有且仅有一个引起它变化的原因(变更风险),否则类应该被拆分,也就是说每个类都只负责单一的功能,切不可太多。
里氏替换原则:子类可以扩展父类的功能,但不能改变父类原有的功能。也就是说:子类继承父类时,除添加新的方法完成新增功能外,尽量不要重写父类的方法。如果重写父类方法,运用多态比较频繁时,程序运行出错的概率会非常大。
接口隔离原则:一个接口拥有的行为应该尽可能的小。要为各个类建立它们需要的专用接口,而不要试图去建立一个很庞大的接口供所有依赖它的类去调用。
依赖倒置原则:高层模块不应该依赖低层模块,两者都应该依赖其抽象;抽象不应该依赖细节,细节应该依赖抽象。目的是通过要面向接口的编程来降低类间的耦合性,比如每个类尽量提供接口或抽象类,或者两者都具备,比如变量的声明类型尽量是接口或者是抽象类。
迪米特原则:高内聚,低耦合。一个类只依赖应该依赖的对象,只暴露应该暴露的方法。
开闭原则:软件实体应当对扩展开放,对修改关闭。当应用的需求改变时,在不修改软件实体的源代码或者二进制代码的前提下,可以扩展模块的功能,使其满足新的需求。开闭原则是面向对象程序设计的终极目标。
创建型
创建型模式的核心是“创建对象”,目标是“将对象的创建与使用分离”。
单例模式
一个类只有一个实例,该单例对象必须由单例类自行创建,然后对外提供一个访问该单例的全局访问点。
饿汉式单例
类一旦加载就创建一个单例,保证在调用 getInstance 方法之前单例已经存在了。
缺点是一旦访问了 Singleton 的任何其他的静态域,就会造成实例的初始化,如果从始至终就没有使用这个实例,会造成内存的浪费。
public class Singleton {
private static final Singleton instance = new Singleton();
// private 避免类在外部被实例化
private Singleton(){}
public static Singleton getInstance()
{
return instance;
}
}
懒汉式单例(双重检查加锁)
类加载时没有生成单例,只有当第一次调用 getlnstance 方法时才去创建这个单例。
public class Singleton {
private static volatile Singleton instance;
private Singleton(){}
public static Singleton getInstance() {
// 双重校验锁
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
instance 变量需要用 volatile 关键字修饰。因为 instance = new Singleton(); 这段代码,即创建 Singleton 对象的过程,是分三步进行的:
1.为 instance 分配内存空间
2.初始化 instance
3.将 instance 指向分配的内存地址
由于 JVM 会进行指令重排,执行顺序有可能变成 1->3->2。假如线程 T1 执行了 1 和 3,此时 T2 调用 getInstance() 发现 instance 不为空,因此返回未初始化的 instance,显然是错误的。使用 volatile 可以禁止 JVM 的指令重排,保证在多线程环境下也能正常运行。
应用
单例模式避免了频繁的创建销毁对象,可以提高性能,适用于需要频繁实例化然后销毁,或者创建对象时耗时(耗资源)过多但又经常用到的对象。一个典型的例子就是 Spring 中的 bean,Spring 中对象创建在 Bean 作用域中也只创建一个。(上面的两个例子稍微有点不同,它们的作用域是整个应用上下文,而不是某个作用域。)
原型模式
用一个已经创建的实例作为原型,通过复制该原型对象来创建一个和原型相同或相似的新对象。
Java 提供了对象的 clone() 方法,只需要直接调用即可。可以把 clone 方法看成内存块的复制操作,它的速度比一般的 new 操作要快。需要注意的是,clone 执行的是浅拷贝,只拷贝引用。如果想要实现深度拷贝,需要重写 clone 方法,或者实现自己的克隆方法。
工厂方法模式
定义一个创建产品对象的工厂接口,将产品对象的实际创建工作推迟到具体子工厂类当中。这满足创建型模式中所要求的“创建与使用相分离”的特点。被创建的对象称为“产品”,把创建产品的对象称为“工厂”。
所以工厂方法模式主要角色有四类角色:抽象工厂(Abstract Factory)、具体工厂(Concrete Factory)、抽象产品(Product)、具体产品(ConcreteProduct)。
工厂方法的缺点是每增加一个产品就要增加一个具体产品类和一个对应的具体工厂类,这增加了系统的复杂度。
应用
在 JDK 中的 Object 对象和其 toString() 方法中,Object 类相当于抽象工厂,Object.toString() 产生的 String 对象相当于抽象产品。继承自 Object 的类相当于具体工厂,在这些类中实现了 toString() 方法,其产生的 String 对象为具体产品。
抽象工厂模式
抽象工厂模式与工厂方法模式的区别是,其工厂的接口里是一系列创造抽象产品的方法,而不再是一个,而相应的,抽象产品也不再是一个了,而是一系列相关的产品。这其实是工厂方法模式的一种扩展。
抽象工厂模式也由四类角色组成:抽象工厂(Abstract Factory)、具体工厂(Concrete Factory)、抽象产品(Product)、具体产品(ConcreteProduct)。
应用
和 List 相关的 iterator() 和 listIterator() 方法,List 相当于抽象工厂,把 Iterator 看成抽象产品 A,ListIterator 看成抽象产品 B。List 有多个实现类,最常用的有 ArrayList 和 LinkedList。ArrayList 相当于具体工厂,由 ArrayList 产生的 Iterator 是抽象产品 A 对应的具体产品,ListIterator 是抽象产品 B 对应的具体产品。LinkedList 同理。ListIterator 和 Iterator 的功能有一定区别,是两种“产品”。
建造者模式
将一个复杂对象的构造与它的表示分离,使同样的构建过程可以创建不同的表示,这样的设计模式被称为建造者模式。
工厂模式注重把这个产品创造出来即可,而建造者更关心创建的细节,当创建一个对象需要使用很多步骤去完成的时候,我们可以考虑建造者模式,当创建一个对象比较简单的时候,我们就可以使用工厂模式
主要有以下几个角色:
- 产品角色(Product):它是包含多个组成部件的复杂对象,由具体建造者来创建其各个部件。
- 抽象建造者(Builder):它是一个包含创建产品各个子部件的抽象方法的接口,通常还包含一个返回复杂产品的方法 getResult()。
- 具体建造者(Concrete Builder):实现 Builder 接口,完成复杂产品的各个部件的具体创建方法。
- 指挥者(Director):它调用建造者对象中的部件构造与装配方法完成复杂对象的创建,在指挥者中不涉及具体产品的信息。
应用
StringBuilder 的 append 方法应用了建造者模式。StringBuilder 继承自 AbstractStringBuilder,AbstractStringBuilder 实现了 Appendable 接口。Appendable 的角色为抽象建造者,定义了建造方法,AbstractStringBuilder 为具体建造者,实现了 Appendable 接口的 append 方法,StringBuilder 继承了 AbstractStringBuilder,既是指挥者,也是产品角色。
结构型
结构型模式描述如何将类或对象按某种布局组成更大的结构。它分为类结构型模式和对象结构型模式,前者采用继承机制来组织接口和类,后者釆用组合或聚合来组合对象。
代理模式
代理模式中主要有以下三个角色:
- 抽象主题(Subject)类:通过接口或抽象类声明真实主题和代理对象实现的业务方法。
- 真实主题(Real Subject)类:实现了抽象主题中的具体业务,是代理对象所代表的真实对象,是最终要引用的对象。
- 代理(Proxy)类:提供了与真实主题相同的接口,其内部含有对真实主题的引用,它可以访问、控制或扩展真实主题的功能。
通过定义一个继承抽象主题的代理来包含真实主题,从而实现对真实主题的访问。
适配器模式
适配器模式的定义是,将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类能一起工作。
适配器模式从实现方式上分为两种,类适配器和对象适配器,这两种的区别在于实现方式上的不同,一种采用继承,一种采用组合的方式。
适配器模式包含以下主要角色:
- 目标(Target)接口:当前系统业务所期待的接口,它可以是抽象类或接口。
- 适配者(Adaptee)类:它是被访问和适配的现存组件库中的组件接口。
- 适配器(Adapter)类:它是一个转换器,通过继承或引用适配者的对象,把适配者接口转换成目标接口,让客户按目标接口的格式访问适配者。
应用
系统中有一套完整的类结构,而我们需要利用其中某一个类的功能(通俗点说可以说是方法),但是我们只认识另外一个和这个类结构不相关的接口,这时候使用适配器模式适配,这个我们认识的接口称为目标接口,系统中已存在的那个类成为适配者类,创造一个新的类(继承/组合)目标接口和适配者类,这个新创造的类称为适配器类。
典型如 Arrays 类中的 asList() 方法,从源码中可以看到,其实例化了一个“ArrayList” 对象(注意,此 “ArrayList” 并不是 java.util.ArrayList,而是Arrays 的内部类,继承自 AbstractList)。asList 充当了基于数组的 API 与基于 Collection 的 API 之间的桥梁,但是 asList 只是实现了 set、get 等方法,且所有操作仍然是基于数组,修改的是数组的数据,所以不能把 asList 获得的 List 当成 java.util.ArrayList,况且其没有重写 add,remove 方法(数组不支持 add 和 remove)。
桥接模式
将抽象与实现分离,使它们可以独立变化。它是用组合关系代替继承关系来实现,从而降低了抽象和实现这两个可变维度的耦合度。
桥接模式包含以下主要角色:
- 抽象化(Abstraction)角色:定义抽象类,并包含一个对实现化对象的引用。
- 扩展抽象化(Refined Abstraction)角色:是抽象化角色的子类,实现父类中的业务方法,并通过组合关系调用实现化角色中的业务方法。
- 实现化(Implementor)角色:定义实现化角色的接口,供扩展抽象化角色调用。
- 具体实现化(Concrete Implementor)角色:给出实现化角色接口的具体实现。
核心在于将实现部分组合到抽象部分中。
装饰器模式
装饰器模式指的是在不改变现有对象结构的情况下,动态地给该对象增加一些职责(即增加其额外功能)。
如果使用组合关系来创建一个包装对象(即装饰对象)来包裹真实对象,并在保持真实对象的类结构不变的前提下,为其提供额外的功能,这就是装饰模式的目标。
装饰模式主要包含以下角色:
- 抽象构件(Component)角色:定义一个抽象接口以规范准备接收附加责任的对象。
- 具体构件(Concrete Component)角色:实现抽象构件,通过装饰角色为其添加一些职责。
- 抽象装饰(Decorator)角色:继承抽象构件,并包含具体构件的实例,可以通过其子类扩展具体构件的功能。
- 具体装饰(ConcreteDecorator)角色:实现抽象装饰的相关方法,并给具体构件对象添加附加的责任。
应用
Dubbo 中的 Wrapper 类都是装饰器模式,例如 ProtocolFilterWrapper 类把 Protocol 实例当成一个属性,同时又实现了 Protocol。但 ProtocolFilterWrapper 并不是 Protocol 的真正实现,只是在其属性 “Protocol” 上包装了一层,多加了些功能。
外观模式
通过为多个复杂的子系统提供一个一致的接口,而使这些子系统更加容易被访问。
外观模式包含以下主要角色。
- 外观(Facade)角色:为多个子系统对外提供一个共同的接口。
- 子系统(Sub System)角色:实现系统的部分功能,客户可以通过外观角色访问它。
- 客户(Client)角色:通过一个外观角色访问各个子系统的功能。
应用
存在不同功能的类(接口)SubSystem1、SubSystem2、SubSystem3,需要三个类一起完成一项工作,定义外观角色 System,同时将三个类的实例组合到 System 类中,即完成了外观模式的创建。
在 WEB 开发中,action-service-dao 结构应该是最常见的结构。service 层在 action 层和 dao 层之间,起到组合 dao 层暴露给 action 层功能的作用。dao 层是数据层,每个类只是简单的数据操作对象,没有业务逻辑,而且单个 action 通常需要依赖多个 dao 类才能完成,service 赋予 dao 业务逻辑,整合多个 dao 将其暴露给控制层。在这一结构中,action 是客户角色,service 是外观角色,dao 则是子系统角色。
享元模式
使用共享物件,用来尽可能减少内存使用量以及分享资讯给尽可能多的相似物件。享元模式的主要优点是:相同对象只要保存一份,这降低了系统中对象的数量,从而降低了系统中细粒度对象给内存带来的压力。
应用
对于以下代码:
Integer a1 = 3;
Integer b1 = Integer.valueOf(3);
Integer a2 = 200;
Integer b2 = Integer.valueOf(200);
System.out.println(a1 == b1);
System.out.println(a2 == b2);
结果为 true, false。
在 Integer 中已经缓存 -128 - 127 范围内的 Integer 对象,如果 Integer.valueOf 传入参数为此范围内,每一次都将直接获取缓存对象,超出此范围时才会创建新的对象。
组合模式
组合模式适用以下场景:
- 在需要表示一个对象整体与部分的层次结构的场合。
- 要求对用户隐藏组合对象与单个对象的不同,用户可以用统一的接口使用组合结构中的所有对象的场合。
应用
Map 的 putAll(Map) 方法、List 的 addAll(Collection) 方法和 Set 的 addAll(Collection) 均用到了组合模式。例如,putAll 方法可以将另一个 Map 对象放入自己的存储空间中,Map 相当于树枝构建,Map.Node 相当于树叶构件。
行为型
行为型模式用于描述程序在运行时复杂的流程控制,即描述多个类或对象之间怎样相互协作共同完成单个对象都无法单独完成的任务,它涉及算法与对象间职责的分配。
模板方法模式
定义一个操作中的算法骨架,而将算法的一些步骤延迟到子类中,使得子类可以不改变该算法结构的情况下重定义该算法的某些特定步骤。
应用
模板方法模式在 JDK 和各种框架、中间件中应用非常广泛。
-
类加载器 ClassLoader 中的 loadClass 方法就应用了模板方法模式,loadClass 已经定义好了类加载的双亲委派机制流程,在流程中使用到了“未实现的” findClass 方法,供子类扩展。
Java 类加载机制详见 “《深入理解 Java 虚拟机》阅读笔记 - 类加载机制”。 -
JUC 中 AQS 也用到了模板方法模式,抽象父类中定义好了 acquire、release 等方法的基本流程,而其中涉及到的 tryAcquire、tryRelease 等方法则留给具体的子类扩展。
策略模式
该模式定义了一系列算法,并将每个算法封装起来,使它们可以相互替换,且算法的变化不会影响使用算法的客户。策略模式属于对象行为模式,它通过对算法进行封装,把使用算法的责任和算法的实现分割开来,并委派给不同的对象对这些算法进行管理。
应用
Comparator 接口的 compare 方法是策略模式的具体应用。几乎所有需要排序的集合类都需要实现 Comparator 接口用于元素排序,而具体的排序算法(策略)则在 Comparator 的 compare 方法中定义。
命令模式
将一个请求封装为一个对象,使发出请求的责任和执行请求的责任分割开。这样两者之间通过命令对象进行沟通,这样方便将命令对象进行储存、传递、调用、增加与管理,极大地降低了系统的耦合度。
策略模式是通过不同的算法做同一件事情:例如排序,而 命令模式 则是通过不同的命令做不同的事情,常含有(关联)接收者。
应用
Runnable 是命令模式的应用,将请求的执行过程封装到 run 方法中,供请求者调用。
职责链模式
为了避免请求发送者与多个请求处理者耦合在一起,将所有请求的处理者通过前一对象记住其下一个对象的引用而连成一条链;当有请求发生时,可将请求沿着这条链传递,直到有对象处理它为止。
状态模式
对有状态的对象,把复杂的“判断逻辑”提取到不同的状态对象中,允许状态对象在其内部状态发生改变时改变其行为。
状态模式包含以下主要角色:
- 环境(Context)角色:也称为上下文,它定义了客户感兴趣的接口,维护一个当前状态,并将与状态相关的操作委托给当前状态对象来处理。
- 抽象状态(State)角色:定义一个接口,用以封装环境对象中的特定状态所对应的行为。
- 具体状态(Concrete State)角色:实现抽象状态所对应的行为。
状态模式的缺点是会增加系统的类与对象的个数,结构与实现都较为复杂。
应用
一个环境角色有多个状态,需要对多个状态进行选择或判断(if/else),可以把状态封装成多个状态角色类。
观察者模式
指多个对象间存在一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。这种模式有时又称作发布-订阅模式、模型-视图模式。
中介者模式
定义一个中介对象来封装一系列对象之间的交互,使原有对象之间的耦合松散,且可以独立地改变它们之间的交互。
中介者模式包含以下主要角色:
- 抽象中介者(Mediator)角色:它是中介者的接口,提供了同事对象注册与转发同事对象信息的抽象方法。
- 具体中介者(ConcreteMediator)角色:实现中介者接口,定义一个 List 来管理同事对象,协调各个同事角色之间的交互关系,因此它依赖于同事角色。
- 抽象同事类(Colleague)角色:定义同事类的接口,保存中介者对象,提供同事对象交互的抽象方法,实现所有相互影响的同事类的公共功能。
- 具体同事类(Concrete Colleague)角色:是抽象同事类的实现者,当需要与其他同事对象交互时,由中介者对象负责后续的交互。
应用
Executor.execute 和 ExecutorService.submit 使用了中介者模式,Executor 担当中介者角色,对同事类角色 Runnable 进行管理。
迭代器模式
提供一种方法顺序访问一个聚合对象中各个元素,而又不需暴露该对象的内部表示。
应用
Java 集合框架中的 Iterator。
访问者模式
将作用于某种数据结构中的各元素的操作分离出来封装成独立的类,使其在不改变数据结构的前提下可以添加作用于这些元素的新的操作,为数据结构中的每个元素提供多种访问方式。
访问者模式包含以下主要角色:
- 抽象访问者(Visitor)角色:定义一个访问具体元素的接口,为每个具体元素类对应一个访问操作 visit() ,该操作中的参数类型标识了被访问的具体元素。
- 具体访问者(ConcreteVisitor)角色:实现抽象访问者角色中声明的各个访问操作,确定访问者访问一个元素时该做什么。
- 抽象元素(Element)角色:声明一个包含接受操作 accept() 的接口,被接受的访问者对象作为 accept() 方法的参数。
- 具体元素(ConcreteElement)角色:实现抽象元素角色提供的 accept() 操作,其方法体通常都是 visitor.visit(this) ,另外具体元素中可能还包含本身业务逻辑的相关操作。
- 对象结构(Object Structure)角色:是一个包含元素角色的容器,提供让访问者对象遍历容器中的所有元素的方法,通常由 List、Set、Map 等聚合类实现。
备忘录模式
在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,以便以后当需要时能将该对象恢复到原先保存的状态。该模式又叫快照模式。
解释器模式
给分析对象定义一个语言,并定义该语言的文法表示,再设计一个解析器来解释语言中的句子。
参考