设计模式之享元模式、组合模式和代理模式

结构型模式还剩下这三个,一起记录下来吧

享元模式

描述:通过共享以便有效的支持大量小颗粒对象。

享元模式

享元模式可以避免大量非常相似类的开销,在程序设计中,有时需要生成大量细粒度的类实例来表示数据。如果能发现这些实例除了几个参数外基本都是相同的,有时就能够受大幅度地减少需要实例化的类的数量。如果能把那些参数移到类实例的外面,在方法调用时将他们传递进来,就可以通过共享大幅度减少单个实例的数目。

什么时候用享元模式?
如果一个应用程序使用了大量的对象,而大量的这些对象造成了很大的存储开销时就应该考虑使用;还有就是对象的大多数状态可以是外部状态,如果删除对象的外部状态,那么可以用相对较少的共享对象取代很多组对象,此时可以考虑使用享元模式。

享元模式的4个角色

  • Flyweight(抽象享元类):通常是一个接口或抽象类,在抽象享元类中声明了具体享元类公共的方法,这些方法可以向外界提供享元对象的内部数据(内部状态),同时也可以通过这些方法来设置外部数据(外部状态)。

  • ConcreteFlyweight(具体享元类):它实现了抽象享元类,其实例称为享元对象;在具体享元类中为内部状态提供了存储空间。通常我们可以结合单例模式来设计具体享元类,为每一个具体享元类提供唯一的享元对象。

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

  • FlyweightFactory(享元工厂类):享元工厂类用于创建并管理享元对象,它针对抽象享元类编程,将各种类型的具体享元对象存储在一个享元池中,享元池一般设计为一个存储“键值对”的集合(也可以是其他类型的集合),可以结合工厂模式进行设计;当用户请求一个具体享元对象时,享元工厂提供一个存储在享元池中已创建的实例或者创建一个新的实例(如果不存在的话),返回新创建的实例并将其存储在享元池中。

以棋类运动为例,棋子可以看作抽象享元类,围棋棋子,五子棋棋子,象棋棋子这些可以看作是具体享元类。各类棋子除了具体位置(在此处以(x,y)坐标表示)这一外部状态不同,其他属性都相同。此时就可以使用享元模式,比如在一局围棋游戏中,只有黑棋和白棋这两种具体享元类,大大减少了重复生成棋子的内存开销。

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
//Flyweight
abstract class Piece {
public abstract void setPosition(int x, int y);
}

//ConcreteFlyweight
class GoPieces extends Piece {
private String type;

public GoPieces(String type) {
this.type = type;
}

@Override
public void setPosition(int x, int y) {
System.out.println(type + " 下在(" + x + "," + y + ")处。");
}
}

//FlyweightFactory
class PieceFactory {
private Map<String, Piece> pieceMap = new HashMap<>();

public Piece getPiece(String key) {
if (!pieceMap.containsKey(key)) {
pieceMap.put(key, new GoPieces(key));
}
return pieceMap.get(key);
}

public int getNumberOfPiecesType() {
return pieceMap.size();
}
}

主程序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class Flyweight {
public static void main(String[] args) {
PieceFactory pieceFactory = new PieceFactory();
Piece black1 = pieceFactory.getPiece("黑棋");
black1.setPosition(3, 4);

Piece write1 = pieceFactory.getPiece("白棋");
write1.setPosition(5, 6);

Piece black2 = pieceFactory.getPiece("黑棋");
black2.setPosition(1, 2);

Piece write2 = pieceFactory.getPiece("白棋");
write2.setPosition(3, 7);

Piece black3 = pieceFactory.getPiece("黑棋");
black3.setPosition(4, 8);

System.out.println(pieceFactory.getNumberOfPiecesType());
}
}

组合模式

描述:把多个对象组成树状结构来表示局部与整体,这样用户可以一样的对待单个对象和对象的组合。

组合模式

组合模式要解决的是整体和部分可以被一致对待的问题

何时使用组合模式?
需求中是体现部分与整体层次的结构时,希望用户可以忽略组合对象与单个对象的不同,统一地使用组合结构中的所有对象时,就应该考虑用组合模式。

组合模式的3个角色

  • Component(抽象构件):它可以是接口或抽象类,为叶子构件和容器构件对象声明接口,在该角色中可以包含所有子类共有行为的声明和实现。在抽象构件中定义了访问及管理它的子构件的方法,如增加子构件、删除子构件、获取子构件等。

  • Leaf(叶子构件):它在组合结构中表示叶子节点对象,叶子节点没有子节点,它实现了在抽象构件中定义的行为。对于那些访问及管理子构件的方法,可以通过异常等方式进行处理。

  • Composite(容器构件):它在组合结构中表示容器节点对象,容器节点包含子节点,其子节点可以是叶子节点,也可以是容器节点,它提供一个集合用于存储子节点,实现了在抽象构件中定义的行为,包括那些访问及管理子构件的方法,在其业务方法中可以递归调用其子节点的业务方法。

组合模式的关键是定义了一个抽象构件类,它既可以代表叶子,又可以代表容器,而客户端针对该抽象构件类进行编程,无须知道它到底表示的是叶子还是容器,可以对其进行统一处理。同时容器对象与抽象构件类之间还建立一个聚合关联关系,在容器对象中既可以包含叶子,也可以包含容器,以此实现递归组合,形成一个树形结构。

透明模式与安全模式

  • 透明模式在Component中声明所有管理子对象的方法,这样做的好处是叶结点和枝结点对于外界没有区别,它们具备完全一致的行为接口,但Leaf本身不具备add()、remove()功能,所有实现它们是没有意义的。

  • 安全模式在Component接口中不去声明管理子对象的方法,那么子类Leaf就不需要实现它们,而是在Composite中实现,不过由于不够透明,叶结点和枝结点有不同的接口,客户端调用时要做相应的判断。

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
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
//Component
abstract class Company {
protected String name;

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

public abstract void add(Company company);

public abstract void remove(Company company);

public abstract void display(int depth);

public abstract void work();
}

