创建型模式
创建型模式关注的重点是如何创建对象,它的主要特点是将对象的创建与使用分离;这样可以降低系统的耦合度,使用者不需要关注对象的创建细节。
创建型模式分为:
单例模式
单例模式(Singleton Pattern)是最简单的设计模式之一;属于创建型模式,它提供了一种创建对象的最佳方式。
该模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。
该类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。
角色:单例模式主要涉及以下角色
- 单例类:只能创建一个实例的类。
- 访问类:使用单例类。
实现:单例设计模式分类两种:
饿汉式:类加载时创建单例实例对象。
静态成员变量
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20/**
* 静态成员变量
*/
public class Singleton1 {
private Singleton1() {
// 私有构造方法
}
// 使用静态成员变量创建该类的对象并初始化
private static final Singleton1 INSTANCE = new Singleton1();
/**
* 对外提供获取该对象的静态方法
*
* @return Singleton1
*/
public static Singleton1 getInstance() {
return INSTANCE;
}
}- 使用静态成员变量声明
Singleton1的对象实例INSTANCE并进行初始化。 INSTANCE对象实例是随着类的加载而创建的。- 如果该对象占用的内存很大且没有使用则会造成内存浪费。
- 使用静态成员变量声明
静态代码块
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17/**
* 静态代码块
*/
public class Singleton2 {
private Singleton2() {
}
private static final Singleton2 INSTANCE;
static {
INSTANCE = new Singleton2();
}
public static Singleton2 getInstance() {
return INSTANCE;
}
}- 使用静态成员变量声明
Singleton1的对象实例INSTANCE,但在静态代码块中进行初始化。 INSTANCE对象实例是随着类的加载而创建的。- 如果该对象占用的内存很大且没有使用则会造成内存浪费。
- 本质上和静态成员变量方式一致。
- 使用静态成员变量声明
枚举
1
2
3
4
5
6/**
* 枚举
*/
public enum Singleton3 {
INSTANCE
}- 枚举类实现单例模式是极力推荐的单例实现模式,因为枚举类型是线程安全的并且只会装载一次。
- 设计者充分的利用了枚举的这个特性来实现单例模式,枚举的写法非常简单。
- 枚举类型是所有单例实现中唯一不会被破坏的单例实现模式。
懒汉式:类加载时不创建单例实例对象,首次调用时创建单例实例对象。
静态成员变量(线程不安全)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18/**
* 静态成员变量(线程不安全)
*/
public class Singleton4 {
private Singleton4() {
}
private static Singleton4 INSTANCE;
public static Singleton4 getInstance() {
if (null == INSTANCE) {
INSTANCE = new Singleton4();
}
return INSTANCE;
}
}- 使用静态成员变量声明
Singleton4的对象实例INSTANCE但未进行初始化。 - 当首次调用
getInstance()方法时初始化INSTANCE对象实例,实现了懒加载效果。 - 线程不安全,多线程环境下可能会创建出多个不同的实例。
- 使用静态成员变量声明
静态成员变量(线程安全)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18/**
* 静态成员变量(线程安全)
*/
public class Singleton5 {
private Singleton5(){
}
private static Singleton5 INSTANCE;
public static synchronized Singleton5 getInstance(){
if(null != INSTANCE){
INSTANCE = new Singleton5();
}
return INSTANCE;
}
}- 懒加载的同时,使用
synchronized解决了线程安全问题。 - 效率低下:调用
getInstance()方法的绝大部分场景都是读操作;读操作是线程安全的,没必让每个线程必须持有锁才能调用该方法法。
- 懒加载的同时,使用
静态成员变量(线程安全双重检查锁)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24/**
* 静态成员变量(线程安全双重检查锁)
*/
public class Singleton6 {
private Singleton6() {
}
private static volatile Singleton6 INSTANCE;
public static Singleton6 getInstance() {
// 1 判断实例是否为null,如果为空进入抢锁阶段
if (null == INSTANCE) {
synchronized (Singleton6.class) {
// 2 抢锁成功后再次判断是否为null,为null则初始化对象
if (null == INSTANCE) {
INSTANCE = new Singleton6();
}
}
}
// 不为空直接返回实例对象
return INSTANCE;
}
}- 解决了单例、性能、线程安全问题。
- 使用
volatile关键字避免了初始化语句处的指令重排,同时确保所有线程看到这个变量的值是一致的。 INSTANCE = new Singleton6()实际执行了三步:- 申请内存空间。
- 在内存空间中初始化对象
Singleton6。 - 将内存地址赋值给
INSTANCE(执行此步骤时,INSTANCE已经有值,但对象可能未初始化完毕)。
- 若不使用
volatile关键字发生指令重排时,上述1、2、3的顺序可能重排为1、3、2;其他线程执行到3时将获取到未初始化完毕的对象。
静态内部类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16/**
* 静态内部类
*/
public class Singleton7 {
private Singleton7() {
}
private static class SingletonHolder {
private static final Singleton7 INSTANCE = new Singleton7();
}
public static Singleton7 getInstance() {
return SingletonHolder.INSTANCE;
}
}- 首次加载
Singleton类时不会初始化INSTANCE。 - 当第一次调用
getInstance()方法时,虚拟机加载SingletonHolder并初始化INSTANCE。 - 既保证了线程安全型,又保证乐
Singleton类的唯一性。
- 首次加载
破解:前文定义的单例类除枚举实现外,可以通过反射和序列化创建多个实例破坏单例。
反射
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/**
* 反射破坏单例
*/
class Singleton {
private Singleton() {
}
private static volatile Singleton INSTANCE;
public static Singleton getInstance() {
if (null != INSTANCE) {
return INSTANCE;
}
synchronized (Singleton.class) {
if (null != INSTANCE) {
return INSTANCE;
}
INSTANCE = new Singleton();
return INSTANCE;
}
}
}
public class SingletonReflectionHack {
public static void main(String[] args) throws Exception {
Class<Singleton> clz = Singleton.class;
Constructor<Singleton> constructor = clz.getDeclaredConstructor();
constructor.setAccessible(true);// 取消访问检查
Singleton s1 = constructor.newInstance();
Singleton s2 = constructor.newInstance();
System.out.println(s1 == s2);// false
}
}上述代码执行结果为
false,反射已经破坏了单例设计模式。解决方案:直接在构造方法中抛出异常,避免反射破坏单例。
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/**
* 反射破坏单例解决方案
*/
class AntiReflectionSingleton {
private AntiReflectionSingleton() {
if (null != INSTANCE) {
throw new RuntimeException();
}
}
private static volatile AntiReflectionSingleton INSTANCE;
public static AntiReflectionSingleton getInstance() {
if (null != INSTANCE) {
return INSTANCE;
}
synchronized (AntiReflectionSingleton.class) {
if (null != INSTANCE) {
return INSTANCE;
}
INSTANCE = new AntiReflectionSingleton();
return INSTANCE;
}
}
}序列化
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/**
* 序列化破坏单例
*/
class Singleton implements Serializable {
private Singleton() {
}
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
public class SingletonSerialization {
public static void main(String[] args) throws Exception {
/*
将单例对象序列化到文件中
*/
String filename = "singleton.serial";
OutputStream os = new FileOutputStream(filename);
ObjectOutputStream oos = new ObjectOutputStream(os);
oos.writeObject(Singleton.getInstance());
/*
反序列化到对象1
*/
InputStream in1 = new FileInputStream(filename);
ObjectInputStream ois1 = new ObjectInputStream(in1);
Object o1 = ois1.readObject();
/*
反序列化到对象2
*/
InputStream in2 = new FileInputStream(filename);
ObjectInputStream ois2 = new ObjectInputStream(in2);
Object o2 = ois2.readObject();
/*
判断是否为同一个对象
*/
System.out.println(o1 == o2);// false
}
}分析
ObjectInputStream类readObject()方法源码: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
38public final Object readObject() throws IOException, ClassNotFoundException{
...
// if nested read, passHandle contains handle of enclosing object
int outerHandle = passHandle;
try {
Object obj = readObject0(false);//重点查看readObject0方法
.....
}
private Object readObject0(boolean unshared) throws IOException {
...
try {
switch (tc) {
...
case TC_OBJECT:
return checkResolve(readOrdinaryObject(unshared));//重点查看readOrdinaryObject方法
...
}
} finally {
depth--;
bin.setBlockDataMode(oldMode);
}
}
private Object readOrdinaryObject(boolean unshared) throws IOException {
...
//isInstantiable 返回true,执行 desc.newInstance(),通过反射创建新的单例类,
obj = desc.isInstantiable() ? desc.newInstance() : null;
...
// 在Singleton类中添加 readResolve 方法后 desc.hasReadResolveMethod() 方法执行结果为true
if (obj != null && handles.lookupException(passHandle) == null && desc.hasReadResolveMethod()) {
// 通过反射调用 Singleton 类中的 readResolve 方法,将返回值赋值给rep变量
// 这样多次调用ObjectInputStream类中的readObject方法,继而就会调用我们定义的readResolve方法,所以返回的是同一个对象。
Object rep = desc.invokeReadResolve(obj);
...
}
return obj;
}上面代码运行结果是
false,序列化和反序列化破坏了单例设计模式解决方案:在单例类中添加
readResolve()方法并返回同一个实例即可: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
52package dev.pages.islongshiyu.patterns.p02.creational.singleton.hack.serialization;
import java.io.*;
class AntiSerializationSingleton implements Serializable {
private AntiSerializationSingleton() {
}
private static class SingletonHolder {
private static final AntiSerializationSingleton INSTANCE = new AntiSerializationSingleton();
}
public static AntiSerializationSingleton getInstance() {
return SingletonHolder.INSTANCE;
}
/**
* 避免序列化破坏单例模式
*/
private Object readResolve() {
return SingletonHolder.INSTANCE;
}
}
public class SingletonAntiSerialization {
public static void main(String[] args) throws Exception {
/*
将单例对象序列化到文件中
*/
String filename = "singleton.serial";
OutputStream os = new FileOutputStream(filename);
ObjectOutputStream oos = new ObjectOutputStream(os);
oos.writeObject(AntiSerializationSingleton.getInstance());
/*
反序列化到对象1
*/
InputStream in1 = new FileInputStream(filename);
ObjectInputStream ois1 = new ObjectInputStream(in1);
Object o1 = ois1.readObject();
/*
反序列化到对象2
*/
InputStream in2 = new FileInputStream(filename);
ObjectInputStream ois2 = new ObjectInputStream(in2);
Object o2 = ois2.readObject();
/*
判断是否为同一个对象
*/
System.out.println(o1 == o2);// true
}
}
拓展:JDK 源码中的
Runtime使用的就是单例模式的静态成员变量饿汉式实现的。1
2
3
4
5
6
7
8
9
10
11
12
13public class Runtime {
private static final Runtime currentRuntime = new Runtime();
private static Version version;
public static Runtime getRuntime() {
return currentRuntime;
}
private Runtime() {}
// ...
}Runtime类的使用示例:1
2
3
4
5
6
7
8
9
10
11
12
13
14public class RuntimeTests {
public static void main(String[] args) throws IOException {
Runtime runtime = Runtime.getRuntime();
System.out.println(runtime.totalMemory());// 当前 JVM 进程内存总量
System.out.println(runtime.maxMemory());// 当前 JVM 进程试图使用的最大内存总量
/*
执行命令并通过输入流获取命令执行的结果
*/
Process process = runtime.exec("ipconfig /flushdns");
InputStream in = process.getInputStream();
byte[] bytes = in.readAllBytes();
System.out.println(new String(bytes, Charset.forName("GBK")));
}
}
工厂模式
需求:咖啡店点餐系统设计。
设计如下:
classDiagram
class Coffee {
<<抽象类>>
+ String getName()*
+ void addMilk()
+ void addSugar()
}
class LatteCoffee {
+ String getName()
}
class AmericanCoffee {
+ String getName()
}
class Cafe {
+ Coffee make(String)
}
Coffee <|-- LatteCoffee
Coffee <|-- AmericanCoffee
Coffee .. Cafe
咖啡厅类Cafe代码如下:
1 | public class Cafe { |
上述的设计存在的问题:
Cafe类直接new创建对象,与具体的类严重耦合。- 若要更换对象,则需要修改创建对象的代码,违背了开闭原则。
应使用工厂来生产对象,创建对象只和工厂打交道,彻底和对象解耦;若需更换对象,直接在工厂里更换该对象即可,达到了与对象解耦的目的。
工厂模式最大的优点就是:解耦。
本文将涉及三种工厂模式:
- 简单工厂模式(不属于 GoF 的
23种经典设计模式) - 工厂方法模式
- 抽象工厂模式
简单工厂模式
简单工厂不是一种设计模式,更像是一种编程习惯。
角色:
- 抽象产品:定义了产品的规范,描述了产品的主要特性和功能。
- 具体产品:实现或者继承抽象产品的子类
- 具体工厂:提供了创建产品的方法,调用者通过该方法来获取产品。
实现:
classDiagram direction LR class Coffee { <<抽象类>> + String getName()* + void addMilk() + void addSugar() } class SimpleCoffeeFactory { + Coffee make(String) } class AmericanCoffee { + String getName() } class Cafe { - SimpleCoffeeFactory factory + Cafe Cafe(SimpleCoffeeFactory) + Coffee order(String) } class LatteCoffee { + String getName() } Cafe --> SimpleCoffeeFactory SimpleCoffeeFactory ..> Coffee Coffee <|-- AmericanCoffee Coffee <|-- LatteCoffeeCafe类代码如下:1
2
3
4
5
6
7
8
9public class Cafe {
public Coffee order(String name) {
SimpleCoffeeFactory factory = new SimpleCoffeeFactory();
Coffee coffee = factory.make(name);
coffee.addMilk();
coffee.addSugar();
return coffee;
}
}- 解除了
Cafe与具体的Coffee间的耦合,由SimpleCoffeeFactory处理创建Coffee对象。 - 加入
SimpleCoffeeFactory后,Cafe中的order(String)方法成为该对象的调用方,产生了新的耦合(工厂对象与其生产的商品之间耦合)。 - 工厂类的客户端可能有很多,比如创建美团外卖等,这样只需要修改工厂类的代码,省去其他的修改操作。
- 解除了
优点:
- 封装了创建对象的过程,可以通过参数直接获取对象。
- 将对象的创建和业务逻辑层分开,避免修改调用方代码。
- 如果要实现新产品直接修改工厂类,而不需要在原代码中修改,这样就降低了调用方代码修改的可能性,便于拓展。
缺点:添加新产品时需要修改工厂类的代码,违背了开闭原则。
拓展:实际开发可将工厂类中的创建对象的功能定义为静态的(即静态工厂模式,也不属于
23种设计模式之一)。1
2
3
4
5
6
7
8
9
10
11
12
13
14public class SimpleCoffeeStaticFactory {
public static Coffee make(String name) {
Coffee coffee;
if (Objects.equals("latte", name)) {
coffee = new LatteCoffee();
} else if (Objects.equals("american", name)) {
coffee = new AmericanCoffee();
} else {
coffee = new LatteCoffee();// default latte
}
return coffee;
}
}
工厂方法模式
定义一个用于创建对象的接口,让子类决定实例化哪个产品类对象;工厂方法使一个产品类的实例化延迟到其工厂的子类。
角色:
- 抽象工厂(Abstract Factory):提供创建产品的接口,调用者通过它访问具体工厂的工厂方法来创建产品。
- 具体工厂(Concrete Factory):实现抽象工厂中的抽象方法,完成具体产品的创建。
- 抽象产品(Abstract Product):定义了产品的规范,描述了产品的主要特性和功能。
- 具体产品(Concrete Product):实现抽象产品定义的接口,由具体工厂来创建,与具体工厂一一对应。
实现:
classDiagram direction TB class AmericanCoffeeFactory { + Coffee make() } class LatteCoffee { + String getName() } class AmericanCoffee { + String getName() } class Cafe { - CoffeeFactory factory + Cafe Cafe(CoffeeFactory) + Coffee order() } class Coffee { <<抽象类>> + String getName()* + void addMilk() + void addSugar() } class LatteCoffeeFactory { + Coffee make() } class CoffeeFactory { <<接口>> ~ Coffee make() } CoffeeFactory <|.. AmericanCoffeeFactory Coffee .. AmericanCoffeeFactory Coffee <|-- LatteCoffee Coffee <|-- AmericanCoffee CoffeeFactory -- Cafe Coffee .. Cafe CoffeeFactory <|.. LatteCoffeeFactory Coffee .. LatteCoffeeFactory Coffee .. CoffeeFactory工厂方法模式是简单工厂模式的进一步抽象;由于使用了多态性,工厂方法模式保持了简单工厂模式的优点,克服了它的缺点。
优点:
- 调用方无需了解产品的创建过程,只需要知道具体的工厂即可得到产品,
- 增加新的产品时只需要添加具体产品类和对应的具体工厂类,无须对原工厂进行任何修改,满足开闭原则。
缺点:
- 每增加一个产品就要增加一个具体产品类和一个对应的具体工厂类,增加了系统的复杂度。
抽象工厂模式
上文所述的工厂方法模式考虑的是同一类产品的生产,如:畜牧场只养动物、电视机厂只生产电视机。
工厂方法模式只考虑生产同类型的产品,实际生产时许多工厂是综合型的工厂,能生产多类型的产品,如:电器厂既生产电视机又生产洗衣机或空调。
抽象工厂模式将考虑多种类型产品的生产:
产品类型:汽车可以分为轿车、SUV、MPV等,也分为奔驰、宝马等;电脑可以分为笔记本电脑、台式机。
产品族:将同一品牌下的所有产品,比如:宝马旗下的所有车是一个产品族,联想是一个产品族。
角色:
- 抽象工厂(Abstract Factory):提供了创建产品的接口,包含多个创建产品的方法,可以创建多种不同类型的产品。
- 具体工厂(Concrete Factory):实现抽象工厂中的多个抽象方法,完成具体产品的创建。
- 抽象产品(Abstract Product):定义了产品的规范,描述了产品的主要特性和功能,抽象工厂模式有多个抽象产品。
- 具体产品(Concrete Product):实现抽象产品角色所定义的接口,由具体工厂来创建,与具体工厂之间是多对一的关系。
实现:当咖啡厅业务发生改变,不仅要生产咖啡还要生产甜点,如提拉米苏、抹茶慕斯等。
若按照工厂方法模式,需要定义提拉米苏类、抹茶慕斯类、提拉米苏工厂、抹茶慕斯工厂、甜点工厂类,很容易发生类爆炸情况。
其中拿铁咖啡、美式咖啡属于咖啡这一产品类别。
提拉米苏、抹茶慕斯属于甜点这一产品类别。
拿铁咖啡和提拉米苏是同一产品族(意大利风味),美式咖啡和抹茶慕斯是同一产品族(美式风味)。
classDiagram class Dessert { <<抽象类>> ~ void show()* } class MatchaMousse { ~ void show() } class DessertFactory { <<接口>> ~ Coffee makeCoffee() ~ Dessert makeDessert() } class AmericanDessertFactory { + Coffee makeCoffee() + Dessert makeDessert() } class AmericanCoffee { + String getName() } class LatteCoffee { + String getName() } class ItalyDessertFactory { + Coffee makeCoffee() + Dessert makeDessert() } class Coffee { <<抽象类>> + String getName()* + void addMilk() + void addSugar() } class Tiramisu { ~ void show() } Dessert <|-- MatchaMousse Coffee .. DessertFactory Dessert .. DessertFactory DessertFactory <|.. AmericanDessertFactory Coffee .. AmericanDessertFactory Dessert .. AmericanDessertFactory Coffee <|-- AmericanCoffee Coffee <|-- LatteCoffee DessertFactory <|.. ItalyDessertFactory Coffee .. ItalyDessertFactory Dessert .. ItalyDessertFactory Dessert <|-- Tiramisu
优点:当一个产品族中的多个对象被设计成一起工作时,它能保证客户端调用方始终只使用同一个产品族中的对象。
缺点:当产品族中需要增加一个新的产品时,所有的工厂类都需要进行修改。
应用:应用场景举例
- 当需要创建的对象是一系列相互关联或相互依赖的产品族时,如电器工厂中的电视机、洗衣机、空调等。
- 系统中有多个产品族,但每次只使用其中的某一族产品。如有人只喜欢穿某一个品牌的衣服和鞋。
- 系统中提供了产品的类库,且所有产品的接口相同,客户端不依赖产品实例的创建细节和内部结构;如:输入法换皮肤,一整套一起换,生成不同操作系统的程序。
工厂模式实战
本小节运用简单工厂模式结合配置文件(解耦)的方式来实际运用前面描述的内容。
定义配置文件。
1
2american=dev.pages.islongshiyu.patterns.p02.creational.factory.simple.AmericanCoffee
latte=dev.pages.islongshiyu.patterns.p02.creational.factory.simple.LatteCoffee结合简单工厂。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23public class SimpleConfiguredFactory {
private static final Map<String, Coffee> BEAN_MAP = new HashMap<>();
static {
Properties properties = new Properties();
try (
InputStream is = SimpleConfiguredFactory.class.getResourceAsStream("/bean.properties")
) {
properties.load(is);
for (Map.Entry<Object, Object> entry : properties.entrySet()) {
String k = entry.getKey().toString();
Object v = entry.getValue();
BEAN_MAP.put(k, (Coffee) Class.forName((String) v).getDeclaredConstructor().newInstance());
}
} catch (Exception e) {
System.out.println(e.getMessage());
}
}
public static Coffee make(String name) {
return BEAN_MAP.get(name);
}
}
原型模式
将一个已经创建的实例作为原型,通过复制该原型对象来创建一个和原型对象相同的新对象。
角色:
抽象原型类:规定了具体原型对象必须实现的的
clone()方法。具体原型类:实现抽象原型类的
clone()方法,它是可被复制的对象。具体访问类:使用具体原型类中的
clone()方法来复制新的对象。classDiagram class Realize { + Realize clone() } class Prototype { <<接口>> ~ Prototype clone() } Prototype <|.. Realize
实现:原型模式的克隆分为浅克隆和深克隆。
浅克隆:创建一个新对象,新对象的属性和原来对象完全相同,对于非基本类型属性,仍指向原有属性所指向的对象的内存地址。
Java 中的
Object类中提供了clone()方法来实现浅克隆。Cloneable接口是上面的类图中的抽象原型类,而实现了Cloneable接口的子实现类就是具体的原型类。具体的原型类:
1
2
3
4
5
6
7
8
9
10public class Realized implements Cloneable{
public Realized clone() {
try {
return (Realized) super.clone();
} catch (CloneNotSupportedException e) {
throw new AssertionError();
}
}
}具体的访问类:
1
2
3
4
5
6
7public class RealizedTest {
public static void main(String[] args) {
Realized r1 = new Realized();
Realized r2 = r1.clone();
System.out.println("r1 == r2 ? " + (r1 == r2));
}
}深克隆:创建一个新对象,属性中引用的其他对象也会被克隆,不再指向原有对象地址。
场景:
- 若对象的创建非常复杂,可以使用原型模式快捷的创建对象。
- 性能和安全性要求比较高。
案例:通过原型模式生成三好学生奖状;同一学校的三好学生奖状除了获奖人姓名不同,其他都相同,可以使用原型模式复制多个三好学生奖状,然后修改奖状上的名字即可。
classDiagram direction TB class Cloneable { <<接口>> ~ Object clone() } class CitationTest { + void main(String[])$ } class Citation { - String name + void show() + Citation clone() } Cloneable <|.. Citation CitationTest .. Citation奖状类:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18public class Citation implements Cloneable {
private String name;
public void show() {
System.out.println(name + "同学:在2023学年第一学期中表现优秀,被评为三好学生。特发此状!");
}
public Citation clone() {
try {
return (Citation) super.clone();
} catch (CloneNotSupportedException e) {
throw new AssertionError();
}
}
}访问类:
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
26public class CitationTest {
public static void main(String[] args) {
Citation c1 = new Citation();
c1.setName("张三");
/*
clone
*/
Citation c2 = c1.clone();
c2.setName("李四");
/*
show
*/
c1.show();// 张三同学:在2023学年第一学期中表现优秀,被评为三好学生。特发此状!
c2.show();// 李四同学:在2023学年第一学期中表现优秀,被评为三好学生。特发此状!
/*
compare
*/
System.out.println(c1 == c2);// false
}
}拓展:
浅克隆
对前面案例中的
Citation类中的name属性改为Student对象。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19public class Citation implements Cloneable {
private Student student;
public void show() {
System.out.println(student.getName() + "同学:在2023学年第一学期中表现优秀,被评为三好学生。特发此状!");
}
public Citation clone() {
try {
return (Citation) super.clone();
} catch (CloneNotSupportedException e) {
throw new AssertionError();
}
}
}学生类如下:
1
2
3
4
5
6
7
8
9
public class Student {
private String name;
private String address;
}访问类代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24public class CitationTest {
public static void main(String[] args) {
Citation c1 = new Citation();
Student student1 = new Student("张三", "西安");
c1.setStudent(student1);
/*
clone
*/
Citation c2 = c1.clone();
Student student2 = c2.getStudent();
student2.setName("李四");
/*
compare
*/
System.out.println("student1 == student2 ? " + (student1 == student2));// student1 == student2 ? true
c1.show();// 李四同学:在2023学年第一学期中表现优秀,被评为三好学生。特发此状!
c2.show();// 李四同学:在2023学年第一学期中表现优秀,被评为三好学生。特发此状!
}
}运行结果为:
student1和student2是同一个对象。- 两个
Citation类中引用的都是同一个Student对象。 - 上述都是浅克隆的效果,对具体原型类
Citation中的引用类型的属性进行引用的复制。
深克隆:深克隆需要使用对象流。
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
35public class CitationDeepCloneTest {
public static void main(String[] args) {
Citation c1 = new Citation();
Student student1 = new Student("张三", "西安");
c1.setStudent(student1);
try (
OutputStream os = new FileOutputStream("obj.bin");
ObjectOutputStream oos = new ObjectOutputStream(os);
InputStream is = new FileInputStream("obj.bin");
ObjectInputStream ois = new ObjectInputStream(is)
) {
/*
write object
*/
oos.writeObject(c1);
/*
read object
*/
Citation c2 = (Citation) ois.readObject();
Student student2 = c2.getStudent();
student2.setName("李四");
/*
compare
*/
System.out.println("student1 == student2 ? " + (student1 == student2));// student1 == student2 ? false
c1.show();// 张三同学:在2023学年第一学期中表现优秀,被评为三好学生。特发此状!
c2.show();// 李四同学:在2023学年第一学期中表现优秀,被评为三好学生。特发此状!
} catch (Exception e) {
System.out.println(e.getMessage());
}
}
}Citation,Student均需实现Serializable接口!
建造者模式
将一个复杂的构建与其表示相分离,使得同样的构建过程可以创建不同的表示。
角色:建造者(Builder)模式包含这些角色。
抽象建造者类(Builder):这个接口规定要实现复杂对象的哪些部分的创建,并不涉及具体的部件对象的创建。
具体建造者类(ConcreteBuilder):实现 Builder 接口,完成复杂产品的各个部件的具体创建方法;在构造过程完成后,提供产品的实例。
产品类(Product):要创建的复杂对象。
指挥者(Director):调用具体建造者来创建复杂对象的各个部分,它不涉及具体产品的信息,只负责保证对象各部分完整创建或按某种顺序创建。
classDiagram class Director { - Builder builder + Director Director(Builder) + Product construct() } class Product { - String part1 - String part2 - String part3 } class Builder { <<抽象类>> # void buildPart1()* # void buildPart2()* # void buildPart3()* + Product build()* } class ConcreteBuilder { - Product product + ConcreteBuilder ConcreteBuilder() # void buildPart1() # void buildPart2() + void buildPart3() + Product build() } Builder -- Director Product .. Director Product .. Builder Builder <|-- ConcreteBuilder Product -- ConcreteBuilder
案例:建造者模式生产共享单车。
抽象建造者类(
Builder)具体建造者类(
MobikeBuilder、OfoBuilder)产品类(
Bike),包含车架、车座等指挥者(
Director)classDiagram class Bike { - String seat - String frame } class MobikeBuilder { - Bike bike + MobikeBuilder MobikeBuilder() # void buildSeat() # void buildFrame() + Bike build() } class OfoBuilder { - Bike bike + OfoBuilder OfoBuilder() # void buildSeat() # void buildFrame() + Bike build() } class Builder { <<抽象类>> # void buildSeat()* # void buildFrame()* + Bike build()* } class Director { - Builder builder + Director Director(Builder) + Bike construct() } Builder <|-- MobikeBuilder Bike -- MobikeBuilder Builder <|-- OfoBuilder Bike -- OfoBuilder Bike .. Builder Builder -- Director Bike .. DirectorBike:1
2
3
4
5
6
public class Bike {
private String seat;
private String frame;
}Builder:1
2
3
4
5
6
7public abstract class Builder {
protected abstract void buildSeat();
protected abstract void buildFrame();
public abstract Bike build();
}OfoBuilder:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22public class OfoBuilder extends Builder {
private final Bike bike;
public OfoBuilder() {
this.bike = new Bike();
}
protected void buildSeat() {
this.bike.setSeat("橡胶车座");
}
protected void buildFrame() {
this.bike.setSeat("碳纤维车架");
}
public Bike build() {
return bike;
}
}MobikeBuilder:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22public class MobikeBuilder extends Builder {
private final Bike bike;
public MobikeBuilder() {
this.bike = new Bike();
}
protected void buildSeat() {
bike.setSeat("真皮车座");
}
protected void buildFrame() {
bike.setFrame("钛合金车架");
}
public Bike build() {
return bike;
}
}Director:1
2
3
4
5
6
7
8
9
10
11
12
13public class Director {
private final Builder builder;
public Director(Builder builder) {
this.builder = builder;
}
public Bike construct() {
builder.buildSeat();
builder.buildFrame();
return builder.build();
}
}
上面的案例展示了
Builder模式的常规用法;指挥者类Director在建造者模式中具有很重要的作用。- 指导具体构建者如何构建产品。
- 控制调用先后次序,并向调用者返回完整的产品类。
但是有些情况下需要简化系统结构,可以把指挥者类和抽象建造者进行结合。
1
2
3
4
5
6
7
8
9
10
11
12
13public abstract class DirectorBuilder {
protected abstract void buildSeat();
protected abstract void buildFrame();
public abstract Bike build();
public Bike construct() {
this.buildSeat();
this.buildFrame();
return this.build();
}
}这样做确实简化了系统结构,但同时也加重了抽象建造者类的职责,也不是太符合单一职责原则,如果
construct()过于复杂,还是应该封装到Director中。优点:
- 建造者模式的封装性很好。使用建造者模式可以有效的封装变化,在使用建造者模式的场景中,一般产品类和建造者类是比较稳定的;因此,将主要的业务逻辑封装在指挥者类中对整体而言可以取得比较好的稳定性。
- 在建造者模式中,客户端不必知道产品内部组成的细节,将产品本身与产品的创建过程解耦,使得相同的创建过程可以创建不同的产品对象。
- 可以更加精细地控制产品的创建过程 。将复杂产品的创建步骤分解在不同的方法中,使得创建过程更加清晰,也更方便使用程序来控制创建过程。
- 建造者模式很容易进行扩展。如果有新的需求,通过实现一个新的建造者类就可以完成,基本上不用修改之前已经测试通过的代码,因此也就不会对原有功能引入风险;符合开闭原则。
缺点:建造者模式所创建的产品一般具有较多的共同点,其组成部分相似,如果产品之间的差异性很大,则不适合使用建造者模式,因此其使用范围受到一定的限制。
场景:建造者模式创建的是复杂对象,其产品的各个部分经常面临着剧烈的变化,但将它们组合在一起的算法却相对稳定,所以它通常在以下场合使用。
- 创建的对象较复杂,由多个部件构成,各部件面临着复杂的变化,但构件间的建造顺序是稳定的。
- 创建复杂对象的算法独立于该对象的组成部分以及它们的装配方式,即产品的构建过程和最终的表示是独立的。
拓展:当一个类构造器需要传入很多参数时,如果创建这个类的实例,代码可读性会非常差,而且很容易引入错误,此时就可以利用建造者模式进行重构。
改造前: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
49public class Phone {
private String cpu;
private String screen;
private String memory;
private String motherboard;
public Phone(String cpu, String screen, String memory, String motherboard) {
this.cpu = cpu;
this.screen = screen;
this.memory = memory;
this.motherboard = motherboard;
}
public String getCpu() {
return cpu;
}
public void setCpu(String cpu) {
this.cpu = cpu;
}
public String getScreen() {
return screen;
}
public void setScreen(String screen) {
this.screen = screen;
}
public String getMemory() {
return memory;
}
public void setMemory(String memory) {
this.memory = memory;
}
public String getMotherboard() {
return motherboard;
}
public void setMotherboard(String motherboard) {
this.motherboard = motherboard;
}
public static void main(String[] args) {
Phone phone = new Phone("Intel", "Samsung", "Kingston", "Acer");
}
}改造后:
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
94
95public class Phone1 {
private String cpu;
private String screen;
private String memory;
private String motherboard;
public static Phone1Builder builder() {
return new Phone1Builder();
}
public String getCpu() {
return this.cpu;
}
public String getScreen() {
return this.screen;
}
public String getMemory() {
return this.memory;
}
public String getMotherboard() {
return this.motherboard;
}
public void setCpu(String cpu) {
this.cpu = cpu;
}
public void setScreen(String screen) {
this.screen = screen;
}
public void setMemory(String memory) {
this.memory = memory;
}
public void setMotherboard(String motherboard) {
this.motherboard = motherboard;
}
public Phone1() {
}
public Phone1(String cpu, String screen, String memory, String motherboard) {
this.cpu = cpu;
this.screen = screen;
this.memory = memory;
this.motherboard = motherboard;
}
public static class Phone1Builder {
private String cpu;
private String screen;
private String memory;
private String motherboard;
Phone1Builder() {
}
public Phone1Builder cpu(String cpu) {
this.cpu = cpu;
return this;
}
public Phone1Builder screen(String screen) {
this.screen = screen;
return this;
}
public Phone1Builder memory(String memory) {
this.memory = memory;
return this;
}
public Phone1Builder motherboard(String motherboard) {
this.motherboard = motherboard;
return this;
}
public Phone1 build() {
return new Phone1(this.cpu, this.screen, this.memory, this.motherboard);
}
}
public static void main(String[] args) {
Phone1 phone = Phone1.builder()
.cpu("Intel")
.screen("Samsung")
.memory("Kingston")
.motherboard("Acer")
.build();
}
}改造后可读性很高,使用方便,某种程度上提高了开发效率。
简化版(lombok):
1
2
3
4
5
6
7
8
9
10
11
public class Phone2 {
private String cpu;
private String screen;
private String memory;
private String motherboard;
}
创建型模式对比
- 工厂方法模式 VS 建造者模式
- 工厂方法模式注重的是整体对象的创建方式;
- 建造者模式注重的是部件构建的过程,意在通过一步一步地精确构造创建出一个复杂的对象。
- 举个栗子:制造一个超人。
- 如果使用工厂方法模式,直接产生出来的就是一个力大无穷、能够飞翔、内裤外穿的超人。
- 如果使用建造者模式,则需要组装手、头、脚、躯干等部分,然后再把内裤外穿,于是一个超人就诞生了。
- 抽象工厂模式 VS 建造者模式
- 抽象工厂模式实现对产品家族的创建,一个产品家族是这样的一系列产品:具有不同分类维度的产品组合,采用抽象工厂模式则是不需要关心构建过程,只关心什么产品由什么工厂生产即可。
- 建造者模式则是要求按照指定的蓝图建造产品,它的主要目的是通过组装零配件而产生一个新产品。
- 如果将抽象工厂模式看成汽车配件生产工厂,生产一个产品族的产品,那么建造者模式就是一个汽车组装工厂,通过对部件的组装可以返回一辆完整的汽车。
结构型模式
结构型模式描述如何将类或对象按某种布局组成更大的结构。它分为类结构型模式和对象结构型模式,前者采用继承机制来组织接口和类,后者釆用组合或聚合来组合对象。
由于组合关系或聚合关系比继承关系耦合度低,满足合成复用原则,所以对象结构型模式比类结构型模式具有更大的灵活性。
结构型模式分为:
代理模式
由于某些原因需要给某对象提供一个代理以控制对该对象的访问。此时,访问对象不适合或者不能直接引用目标对象,采用代理对象作为访问对象和目标对象之间的中介。
Java 中的代理按照代理类生成时机不同又分为静态代理和动态代理。静态代理代理类在编译期就生成,而动态代理代理类则是在 Java 运行时动态生成。动态代理又有 JDK 代理和 CGLib 代理两种。
角色:代理(Proxy)模式分为三种角色。
抽象主体类(Subject):通过接口或抽象类声明真实主体和代理对象实现的业务方法。
真实主体类(Real Subject):实现了抽象主体中的具体业务,是代理对象所代表的真实对象,是最终要引用的对象。
代理类(Proxy):提供了与真实主体相同的接口,其内部含有对真实主体的引用,它可以访问、控制或扩展真实主体的功能。
classDiagram class Proxy { - Subject subject + Proxy Proxy(Subject) + boolean isAvailable() + void call() } class RealSubject { + void call() } class Subject { <<接口>> ~ void call() } Subject <|.. Proxy Subject <|.. RealSubject Proxy .. RealSubject
静态代理:本例演示如何使用代理模式在第三方腾讯视频(TencentVideo,代码示例中记为 TV) 程序库中添加延迟初始化和缓存。
classDiagram class ThirdPartyTV { + Video getVideo(String) + Map~String,Video~ popularVideos() - int random(int,int) - void experienceNetworkLatency() - void connectToServer(String) - Map~String,Video~ getRandomVideos() - Video getSomeVideo(String) } class ThirdPartyTVLib { <<接口>> ~ Video getVideo(String) ~ Map~String,Video~ popularVideos() } class TVDownloader { - ThirdPartyTVLib lib + TVDownloader TVDownloader(ThirdPartyTVLib) + void renderVideoPage(String) + void renderPopularVideos() } class CachedTV { - ThirdPartyTVLib thirdPartyTVLib + CachedTV CachedTV(ThirdPartyTVLib) + Video getVideo(String) + Map~String,Video~ popularVideos() + void reset() } class Video { - String id - String title - String metadata + Video Video(String) + Video Video(String,String) } ThirdPartyTVLib <|.. ThirdPartyTV Video .. ThirdPartyTV Video .. ThirdPartyTVLib ThirdPartyTVLib -- TVDownloader ThirdPartyTVLib <|.. CachedTV Video .. CachedTVThirdPartyTVLib:腾讯视频远程服务接口类- 获取单个视频详情
- 获取热门推荐视频
1
2
3
4
5public interface ThirdPartyTVLib {
Video getVideo(String videoId);
Map<String, Video> popularVideos();
}ThirdPartyTV:腾讯视频远程服务实现类,实现连接服务器下载视频(伪代码实现)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
57public class ThirdPartyTV implements ThirdPartyTVLib {
public Video getVideo(String videoId) {
connectToServer("https://v.qq.com/" + videoId);
return getSomeVideo(videoId);
}
public Map<String, Video> popularVideos() {
connectToServer("https://v.qq.com");
return getRandomVideos();
}
private int random(int min, int max) {
return min + (int) (Math.random() * ((max - min) + 1));
}
private void experienceNetworkLatency() {
int randomLatency = random(5, 10);
for (int i = 0; i < randomLatency; i++) {
try {
Thread.sleep(100);
} catch (InterruptedException ex) {
System.out.println(ex.getMessage());
}
}
}
private void connectToServer(String server) {
System.out.println("开始链接到服务器:" + server + "... ");
experienceNetworkLatency();
System.out.println("成功连接到服务器!");
}
private Map<String, Video> getRandomVideos() {
System.out.println("下载热门视频...");
experienceNetworkLatency();
Map<String, Video> v = new HashMap<>();
v.put("EUx2lQ", new Video("EUx2lQ", "超肥的蓝猫睡觉打呼噜.avi"));
v.put("YjOvUK", new Video("YjOvUK", "哈士奇打篮球.mp4"));
v.put("o0OYtW", new Video("o0OYtW", "蔡徐坤打篮球.mpq"));
v.put("UuUzUY", new Video("UuUzUY", "鸡你太美.mov"));
v.put("IbufD9", new Video("IbufD9", "编程入门#1.avi"));
System.out.println("下载完毕!");
return v;
}
private Video getSomeVideo(String videoId) {
System.out.println("下载视频... ");
experienceNetworkLatency();
Video video = new Video(videoId);
System.out.println("下载完毕!");
return video;
}
}CachedTV:缓存代理类- 引用了腾讯视频远程服务实现类,使用其相关功能。
- 拓展了缓存功能。
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
39public class CachedTV implements ThirdPartyTVLib {
private final ThirdPartyTVLib thirdPartyTVLib;
private Map<String, Video> cachePopular = new HashMap<>();
private final Map<String, Video> cacheAll = new HashMap<>();
public CachedTV(ThirdPartyTVLib lib) {
this.thirdPartyTVLib = lib;
}
public Video getVideo(String videoId) {
Video video = cacheAll.get(videoId);// 从缓存中获取视频
/*
若未缓存该视频则从服务器下载该视频并缓存起来
*/
if (video == null) {
video = thirdPartyTVLib.getVideo(videoId);
cacheAll.put(videoId, video);
return video;
}
return video;
}
public Map<String, Video> popularVideos() {
/*
若未缓存该视频则从服务器下载该视频并缓存起来
*/
return cachePopular.isEmpty() ? cachePopular = thirdPartyTVLib.popularVideos() : cachePopular;
}
/**
* 清空缓存
*/
public void reset() {
cachePopular.clear();
cacheAll.clear();
}
}TVDownloader:腾讯视频下载器,模拟客户端应用,它引用了腾讯视频远程服务实现类,渲染视频展示界面。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
27public class TVDownloader {
private final ThirdPartyTVLib lib;
public TVDownloader(ThirdPartyTVLib lib) {
this.lib = lib;
}
public void renderVideoPage(String videoId) {
Video video = lib.getVideo(videoId);
System.out.println("------------------------------");
System.out.println("视频详情");
System.out.println("编号:" + video.getId());
System.out.println("标题:" + video.getTitle());
System.out.println("信息:" + video.getMetadata());
System.out.println("-------------------------------");
}
public void renderPopularVideos() {
Map<String, Video> list = lib.popularVideos();
System.out.println("-------------------------------");
System.out.println("热门视频");
for (Video video : list.values()) {
System.out.println("编号:" + video.getId() + " / 标题: " + video.getTitle());
}
System.out.println("-------------------------------");
}
}Video:视频类1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Video {
private String id;
private String title;
private String metadata;
public Video(String id) {
this.id = id;
}
public Video(String id, String title) {
this.id = id;
this.title = title;
}
}Test:测试类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
30public class Test {
public static void main(String[] args) {
ThirdPartyTV tv = new ThirdPartyTV();
TVDownloader naiveDownloader = new TVDownloader(tv);
TVDownloader smartDownloader = new TVDownloader(new CachedTV(tv));
long naive = test(naiveDownloader);
long smart = test(smartDownloader);
System.out.print("使用缓存代理节省时间:" + (naive - smart) + "毫秒");
}
private static long test(TVDownloader downloader) {
long startTime = System.currentTimeMillis();
/*
用户查看热门视频
*/
downloader.renderPopularVideos();
downloader.renderVideoPage("EUx2lQ");
downloader.renderPopularVideos();
downloader.renderVideoPage("UuUzUY");
/*
用户查看同一视频
*/
downloader.renderVideoPage("EUx2lQ");
downloader.renderVideoPage("IbufD9");
long estimatedTime = System.currentTimeMillis() - startTime;
System.out.println("耗时:" + estimatedTime + "毫秒");
return estimatedTime;
}
}输出结果(省略视频信息):
1
2耗时:6204毫秒
使用缓存代理节省时间:3912毫秒
动态代理:
- JDK:Java 中提供了一个动态代理类
Proxy,Proxy并不是上述所说的代理对象类,而是提供了一个创建代理对象的静态方法newProxyInstance()来获取代理对象;此处使用 JDK 动态代理来实现上面的静态代理。ThirdPartyVideo:第三方视频远程服务接口类1
2
3
4
5public interface ThirdPartyVideo {
Video getVideo(String videoId);
Map<String, Video> popularVideos();
}优酷视频服务类:第三方视频远程服务接口的具体实现类
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/**
* 优酷视频服务类
*/
public class YouKuVideo implements ThirdPartyVideo {
public Video getVideo(String videoId) {
connectToServer("https://youku.com/" + videoId);
return getSomeVideo(videoId);
}
public Map<String, Video> popularVideos() {
connectToServer("https://youku.com/");
return getRandomVideos();
}
private int random(int min, int max) {
return min + (int) (Math.random() * ((max - min) + 1));
}
private void experienceNetworkLatency() {
int randomLatency = random(5, 10);
for (int i = 0; i < randomLatency; i++) {
try {
Thread.sleep(100);
} catch (InterruptedException ex) {
System.out.println(ex.getMessage());
}
}
}
private void connectToServer(String server) {
System.out.println("开始链接到服务器:" + server + "... ");
experienceNetworkLatency();
System.out.println("成功连接到服务器!");
}
private Map<String, Video> getRandomVideos() {
System.out.println("下载热门视频...");
experienceNetworkLatency();
Map<String, Video> v = new HashMap<>();
v.put("EUx2lQ", new Video("g4XZhv", "拳皇98C大门岚之山.avi"));
v.put("YjOvUK", new Video("sfMFvR", "黑狗咬伤两岁女童.mp4"));
v.put("o0OYtW", new Video("C8TjqY", "李佳琪道歉.mpq"));
v.put("UuUzUY", new Video("wN5CoW", "只因你太美.mov"));
v.put("IbufD9", new Video("j8ngHd", "PHP是世界上最好的语言.avi"));
System.out.println("下载完毕!");
return v;
}
private Video getSomeVideo(String videoId) {
System.out.println("下载视频... ");
experienceNetworkLatency();
Video video = new Video(videoId);
System.out.println("下载完毕!");
return video;
}
}CachedInvocationHandler:缓存功能拓展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
37public class CachedInvocationHandler implements InvocationHandler {
private final Object target;
public CachedInvocationHandler(Object target) {
this.target = target;
}
private Map<String, Object> cachePopular = new HashMap<>();
private final Map<String, Object> cacheAll = new HashMap<>();
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String mn = method.getName();
System.out.println("Object Class Name > " + proxy.getClass().getName());
if (Objects.equals("getVideo", mn)) {
Object obj = cacheAll.get(args[0]);// 从缓存中获取视频
/*
若未缓存该视频则从服务器下载该视频并缓存起来
*/
if (obj == null) {
obj = method.invoke(target, args);
cacheAll.put((String) args[0], obj);
return obj;
}
return obj;
} else if (Objects.equals("popularVideos", mn)) {
/*
若未缓存该视频则从服务器下载该视频并缓存起来
*/
return cachePopular.isEmpty() ? cachePopular = (Map<String, Object>) method.invoke(target, args) : cachePopular;
} else {
return method.invoke(target, args);
}
}
}Video:视频类1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22/**
* 视频类
*/
public class Video {
private String id;
private String title;
private String metadata;
public Video(String id) {
this.id = id;
}
public Video(String id, String title) {
this.id = id;
this.title = title;
}
}Test:测试类1
2
3
4
5
6
7
8
9
10
11public class Test {
public static void main(String[] args) throws InterruptedException {
ThirdPartyVideo lib = (ThirdPartyVideo) Proxy.newProxyInstance(YouKuVideo.class.getClassLoader(),
YouKuVideo.class.getInterfaces(),
new CachedInvocationHandler(new YouKuVideo()));
System.out.println(lib.popularVideos());
System.out.println(lib.getVideo("g4XZhv"));
}
}输出结果:
1
2
3
4
5
6
7
8
9
10
11
12
13Object Class Name > jdk.proxy1.$Proxy0
Method Name >
开始链接到服务器:https://youku.com/...
成功连接到服务器!
下载热门视频...
下载完毕!
{IbufD9=Video(id=j8ngHd, title=PHP是世界上最好的语言.avi, metadata=null), YjOvUK=Video(id=sfMFvR, title=黑狗咬伤两岁女童.mp4, metadata=null), EUx2lQ=Video(id=g4XZhv, title=拳皇98C大门岚之山.avi, metadata=null), UuUzUY=Video(id=wN5CoW, title=只因你太美.mov, metadata=null), o0OYtW=Video(id=C8TjqY, title=李佳琪道歉.mpq, metadata=null)}
Object Class Name > jdk.proxy1.$Proxy0
开始链接到服务器:https://youku.com/g4XZhv...
成功连接到服务器!
下载视频...
下载完毕!
Video(id=g4XZhv, title=null, metadata=null)jdk.proxy1.$Proxy0代理类是在运行时生成字节码,利用arthas-boot.jar反编译得到的源码如下: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
82public final class $Proxy0 extends Proxy implements ThirdPartyVideo {
private static final Method m0;
private static final Method m1;
private static final Method m2;
private static final Method m3;
private static final Method m4;
private static MethodHandles.Lookup proxyClassLookup(MethodHandles.Lookup lookup) throws IllegalAccessException {
if (lookup.lookupClass() == Proxy.class && lookup.hasFullPrivilegeAccess()) {
return MethodHandles.lookup();
}
throw new IllegalAccessException(lookup.toString());
}
public Map popularVideos() {
try {
return (Map) this.h.invoke(this, m3, null);
} catch (Error | RuntimeException throwable) {
throw throwable;
} catch (Throwable throwable) {
throw new UndeclaredThrowableException(throwable);
}
}
public Video getVideo(String string) {
try {
return (Video) this.h.invoke(this, m4, new Object[]{string});
} catch (Error | RuntimeException throwable) {
throw throwable;
} catch (Throwable throwable) {
throw new UndeclaredThrowableException(throwable);
}
}
public $Proxy0(InvocationHandler invocationHandler) {
super(invocationHandler);
}
static {
try {
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m2 = Class.forName("java.lang.Object").getMethod("toString");
m3 = Class.forName("dev.pages.islongshiyu.patterns.p02.structural.proxy.dynamic.ThirdPartyVideo").getMethod("popularVideos");
m4 = Class.forName("dev.pages.islongshiyu.patterns.p02.structural.proxy.dynamic.ThirdPartyVideo").getMethod("getVideo", Class.forName("java.lang.String"));
} catch (NoSuchMethodException noSuchMethodException) {
throw new NoSuchMethodError(noSuchMethodException.getMessage());
} catch (ClassNotFoundException classNotFoundException) {
throw new NoClassDefFoundError(classNotFoundException.getMessage());
}
}
public boolean equals(Object object) {
try {
return (Boolean) this.h.invoke(this, m1, new Object[]{object});
} catch (Error | RuntimeException throwable) {
throw throwable;
} catch (Throwable throwable) {
throw new UndeclaredThrowableException(throwable);
}
}
public String toString() {
try {
return (String) this.h.invoke(this, m2, null);
} catch (Error | RuntimeException throwable) {
throw throwable;
} catch (Throwable throwable) {
throw new UndeclaredThrowableException(throwable);
}
}
public int hashCode() {
try {
return (Integer) this.h.invoke(this, m0, null);
} catch (Error | RuntimeException throwable) {
throw throwable;
} catch (Throwable throwable) {
throw new UndeclaredThrowableException(throwable);
}
}
}代理类
$Proxy0实现了ThirdPartyVideo,印证了实现类和代理类均实现了相同的接口。代理类
$Proxy0将自定义的CachedInvocationHandler对象传递给了父类。代理类
$Proxy0的执行流程如下:- 在测试类中通过代理对象调用
popularVideos()或getVideo()方法。 - 根据多态的特性,执行的是代理类
$Proxy0中的popularVideos()或getVideo()方法。 - 代理类
$Proxy0中的popularVideos()或getVideo()方法又调用了InvocationHandler接口的子实现类对象的invoke()方法。 invoke()方法通过反射执行了真实对象所属类YouKuVideo中的popularVideos()或getVideo()方法。
- 在测试类中通过代理对象调用
- CGLIB:
引入依赖:此处使用 Spring 修正后的 CGLIB,原版的 CGLIB 在高版本的 JDK 下有安全策略问题;这两者接口定义完全一致。
1
implementation("org.springframework:spring-core:6.0.12")
YouKuVideo:优酷视频服务类,和前面的例子对比,没有实现接口。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
56public class YouKuVideo {
public Video getVideo(String videoId) {
connectToServer("https://youku.com/" + videoId);
return getSomeVideo(videoId);
}
public Map<String, Video> popularVideos() {
connectToServer("https://youku.com/");
return getRandomVideos();
}
private int random(int min, int max) {
return min + (int) (Math.random() * ((max - min) + 1));
}
private void experienceNetworkLatency() {
int randomLatency = random(5, 10);
for (int i = 0; i < randomLatency; i++) {
try {
Thread.sleep(100);
} catch (InterruptedException ex) {
System.out.println(ex.getMessage());
}
}
}
private void connectToServer(String server) {
System.out.println("开始链接到服务器:" + server + "... ");
experienceNetworkLatency();
System.out.println("成功连接到服务器!");
}
private Map<String, Video> getRandomVideos() {
System.out.println("下载热门视频...");
experienceNetworkLatency();
Map<String, Video> v = new HashMap<>();
v.put("EUx2lQ", new Video("g4XZhv", "拳皇98C大门岚之山.avi"));
v.put("YjOvUK", new Video("sfMFvR", "黑狗咬伤两岁女童.mp4"));
v.put("o0OYtW", new Video("C8TjqY", "李佳琪道歉.mpq"));
v.put("UuUzUY", new Video("wN5CoW", "只因你太美.mov"));
v.put("IbufD9", new Video("j8ngHd", "PHP是世界上最好的语言.avi"));
System.out.println("下载完毕!");
return v;
}
private Video getSomeVideo(String videoId) {
System.out.println("下载视频... ");
experienceNetworkLatency();
Video video = new Video(videoId);
System.out.println("下载完毕!");
return video;
}
}Test:测试类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
41public class Test {
public static void main(String[] args) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(YouKuVideo.class);
enhancer.setCallback(new MethodInterceptor() {
private Map<String, Object> cachePopular = new HashMap<>();
private final Map<String, Object> cacheAll = new HashMap<>();
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
String mn = method.getName();
System.out.println("Object Class Name > " + obj.getClass().getName());
if (Objects.equals("getVideo", mn)) {
Object t = cacheAll.get(args[0]);// 从缓存中获取视频
/*
若未缓存该视频则从服务器下载该视频并缓存起来
*/
if (t == null) {
t = proxy.invokeSuper(obj, args);
cacheAll.put((String) args[0], t);
return t;
}
return t;
} else if (Objects.equals("popularVideos", mn)) {
/*
若未缓存该视频则从服务器下载该视频并缓存起来
*/
return cachePopular.isEmpty() ? cachePopular = (Map<String, Object>) proxy.invokeSuper(obj, args) : cachePopular;
} else {
return proxy.invokeSuper(obj, args);
}
}
});
YouKuVideo ykv = (YouKuVideo) enhancer.create();
System.out.println(ykv.popularVideos());
System.out.println(ykv.getVideo("g4XZhv"));
}
}输出结果:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15Object Class Name > dev.pages.islongshiyu.patterns.p02.structural.proxy.dynamic.cglib.YouKuVideo$$EnhancerByCGLIB$$6b3c1ae6
Object Class Name > dev.pages.islongshiyu.patterns.p02.structural.proxy.dynamic.cglib.YouKuVideo$$EnhancerByCGLIB$$6b3c1ae6
开始链接到服务器:https://youku.com/...
成功连接到服务器!
下载热门视频...
下载完毕!
{IbufD9=Video(id=j8ngHd, title=PHP是世界上最好的语言.avi, metadata=null), YjOvUK=Video(id=sfMFvR, title=黑狗咬伤两岁女童.mp4, metadata=null), EUx2lQ=Video(id=g4XZhv, title=拳皇98C大门岚之山.avi, metadata=null), UuUzUY=Video(id=wN5CoW, title=只因你太美.mov, metadata=null), o0OYtW=Video(id=C8TjqY, title=李佳琪道歉.mpq, metadata=null)}
Object Class Name > dev.pages.islongshiyu.patterns.p02.structural.proxy.dynamic.cglib.YouKuVideo$$EnhancerByCGLIB$$6b3c1ae6
Object Class Name > dev.pages.islongshiyu.patterns.p02.structural.proxy.dynamic.cglib.YouKuVideo$$EnhancerByCGLIB$$6b3c1ae6
开始链接到服务器:https://youku.com/g4XZhv...
成功连接到服务器!
下载视频...
Disconnected from the target VM, address: 'localhost:5348', transport: 'socket'
下载完毕!
Video(id=g4XZhv, title=null, metadata=null)
- JDK:Java 中提供了一个动态代理类
对比:
- JDK vs CGLIB:
- CGLIB 底层采用 ASM 字节码生成框架,使用字节码技术生成代理类,在 JDK 1.6 之前比 Java 反射效率要高;CGLIB 不能对声明为
final修饰的类或者方法进行代理,其原理是动态生成被代理类的子类。 - 在 JDK 1.6、JDK 1.7、JDK 1.8 逐步对 JDK 动态代理优化之后,在调用次数较少的情况下,JDK 代理效率高于 CGLIB 代理效率;当进行大量调用的时,JDK 1.6 和 JDK 1.7 比 CGLIB 代理效率低一点,但是到 JDK 1.8 的时候,JDK 代理效率高于 CGLIB 代理。
- 如果有接口使用 JDK 动态代理,如果没有接口使用 CGLIB 代理。
- CGLIB 底层采用 ASM 字节码生成框架,使用字节码技术生成代理类,在 JDK 1.6 之前比 Java 反射效率要高;CGLIB 不能对声明为
- 动态代理 vs 静态代理:
- 动态代理最大的好处是将接口中声明的所有方法转移到调用处理器的方法(如:
InvocationHandler.invoke())集中处理;在接口方法数量比较多的时候,可以进行灵活处理,不需要像静态代理那样每一个方法进行中转。 - 如果接口增加一个方法,静态代理模式除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度,而动态代理不会出现该问题。
- 动态代理最大的好处是将接口中声明的所有方法转移到调用处理器的方法(如:
- JDK vs CGLIB:
优点:
- 代理模式在客户端与目标对象之间起到一个中介作用和保护目标对象的作用
- 代理对象可以扩展目标对象的功能
- 代理模式能将客户端与目标对象分离,在一定程度上降低了系统的耦合度
缺点:增加了系统的复杂度
场景:
- 访问控制:代理模式可以用来控制对实际对象的访问权限;比如,只有特定用户或角色才能访问某些敏感数据。
- 远程访问:代理模式可以用来处理远程对象的访问;比如,通过代理对象来访问远程 Web 服务。
- 延迟加载:代理模式可以用来实现延迟加载;比如,通过代理对象来加载某些资源或数据,以避免在程序启动时就加载所有数据。
- 远程代理:当需要在不同的进程或机器之间进行通信时,可以使用远程代理来封装和管理通信的过程和数据。
适配器模式

出国旅游时,不同的国家的电源插座标准不同,国标的插头在国外不能直接充电;需要使用插座转换器,转换器插头面插入旅游所在地标准的插座,插座面供国标插头使用。

生活中这样的例子很多,手机充电器(将220v转换为5v的电压)、读卡器等,这就是适配器模式。
将一个类的接口转换成调用方希望的另外一个接口,使得原本由于接口不兼容的类能一起工作。
适配器模式分为类适配器模式和对象适配器模式,前者类之间的耦合度比后者高,且要求了解现有组件库中的相关组件的内部结构,应用相对较少些。
角色:适配器模式(Adapter)包含以下主要角色。
- 目标(Target)接口:当前系统业务所期待的接口,可以是抽象类、接口。
- 适配者(Adaptee)类:被访问和适配的现存组件库中的组件接口。
- 适配器(Adapter)类:转换器角色,通过继承或引用适配者的对象,把适配者接口转换成目标接口,让调用方按目标接口的格式访问适配者。
类适配器模式:定义一个适配器类来实现当前系统的业务接口,同时又继承现有组件库中已经存在的组件; 以读卡器为例,现有一台电脑只能读取 SD 卡,采用适配器模式来使该电脑能读取 TF 卡中的数据。
classDiagram class SDCard { <<接口>> ~ String read() ~ void write(String) } class Computer { + String readSD(SDCard) } class SDCardImpl { + String read() + void write(String) } class TFCard { <<接口>> ~ String read() ~ void write(String) } class SDAdapterTF { + String read() + void write(String) } class TFCardImpl { + String read() + void write(String) } SDCard .. Computer SDCard <|.. SDCardImpl SDCard <|.. SDAdapterTF TFCardImpl <|-- SDAdapterTF TFCard <|.. TFCardImplSDCard:SD 卡接口类1
2
3
4
5public interface SDCard {
String read();
void write(String data);
}SDCardImpl:SD 卡接口实现类1
2
3
4
5
6
7
8
9
10
11
12
13
14public class SDCardImpl implements SDCard{
public String read() {
return "reading data from SD card";
}
public void write(String data) {
if (null == data || data.isEmpty()) {
return;
}
System.out.println("writing " + data + " to SD card");
}
}TFCard:TF 卡接口类1
2
3
4
5public interface TFCard {
String read();
void write(String data);
}TFCardImpl:TF 卡接口实现类1
2
3
4
5
6
7
8
9
10
11
12
13
14public class TFCardImpl implements TFCard {
public String read() {
return "reading data from TF card";
}
public void write(String data) {
if (null == data || data.isEmpty()) {
return;
}
System.out.println("writing " + data + " to TF card");
}
}Computer:电脑类1
2
3
4
5
6
7
8
9public class Computer {
public String readSD(SDCard sdCard){
if(Objects.isNull(sdCard)){
throw new NullPointerException("SD card can't be null while reading");
}
return sdCard.read();
}
}SDAdapterTF:适配器类,SD 卡兼容 TF 卡1
2
3
4
5
6
7
8
9
10
11
12
13
14public class SDAdapterTF extends TFCardImpl implements SDCard {
public String read() {
System.out.println("adapter reading tf card");
return super.read();
}
public void write(String data) {
System.out.println("adapter writing tf card");
super.write(data);
}
}Test:测试类1
2
3
4
5
6
7
8
9public class Test {
public static void main(String[] args) {
Computer computer = new Computer();
String dataFromSD = computer.readSD(new SDCardImpl());
String dataFromTF = computer.readSD(new SDAdapterTF());// adapter reading tf card
System.out.println(dataFromSD);// reading data from SD card
System.out.println(dataFromTF);// reading data from TF card
}
}输出如下:
1
2
3adapter reading tf card
reading data from SD card
reading data from TF card
对象适配器模式:将现有组件库中已经实现相应功能的组件引入适配器类中,该类同时实现当前系统的业务接口;举个栗子,SD 卡兼容 TF 卡的适配器类实现
SDCard接口,并引用 TF 卡接口的实现。classDiagram class SDCardImpl { + String read() + void write(String) } class SDCard { <<接口>> ~ String read() ~ void write(String) } class SDAdapterTF { - TFCard tfCard + SDAdapterTF SDAdapterTF(TFCard) + String read() + void write(String) } class TFCardImpl { + String read() + void write(String) } class Computer { + String readSD(SDCard) } class TFCard { <<接口>> ~ String read() ~ void write(String) } SDCard .. Computer SDCard <|.. SDCardImpl SDCard <|.. SDAdapterTF TFCard -- SDAdapterTF TFCard <|.. TFCardImplSDAdapterTF:相较于类适配器模式,对象适配器模式只需要改造该类即可。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19public class SDAdapterTF implements SDCard {
private final TFCard tfCard;
public SDAdapterTF(TFCard tfCard) {
this.tfCard = tfCard;
}
public String read() {
System.out.println("adapter reading tf card");
return tfCard.read();
}
public void write(String data) {
System.out.println("adapter writing tf card");
tfCard.write(data);
}
}
场景:
- 系统需要复用现有类,而该类的接口不符合系统的需求,可以使用适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。
- 多个组件功能类似,但接口不统一且可能会经常切换时,可使用适配器模式,使得客户端可以以统一的接口使用它们。
使用:
- 灵活使用时:选择对象适配器模式,因为类适配器使用对象继承的方式,是静态的定义方式;而对象适配器使用对象组合的方式,是动态组合的方式。
- 需要同时适配源类和其子类时:选择对象的适配器。
- 对于类适配器,由于适配器(Adapter)直接继承了适配者(Adaptee),使得适配器不能和适配者子类一起工作,因为继承是静态的关系,当适配器继承了适配者后,就不可能再去处理适配者的子类了;
- 对于对象适配器,一个适配器可以把多种不同的源适配到同一个目标;换言之,同一个适配器可以把源类和它的子类都适配到目标接口。因为对象适配器采用的是对象组合的关系,只要对象类型正确,是不是子类都无所谓。
- 需要重新定义适配者的部分行为时:选择类适配器。
- 对于类适配器,适配器可以重定义适配者的部分行为,相当于子类覆盖父类的部分实现方法。
- 对于对象适配器,要重新定义适配者的行为比较困难,这种情况下,需要定义适配者的子类来实现重定义,然后让适配器组合子类。虽然重定义适配者的行为比较困难,但是想要增加一些新的行为则方便的很,而且新增加的行为可同时适用于所有的源。
- 仅仅希望使用方便时:选择类适配器。
- 对于类适配器,仅仅引入了一个对象,并不需要额外的引用来间接得到适配者。
- 对于对象适配器,需要额外的引用来间接得到适配者。
拓展:
Reader、InputStream的适配使用的是InputStreamReader,InputStreamReader做了InputStream与Reader字符流之间的转换,此处就是典型的设计模式。classDiagram direction BT class InputStreamReader { - StreamDecoder sd + read(char[], int, int) int + read() int } class Reader { + read(char[], int, int) int + read() int } class StreamDecoder { - InputStream in + read() int + read(char[], int, int) int } class InputStream { + read(byte[]) int + read(byte[], int, int) int + read() int } InputStreamReader --> Reader StreamDecoder --> Reader StreamDecoder .. InputStreamReader InputStream .. StreamDecoderInputStreamReader部分源码:1
2
3
4
5
6
7
8
9
10
11
12public class InputStreamReader extends Reader {
private final StreamDecoder sd;
public int read() throws IOException {
return sd.read();
}
public int read(char[] cbuf, int off, int len) throws IOException {
return sd.read(cbuf, off, len);
}
}
装饰器模式
在不改变现有对象结构的情况下,动态地给该对象增加一些职责(即增加其额外功能)的模式。
角色:装饰器(Decorator)模式有以下角色。
抽象构件(Component):定义一个抽象接口以规范准备接收附加责任的对象。
具体构件(Concrete Component):实现抽象构件,通过装饰角色为其添加一些职责。
抽象装饰(Decorator):继承或实现抽象构件,并包含具体构件的实例,可以通过其子类扩展具体构件的功能。
具体装饰(Concrete Decorator):实现抽象装饰的相关方法,并给具体构件对象添加附加的责任。
classDiagram class Decorator { <<抽象类>> - Component component + Decorator Decorator(Component) + void operation() } class Test { + void main(String[])$ } class Component { <<接口>> ~ void operation() } class ConcreteComponent { + ConcreteComponent ConcreteComponent() + void operation() } class ConcreteDecorator { + ConcreteDecorator ConcreteDecorator(Component) + void operation() + void extraOperation() } Component <|.. Decorator Component <|.. ConcreteComponent Decorator <|-- ConcreteDecorator note for Test "Component component = new ConcreteComponent();\ncomponent.operation();\ncomponent = new ConcreteDecorator(component);\ncomponent.operation();"
案例:开发一个提供通知功能的库, 其他程序可使用它向用户发送关于重要事件的通知;最初实现了短信通知方式,随着需求的迭代,用户希望使用除短信通知之外的功能。有些用户希望在微信上接收消息,有些用户则希望在 QQ 上接收消息。
采用继承方式实现:
classDiagram class Notifier { } class QQNotifier { } class SMSWeChatNotifier { } class SMSNotifier { } class SMSWeChatQQNotifier { } class SMSQQNotifier { } class WeChatNotifier { } class WeChatQQNotifier { } Notifier <|-- QQNotifier Notifier <|-- SMSWeChatNotifier Notifier <|-- SMSNotifier Notifier <|-- SMSWeChatQQNotifier Notifier <|-- SMSQQNotifier Notifier <|-- WeChatNotifier Notifier <|-- WeChatQQNotifier- 继承是静态的,无法在运行时更改已有对象的行为,只能使用由不同子类创建的对象来替代当前的整个对象。
- 继承很容易导致子类数量爆炸式增长。
采用装饰器来实现:
classDiagram direction TB class Notifier { <<接口>> ~ void send(String) } class WeChatNotifierDecorator { + WeChatNotifierDecorator WeChatNotifierDecorator(Notifier) + void send(String) + void extraSend(String) } class NotifierDecorator { <<抽象类>> - Notifier notifier + NotifierDecorator NotifierDecorator(Notifier) + void send(String) } class SMSNotifier { + void send(String) } class QQNotiferDecorator { + QQNotiferDecorator QQNotiferDecorator(Notifier) + void send(String) + void extraSend(String) } NotifierDecorator <|-- WeChatNotifierDecorator Notifier <|.. NotifierDecorator Notifier <|.. SMSNotifier NotifierDecorator <|-- QQNotiferDecorator
优点:
- 装饰器模式可以带来比继承更加灵活性的扩展功能,使用更加方便,可以通过组合不同的装饰器对象来获取具有不同行为状态的多样化的结果。
- 装饰器模式比继承更具良好的扩展性,完美的遵循开闭原则,继承是静态的附加责任,装饰器则是动态的附加责任。
- 装饰类和被装饰类可以独立发展,不会相互耦合,装饰模式是继承的一个替代模式,装饰模式可以动态扩展一个实现类的功能。
场景:
- 当不能采用继承的方式对系统进行扩充或者采用继承不利于系统扩展和维护时;不能采用继承的情况主要有两类:
- 系统中存在大量独立的扩展,为支持每一种组合将产生大量的子类,使得子类数目呈爆炸性增长;
- 类定义为不能继承,如:
final修饰的类;
- 在不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责。
- 当对象的功能要求可以动态地添加,也可以再动态地撤销时。
- 当不能采用继承的方式对系统进行扩充或者采用继承不利于系统扩展和维护时;不能采用继承的情况主要有两类:
拓展:JDK 源码 IO 流的包装类使用到了装饰器模式;如,
BufferedInputStream,BufferedOutputStream,BufferedReader,BufferedWriter等。classDiagram direction BT class Writer { # Writer() # Writer(Object) } class BufferedWriter { + BufferedWriter(Writer, int) + BufferedWriter(Writer) } class FileWriter { + FileWriter(FileDescriptor) + FileWriter(String, Charset) + FileWriter(String, Charset, boolean) + FileWriter(File, Charset, boolean) + FileWriter(File, boolean) + FileWriter(File) + FileWriter(String, boolean) + FileWriter(File, Charset) + FileWriter(String) } class OutputStreamWriter { + OutputStreamWriter(OutputStream) + OutputStreamWriter(OutputStream, String) + OutputStreamWriter(OutputStream, Charset) + OutputStreamWriter(OutputStream, CharsetEncoder) } FileWriter --> OutputStreamWriter OutputStreamWriter --> Writer BufferedWriter --> Writer BufferedWriter .. FileWriterBufferedWriter使用装饰器模式对Writer子实现类进行了增强,添加了缓冲区,提高了写数据的效率。对比:静态代理和装饰器模式的区别如下
- 相同点:
- 都要实现与目标类相同的业务接口
- 在两个类中都要声明目标对象
- 都可以在不修改目标类的前提下增强目标方法
- 不同点:
- 二者目的不同,装饰器模式是为了增强目标对象而静态代理是为了保护和隐藏目标对象
- 获取目标对象构建的方式不同,装饰器模式是由外界传递进来,可以通过构造方法传递;静态代理是在代理类内部创建,以此来隐藏目标对象。
- 相同点:
桥接模式
桥接模式将抽象与实现分离,使它们可以独立变化。它是用组合关系代替继承关系来实现,从而降低了抽象和实现这两个可变维度的耦合度。
角色:
抽象化(Abstraction)角色:定义抽象类,并包含一个对实现化对象的引用。
扩展抽象化(Refined Abstraction)角色:是抽象化角色的子类,实现父类中的业务方法,并通过组合关系调用实现化角色中的业务方法。
实现化(Implementor)角色:定义实现化角色的接口,供扩展抽象化角色调用。
具体实现化(Concrete Implementor)角色:给出实现化角色接口的具体实现。
classDiagram direction TD class ConcreteImplementorB { + void operationImpl() } class Abstraction { <<抽象类>> # Implementor implementor + Abstraction Abstraction(Implementor) + void operation()* } class RefinedAbstraction { + RefinedAbstraction RefinedAbstraction(Implementor) + void operation() } class ConcreteImplementorA { + void operationImpl() } class Implementor { <<接口>> ~ void operationImpl() } class Client { } Implementor <|.. ConcreteImplementorB Implementor -- Abstraction Abstraction <|-- RefinedAbstraction Implementor <|.. ConcreteImplementorA Client .. Abstraction
案例:现在有一个需求,需要创建不同的图形,并且每个图形都有可能会有不同的颜色。
采用继承方式实现:用继承方式会造成类爆炸,扩展起来不灵活;每在某个维度上新增一个具体实现都要增加多个子类。
classDiagram direction TB class GreenCircle { ~ void draw() } class RedRectangle { ~ void draw() } class Square { <<抽象类>> } class GreenRectangle { ~ void draw() } class BlueCircle { ~ void draw() } class BlueSquare { ~ void draw() } class Circle { <<抽象类>> } class BlueRectangle { ~ void draw() } class GreenSquare { ~ void draw() } class Shape { <<抽象类>> ~ void draw()* } class RedCircle { ~ void draw() } class RedSquare { ~ void draw() } class Rectangle { <<抽象类>> } Circle <|-- GreenCircle Rectangle <|-- RedRectangle Shape <|-- Square Rectangle <|-- GreenRectangle Circle <|-- BlueCircle Square <|-- BlueSquare Shape <|-- Circle Rectangle <|-- BlueRectangle Square <|-- GreenSquare Circle <|-- RedCircle Square <|-- RedSquare Shape <|-- Rectangle采用桥接模式实现:
classDiagram class Blue { + void paint() } class Red { + void paint() } class Rectangle { + Rectangle Rectangle(Color) + void draw() } class Square { + Square Square(Color) + void draw() } class Green { + void paint() } class Shape { <<抽象类>> # Color color + Shape Shape(Color) + void draw()* } class Client { } class Circle { + Circle Circle(Color) + void draw() } class Color { <<接口>> ~ void paint() } Color <|.. Blue Color <|.. Red Shape <|-- Rectangle Shape <|-- Square Color <|.. Green Color -- Shape Shape <|-- Circle Client .. Shape
优点:
- 桥接模式提高了系统的可扩充性,在两个变化维度中任意扩展一个维度,都不需要修改原有系统。
- 实现细节对客户透明,可以对用户隐藏实现细节。
- 分离抽象接口及其实现部分。
缺点:
- 桥接模式的引入会增加系统的理解与设计难度,由于聚合关联关系建立在抽象层,要求开发者针对抽象进行设计与编程。
- 桥接模式要求正确识别出系统中两个独立变化的维度,因此其使用范围具有一定的局限性。
场景:
- 当一个类存在两个独立变化的维度,且这两个维度都需要进行扩展时。
- 当一个系统不希望使用继承或因为多层次继承导致系统类的个数急剧增加时。
- 当一个系统需要在构件的抽象化角色和具体化角色之间增加更多的灵活性时;避免在两个层次之间建立静态的继承联系,通过桥接模式可以使它们在抽象层建立一个关联关系。
外观模式
外观模式(Facade)又名门面模式,通过为多个复杂的子系统提供一个一致的接口,而使这些子系统更加容易被访问。
对外有一个统一接口,外部应用程序不用关心内部子系统的具体的细节,降低应用程序的复杂度,提高了程序的可维护性。
外观模式是迪米特法则的典型应用。
角色:
外观(Facade)角色:为多个子系统对外提供一个共同的接口。
子系统(Sub System)角色:实现系统的部分功能,客户可以通过外观角色访问它。
classDiagram direction LR class Client { } class Facade { - SubSystemA subSystemA - SubSystemB subSystemB - SubSystemC subSystemC + Facade Facade() + void wrapOperation() } Client ..> Facade class SubSystemA { + void operationA() } class SubSystemB { + void operationB() } class SubSystemC { + void operationC() } SubSystemA -- Facade SubSystemB -- Facade SubSystemC -- Facade
案例:智能家电控制
classDiagram class Light { + void on() + void off() } class AirConditioner { + void on() + void off() } class SmartApplianceFacade { - TV tv - Light light - AirConditioner airConditioner + SmartApplianceFacade SmartApplianceFacade() + void control(String) - void on() - void off() } class TV { + void on() + void off() } class Client { } Client .. SmartApplianceFacade TV -- SmartApplianceFacade Light -- SmartApplianceFacade AirConditioner -- SmartApplianceFacadeLight:1
2
3
4
5
6
7
8
9public class Light {
public void on(){
System.out.println("Light.on");
}
public void off(){
System.out.println("Light.off");
}
}TV:1
2
3
4
5
6
7
8
9public class TV {
public void on(){
System.out.println("TV.on");
}
public void off(){
System.out.println("TV.off");
}
}AirConditioner:1
2
3
4
5
6
7
8
9public class AirConditioner {
public void on() {
System.out.println("AirConditioner.on");
}
public void off() {
System.out.println("AirConditioner.off");
}
}SmartApplianceFacade: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
41public class SmartApplianceFacade {
private final TV tv;
private final Light light;
private final AirConditioner airConditioner;
public SmartApplianceFacade() {
this.tv = new TV();
this.light = new Light();
this.airConditioner = new AirConditioner();
}
public void control(String voice) {
if (voice.contains("turn on")) {
on();
} else if (voice.contains("turn off")) {
off();
}
}
/**
* turn on all appliances while get up
*/
private void on() {
System.out.println("it's time to get up!");
tv.on();
light.on();
airConditioner.on();
}
/**
* turn off all appliances while go to bed
*/
private void off() {
System.out.println("it's time to go to bed now!");
tv.off();
light.off();
airConditioner.off();
}
}Client:1
2
3
4
5
6
7public class Client {
public static void main(String[] args) {
SmartApplianceFacade facade = new SmartApplianceFacade();
facade.control("turn on");
facade.control("turn off");
}
}输出如下:
1
2
3
4
5
6
7
8it's time to get up!
TV.on
Light.on
AirConditioner.on
it's time to go to bed now!
TV.off
Light.off
AirConditioner.off优点:
- 降低了子系统与客户端之间的耦合度,使得子系统的变化不会影响调用它的客户类。
- 对客户屏蔽了子系统组件,减少了客户处理的对象数目,并使得子系统使用起来更加容易。
缺点:不符合开闭原则,修改很麻烦。
场景:
- 对分层结构系统构建时,使用外观模式定义子系统中每层的入口点可以简化子系统之间的依赖关系。
- 当一个复杂系统的子系统很多时,外观模式可以为系统设计一个简单的接口供外界访问。
- 当客户端与多个子系统之间存在很大的联系时,引入外观模式可将它们分离,从而提高子系统的独立性和可移植性。
拓展:Web 容器之 Tomcat
RequestFacade类就是使用了外观模式;既使用了Request,又防止了对Request中方法的不合理访问。classDiagram direction BT class ServletRequest { <<接口>> } class HttpServletRequest { <<接口>> } class RequestFacade { # Request request } class Request RequestFacade o.. Request Request ..> HttpServletRequest RequestFacade ..> HttpServletRequest HttpServletRequest --> ServletRequest