创建型
单例模式
实现方式
懒汉式-线程不安全
1 | public class Singleton { |
- 优点:实例化被延迟,没有用到该实例就不会实例化,节约资源。
- 缺点:线程不安全。
饿汉式-线程安全
1 | public class Singleton { |
- 优点:线程安全。
- 缺点:不用也要实例化,占用资源。
懒汉式加锁-线程安全
1 | public class Singleton { |
- 优点:加锁保证线程安全。
- 缺点:当一个线程进入后,其他线程会阻塞,影响性能。
懒汉式双重锁定-线程安全
1 | public class Singleton { |
注意volatile关键字的作用:禁止JVM指令重排序,可见性
在执行 uniqueInstance = new Singleton();
这句的时候,实际上分为三个步骤:
- 为
uniqueInstance
分配内存空间 - 初始化
uniqueInstance
- 将
uniqueInstance
指向分配的内存地址
由于 JVM 具有指令重排的特性,执行顺序有可能变成 1>3>2;在多线程下,线程T1执行了1和3,线程T2执行getUniqueInstance()
方法后发现 uniqueInstance
不为空,因此直接返回uniqueInstance
,但是此时uniqueInstance
实际上还没有初始化。
静态内部类-线程安全
1 | public class Singleton { |
注意: 当Singleton
被加载时,静态内部类SingletonHolder
没有被加载,只有当调用getUniqueInstance()
方法后,才会被加载,并且也能保证只能实例化一次。
注意
如果对象实现了Clone
接口,也可以通过复制的方式来创建对象,所以要严格保证对象不能被除自定义的getInstance
方法之外的其他方式创建类,即不要实现Clone
接口。
单例模式的应用
- 在整个项目需要一个共享访问点或者访问数据
- 创建一个资源消耗资源过多
- 需要定义大量的静态常量和静态方法
- spring中每个bean默认也是单例的
原型模式
角色
- Client(客户)
- Prototype(抽象原型):结构或抽象类
- ConcretePrototype(具体原型):实现或继承抽象原型,是被复制的对象。
复制机制
Object类有一个受保护的clone()方法,可以实现对象的克隆(浅克隆),需要两步:
- 实现Cloneable接口
- 覆盖Object的clone()方法
实现
1 | public interface Prototype extends Cloneable{ |
1 | public class ConcretePrototype implements Prototype { |
1 | public class Client { |
优点
- 性能优良:原型模式是在内存进行二进制流的复制,比直接new一个对象性能好;
- 逃避构造函数的约束:直接在内存中复制,构造函数不会执行,逃避了构造函数的约束,既是优点也是缺点。
适用场景
- 创建新对象成本较大(如初始化需要占用较长的时间,占用太多的CPU资源或网络资源)
工厂三兄弟
简单工厂模式
产品:
1 | public interface Product { |
简单工厂:
1 | public class SimpleFactory { |
客户:
1 | public class Client { |
优点:
- 客户和产品实现解耦,当产品改变时,无需修改客户端,支持开闭原则。
缺点:
- 产品改变或增加需要修改工厂,开闭原则支持不够,不利于扩展;
- 工厂职责太重,包含创建逻辑,一旦出现问题,整个系统都要受到影响。
工厂方法模式
工厂方法把实例化操作推迟到子类。
角色
- 抽象工厂(Factory):抽象工厂可以是接口,也可以是抽象类或者具体类。
- 具体工厂(ConcreteFactory)
- 抽象产品(Product)
- 具体产品(ConcreteProduct)
- 客户端(Client)
1 | interface Factory { |
优点:
- 加入新产品时,无需修改代码,只需添加一个具体产品和一个具体工厂,符合开闭原则
缺点:
- 类成倍增加,更多的类需要编译和运行,会给系统带来一些额外的开销
抽象工厂方法
与工厂方法模式相比,抽象工厂模式中的具体工厂不只是创建一种产品,它负责创建一族产品。
角色
- 抽象工厂:它声明了一组用于创建一族产品的方法,每一个方法对应一种产品。抽象工厂可以是接口,也可以是抽象类或者具体类。
- 具体工厂:同一个具体工厂所创建的产品对象构成了一个产品族。
- 抽象产品
- 具体产品
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93//按钮接口:抽象产品
interface Button {
public void display();
}
//Spring按钮类:具体产品
class SpringButton implements Button {
public void display() {
System.out.println("显示浅绿色按钮。");
}
}
//Summer按钮类:具体产品
class SummerButton implements Button {
public void display() {
System.out.println("显示浅蓝色按钮。");
}
}
//文本框接口:抽象产品
interface TextField {
public void display();
}
//Spring文本框类:具体产品
class SpringTextField implements TextField {
public void display() {
System.out.println("显示绿色边框文本框。");
}
}
//Summer文本框类:具体产品
class SummerTextField implements TextField {
public void display() {
System.out.println("显示蓝色边框文本框。");
}
}
//组合框接口:抽象产品
interface ComboBox {
public void display();
}
//Spring组合框类:具体产品
class SpringComboBox implements ComboBox {
public void display() {
System.out.println("显示绿色边框组合框。");
}
}
//Summer组合框类:具体产品
class SummerComboBox implements ComboBox {
public void display() {
System.out.println("显示蓝色边框组合框。");
}
}
//界面皮肤工厂接口:抽象工厂
interface SkinFactory {
public Button createButton();
public TextField createTextField();
public ComboBox createComboBox();
}
//Spring皮肤工厂:具体工厂
class SpringSkinFactory implements SkinFactory {
public Button createButton() {
return new SpringButton();
}
public TextField createTextField() {
return new SpringTextField();
}
public ComboBox createComboBox() {
return new SpringComboBox();
}
}
//Summer皮肤工厂:具体工厂
class SummerSkinFactory implements SkinFactory {
public Button createButton() {
return new SummerButton();
}
public TextField createTextField() {
return new SummerTextField();
}
public ComboBox createComboBox() {
return new SummerComboBox();
}
}
开闭原则倾斜性
- 增加产品族:对于增加新的产品族,抽象工厂模式很好地支持了“开闭原则”,只需要增加具体产品并对应增加一个新的具体工厂,对已有代码无须做任何修改。
- 增加新的产品等级结构:对于增加新的产品等级结构,需要修改所有的工厂角色,包括抽象工厂类,在所有的工厂类中都需要增加生产新产品的方法,违背了“开闭原则”。
因此要求设计人员在设计之初就能够全面考虑,不会在设计完成之后向系统中增加新的产品等级结构,也不会删除已有的产品等级结构,否则将会导致系统出现较大的修改,为后续维护工作带来诸多麻烦。
适用场景
- 系统中有多于一个的产品族,而每次只使用其中某一产品族
- 属于同一个产品族的产品将在一起使用,这一约束必须在系统的设计中体现出来
- 产品等级结构稳定,设计完成之后,不会向系统中增加新的产品等级结构或者删除已有的产品等级结构。
建造者模式
将一个复杂对象的构建和它的表示分离,使得同样的构建过程可以创建不同的表示。
角色
- 抽象建造者(Builder):规范产品的各个组成部分,可以是抽象类,也可以是接口,一般分两部分,一部分是设置产品的各个部分,一部分是创建产品并返回。
- 具体建造者(ConcreteBuilder):实现Builder中定义的方法,并返回一个组建好的产品实例。
- 产品角色(Product):它是被构建的复杂对象,包含多个组成部件(多个成员属性)。
- 指挥者(Director):负责安排复杂对象的建造次序,客户端一般只需要与指挥者进行交互。
1 | class Product { |
建造产品:
1 | Builder builder = new ConcreteBuilder(); //可通过配置文件实现 |
可以将Director合并到Builder
1 | abstract class ActorBuilder |
优缺点
优点:
- 客户端不必知道产品内部组成的细节,将产品本身与产品的创建过程解耦,使得相同的创建过程可以创建不同的产品对象
- 用户使用不同的具体建造者即可得到不同的产品对象。增加新的具体建造者无须修改原有类库的代码,系统扩展方便,符合“开闭原则”
- 可以更加精细地控制产品的创建过程
缺点:
- 建造者模式所创建的产品一般具有较多的共同点,其组成部分相似,如果产品之间的差异性很大,例如很多组成部分都不相同,不适合使用建造者模式,因此其使用范围受到一定的限制。
适用场景
- 需要生成的产品对象有复杂的内部结构,这些产品对象通常包含多个成员属性。
- 需要生成的产品对象的属性相互依赖,需要指定其生成顺序。
- 隔离复杂对象的创建和使用,并使得相同的创建过程可以创建不同的产品。
行为型
模板方法模式
定义一个操作中算法的框架,而将一些步骤延迟到子类中。模板方法模式使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。
角色
- AbstractClass(抽象模板):定义多个抽象操作,让子类实现,这些抽象操作是基本操作;还要定义并实现一个或多个模板方法,在模板方法中对基本方法进行调度。
- ConcreteClass(具体模板):实现抽象模板的抽象方法
实现
1 | //抽象模板 |
优点
- 封装不变的部分,扩展变的部分:不变的在抽象类中实现,变的在子类中扩展。
- 提取公共部分,便于维护。
- 行为由父类控制,子类实现。
应用场景
- 多个子类有公共方法,并且逻辑基本相同时
- 可以把重要的、复杂的、核心的算法设计为模板方法,其他细节由子类实现
- 重构时,将相同的代码抽取到父类。
观察者模式
定义对象之间的一种一对多依赖关系,使得每当一个对象状态发生改变时,其相关依赖对象皆得到通知并被自动更新。
角色
- Subject(抽象主题):被观察的对象,一个观察目标可以接受任意数量的观察者来观察,可以增加和删除观察者;可以是接口,也可以是抽象类或具体类。
- ConcreteSubject(具体主题):通常它包含有经常发生改变的数据,当它的状态发生改变时,向它的各个观察者发出通知。
- Observer(抽象观察者):观察者将对观察目标的改变做出反应,观察者一般定义为接口。
- ConcreteObserver(具体观察者)
实现
观察目标:
1 | import java.util.*; |
观察者:
1 | //抽象观察者角色一般定义为一个接口,通常只声明一个update()方法 |
JDK中的观察者模式
Observer接口(观察者)
在java.util.Observer接口中只声明一个方法,它充当抽象观察者,其方法声明代码如下所示:
1 | void update(Observable o, Object arg); |
当观察目标的状态发生变化时,该方法将会被调用,在Observer的子类中将实现update()方法,即具体观察者可以根据需要具有不同的更新行为。当调用观察目标类Observable的notifyObservers()方法时,将执行观察者类中的update()方法。
Observable抽象类(观察目标)
方法名 | 方法描述 |
---|---|
Observable() | 构造方法,实例化Vector向量。 |
addObserver(Observer o) | 用于注册新的观察者对象到向量中。 |
deleteObserver (Observer o) | 用于删除向量中的某一个观察者对象。 |
notifyObservers()和notifyObservers(Object arg) | 通知方法,用于在方法内部循环调用向量中每一个观察者的update()方法。 |
deleteObservers() | 用于清空向量,即删除向量中所有观察者对象。 |
setChanged() | 该方法被调用后会设置一个boolean类型的内部标记变量changed的值为true,表示观察目标对象的状态发生了变化。 |
clearChanged() | 用于将changed变量的值设为false,表示对象状态不再发生改变或者已经通知了所有的观察者对象,调用了它们的update()方法。 |
hasChanged() | 用于测试对象状态是否改变。 |
countObservers() | 用于返回向量中观察者的数量。 |
我们可以直接使用Observer接口和Observable类来作为观察者模式的抽象层,再自定义具体观察者类和具体观察目标类。
优缺点
优点:
- 实现表示层和数据逻辑层的分离,定义了稳定的消息更新传递机制,并抽象了更新接口,使得可以有各种各样不同的表示层充当具体观察者角色。
- 在观察目标和观察者之间建立一个抽象的耦合,易于扩展。
- 支持广播通信。
- 观察者模式满足“开闭原则”的要求
缺点:
- 如果一个观察目标对象有很多直接和间接观察者,将所有的观察者都通知到会花费很多时间。
- 如果在观察者和观察目标之间存在循环依赖,观察目标会触发它们之间进行循环调用,可能导致系统崩溃。
- 观察者模式没有相应的机制让观察者知道所观察的目标对象是怎么发生变化的,而仅仅只是知道观察目标发生了变化。
适用场景
- 一个抽象模型有两个方面,其中一个方面依赖于另一个方面
- 一个对象的改变将导致一个或多个其他对象也发生改变,而并不知道具体有多少对象将发生改变,也不知道这些对象是谁。
- 需要在系统中创建一个触发链,A对象的行为将影响B对象,B对象的行为将影响C对象……,可以使用观察者模式创建一种链式触发机制。
具体应用:发布-订阅(Publish/Subscribe)模式、模型-视图(Model/View)模式。
结构型
代理模式
为其他对象提供一种代理以控制这个对象的访问。
角色
- 抽象主题:代理主题和真实主题的共同接口。
- 代理主题:在真实主题处理前后代理一些工作。
- 真实主题:业务的具体执行者。
实现
1 | //抽象主题 |
优缺点
优点:
- 职责清晰:真实主题复制业务,代理主题复制代理。
- 高扩展性:只要实现了接口,代理类可以代理各种真实主题。
- 智能化:代理类可以在运行时才确定要去代理的真实主题。
代理模式种类
- 远程代理
- 虚拟代理:创建资源消耗多的对象时,先创建代理对象,真实对象的创建延迟。
- 保护代理:给不同的用户设置不同的权限。
- 缓存代理
- 同步代理
- 智能代理:记录访问流量和次数。
适配器模式
将一个接口转换成客户希望的另一个接口,使接口不兼容的那些类可以一起工作。
角色
- 目标(Target)角色:要转换成的接口,可以是一个抽象类或接口。
- 源(Adaptee)角色:需要被转换成目标角色的源角色。
- 适配器(Adapter)角色
实现
1 | class Adapter extends Target { |
优缺点
优点:
- 增加了类的透明性和复用性,将具体的业务实现过程封装在适配者类中,对于客户端类而言是透明的,而且提高了适配者的复用性,同一个适配者类可以在多个不同的系统中复用。
- 灵活性和扩展性都非常好,完全符合“开闭原则”。
- 将目标类和适配者类解耦,通过引入一个适配器类来重用现有的适配者类,无须修改原有结构。
- 一个对象适配器可以把多个不同的适配者适配到同一个目标
缺点:
- 适配者类不能为最终类,如在Java中不能为final类。
- 类适配器模式中的目标抽象类只能为接口,不能为类。
装饰者模式
动态地给一个对象增加一些额外的职责,就增加对象功能来说,装饰模式比生成子类实现更为灵活。
角色
- Component(抽象构件):是一个接口或者抽象类,是最核心,最原始的对象(以学生举例,学生有中学生、小学生,这个类相当于学生类)。
- ConcreteComponent(具体构件):最核心,最原始的接口或者抽象类的具体实现。这个就是需要被装饰的角色。(相当于上面例子中的中学生或者小学生)
- Decorator(装饰角色):一般是一个抽象类实现接口或者抽象方法,持有一个构件(Component)对象的实例。
- ConcreteDecorator(具体装饰角色):这个就是要装饰的功能,负责给被装饰者进行装饰。(就是功能组件,去给被装饰者添加这个组件)
实现
抽象构件:
1
2
3public abstract class Component {
public abstract void operate();
}具体构件:(实现了抽象构件)
1
2
3
4
5
6public class ConcreteComponent extends Component{
@Override
public void operate() {
System.out.println("do something");
}
}抽象装饰者:
1 | public abstract class Decorator extends Component{ |
具体装饰角色1:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16public class ConcreteDecorator1 extends Decorator{
public ConcreteDecorator1(Component _component) {
super(_component);
}
//为被修饰对象增加的额外功能
private void method1(){
System.out.println("method1 修饰");
}
@Override
public void operate() {
this.method1();
super.operate();
}
}具体装饰角色2:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16public class ConcreateDecorator2 extends Decorator{
public ConcreateDecorator2(Component _component) {
super(_component);
}
//为被修饰对象增加的额外功能
private void method2(){
System.out.println("method2 修饰");
}
@Override
public void operate() {
this.method2();
super.operate();
}
}
- 最后进行对目标对象的修饰操作:
1
2
3
4
5
6
7
8
9
10
11public class Main {
public static void main(String[] args) {
Component component = new ConcreteComponent();
//如果需要为被修饰者增加功能1
component = new ConcreteDecorator1(component);
//如果需要为被修饰者增加功能2
component = new ConcreateDecorator2(component);
component.operate();
}
}
优缺点
装饰者模式的优点:
- 装饰类跟被装饰类可以独立发展,不会互相耦合
- 装饰模式可以动态扩展一个实现类的功能
- 装饰者模式的缺点:
- 使用装饰模式进行系统设计时将产生很多小对象,这些对象的区别在于它们之间相互连接的方式有所不同,而不是它们的类或者属性值有所不同,大量小对象的产生势必会占用更多的系统资源,在一定程序上影响程序的性能。
- 装饰模式提供了一种比继承更加灵活机动的解决方案,但同时也意味着比继承更加易于出错,排错也很困难,对于多次装饰的对象,调试时寻找错误可能需要逐级排查,较为繁琐。
使用场景
- 需要扩展一个类的功能,或者增加附加功能
- 动态给类增加功能,也可以动态撤销
- 需要为一批兄弟类进行修饰