//Composite
class ConcreteCompany extends Company {
private List<Company> companyList = new ArrayList<>();

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

@Override
public void add(Company company) {
companyList.add(company);
}

@Override
public void remove(Company company) {
companyList.remove(company);
}

@Override
public void display(int depth) {
for (int i = 0; i < depth; ++i) {
System.out.print('-');
}
System.out.println(name);
for (Company component : companyList) {
component.display(depth + 2);
}
}

@Override
public void work() {
for (Company component : companyList) {
component.work();
}
}
}

//Leaf
class HRDepartment extends Company {
public HRDepartment(String name) {
super(name);
}

@Override
public void add(Company company) {

}

@Override
public void remove(Company company) {

}

@Override
public void display(int depth) {
for (int i = 0; i < depth; ++i) {
System.out.print('-');
}
System.out.println(name);
}

@Override
public void work() {
System.out.println(name + " 员工招聘培训管理");
}
}

class FinanceDepartment extends Company {
public FinanceDepartment(String name) {
super(name);
}

@Override
public void add(Company company) {

}

@Override
public void remove(Company company) {

}

@Override
public void display(int depth) {
for (int i = 0; i < depth; ++i) {
System.out.print('-');
}
System.out.println(name);
}

@Override
public void work() {
System.out.println(name + " 公司财务收支管理");
}
}

主程序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class Composite {
public static void main(String[] args) {
ConcreteCompany root = new ConcreteCompany("北京总公司");
root.add(new HRDepartment("总公司人力资源部"));
root.add(new FinanceDepartment("总公司财务部"));

ConcreteCompany comp = new ConcreteCompany("上海华东分公司");
comp.add(new HRDepartment("华东分公司人力资源部"));
comp.add(new FinanceDepartment("华东分公司财务部"));
root.add(comp);

ConcreteCompany comp1 = new ConcreteCompany("南京办事处");
comp.add(new HRDepartment("南京办事处人力资源部"));
comp.add(new FinanceDepartment("南京办事处财务部"));
comp.add(comp1);

root.display(1);
root.work();
}
}

代理模式

描述:为其他对象提供一个代理以控制对这个对象的访问。

代理模式

代理模式的3个角色

  • Subject(抽象角色):通过接口或抽象类声明真实角色实现的业务方法。

  • Proxy(代理角色):实现抽象角色,是真实角色的代理,通过真实角色的业务逻辑方法来实现抽象方法,并可以附加自己的操作。

  • RealSubject(真实角色):实现抽象角色,定义真实角色所要实现的业务逻辑,供代理角色调用。

代理模式又分为静态代理和动态代理

先来举个例子,电脑和手机都可以联网,现在有个代理VPN也可以联网,我们用VPN代理实现翻墙功能,用代码实现就是

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
//Subject
interface Networking {
void connection();
}

//RealSubject
class Computer implements Networking {
private String name;

public Computer() {
}

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

@Override
public void connection() {
System.out.println(name + " 已联网,但访问网站受限。");
}
}

class SmartPhone implements Networking {
private String name;

public SmartPhone() {
}

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

@Override
public void connection() {
System.out.println(name + " 已联网,但访问网站受限。");
}
}

//Proxy
class ComputerVPNProxy implements Networking {
Computer computer;

public ComputerVPNProxy(Computer computer) {
this.computer = computer;
}

@Override
public void connection() {
computer.connection();
System.out.println("已翻墙,开始网上冲浪吧!");
}
}

此处用了静态代理的实现方式,可以看到实现起来非常简单,但有个缺点,如果我们不确定需要代理某个真实类的时候会比较麻烦,而且在类过多的时候,目标对象与代理对象都要维护,会使系统复杂度提升,维护起来也更加麻烦。这时我们就可以使用动态代理来进行解决。

这里记录两种动态代理的方式

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
//JDK动态代理
class DynamicVPNProxy implements InvocationHandler {
private Networking networking;

public DynamicVPNProxy(Networking networking) {
this.networking = networking;
}

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = null;
if ("connection".equals(method.getName())) {
result = method.invoke(networking, args);
}
System.out.println("已翻墙,开始网上冲浪吧!");
return result;
}
}


//cglib代理
class CglibVPNProxy implements MethodInterceptor {
private Object target;//注意 target最好写一个无参构造方法

public CglibVPNProxy(Object target) {
this.target = target;
}

public Object getVPNInstance() {
//1工具类
Enhancer en = new Enhancer();
//2设置父类
en.setSuperclass(target.getClass());
//3设置回调函数
en.setCallback(this);
//4创建子类(代理对象)
return en.create();
}

@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
Object result = null;
if ("connection".equals(method.getName())) {
result = method.invoke(target, objects);
}
System.out.println("已翻墙,开始网上冲浪吧!");
return result;
}
}

主程序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class Proxy {
public static void main(String[] args) {
Computer computer = new Computer("IBN-5100");
computer.connection();

//静态代理
Networking computerVPN = new ComputerVPNProxy(computer);
computerVPN.connection();

//JDK动态代理
Networking smartPhoneVPN = (Networking) java.lang.reflect.Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(),
new Class[]{Networking.class}, new DynamicVPNProxy(new SmartPhone("IPhoneX")));
smartPhoneVPN.connection();

//Cglib动态代理
Networking smartPhoneVPN1 = (Networking) new CglibVPNProxy(new SmartPhone("MI6")).getVPNInstance();
smartPhoneVPN1.connection();
}
}

参考资料:

0%