设计模式之结构型模式

这一次我们来学习结构型模式。结构型模式主要包括:

  • 适配器模式(Adapter Pattern)
  • 桥接模式(Bridge Pattern)
  • 过滤器模式(Filter、Criteria Pattern)
  • 组合模式(Composite Pattern)
  • 装饰器模式(Decorator Pattern)
  • 外观模式(Facade Pattern)
  • 享元模式(Flyweight Pattern)
  • 代理模式(Proxy Pattern)

这些设计模式关注类和对象的组合。继承的概念被用来组合接口和定义组合对象获得新功能的方式。


适配器模式(Adapter Pattern)

适配器模式是两个不兼容的接口之间的桥梁,使原本因为接口不兼容而不能一起工作的类正常工作。
这种模式涉及到一个单一的类,负责加入独立的或者不兼容的接口功能。比如说,内存卡需要在读卡器中才能与电脑通信,读卡器就是一个适配器。再比如,JDK1.1提供了Enumeration接口,而在1.2中提供了Iterator接口,想要使用JDK1.2,则需要把以前系统中的Enumeration接口转换为Iterator接口。
适配器模式可以让任意两个没有关联的类一起运行,提高了类的复用,灵活性非常好。但,如果滥用适配器模式,那么类和类关系错综复杂,使系统变得非常凌乱,让人无从下手。
适配器模式中的角色包括:

  1. 目标接口(Target):客户所期待的接口。目标可以是具体的或抽象的类,也可以是接口。
  2. 需要适配的类(Adaptee):需要适配的类或适配者类。
  3. 适配器(Adapter):通过包装一个需要适配的对象,把原接口转换成目标接口。

适配器模式不是在设计的时候就需要规划的,而是解决正在运行的问题。
下面这个例子是对只支持Mp3播放器的扩展,使其兼容vlc和MP4。

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

interface MediaPlayer {
public void play(String audioType, String fileName);
}
//为高级播放器设计接口
interface AdvancedMediaPlayer {
public void playVlc(String fileName);
public void playMp4(String fileName);
}
//实现高级播放器
class VlcPlayer implements AdvancedMediaPlayer {
@Override
public void playVlc(String fileName) {
System.out.println("playing vlc");
}
@Override
public void playMp4(String fileName) {
//do nothing
}
}
class Mp4Player implements AdvancedMediaPlayer {
@Override
public void playVlc(String fileName) {
//do nothing
}
@Override
public void playMp4(String fileName) {
System.out.println("playing mp4");
}
}
class MediaAdapter implements MediaPlayer {
AdvancedMediaPlayer advancedMusicPlayer;
public MediaAdapter(String audioType) {
switch (audioType.toLowerCase()) {
case "vlc":
advancedMusicPlayer = new VlcPlayer();
break;
case "mp4" :
advancedMusicPlayer = new Mp4Player();
break;
}
}
@Override
public void play(String audioType, String fileName) {
switch (audioType.toLowerCase()) {
case "vlc":
advancedMusicPlayer.playVlc(fileName);
break;
case "mp4" :
advancedMusicPlayer.playMp4(fileName);
break;
}
}
}
//Adapter
class AudioPlayer implements MediaPlayer {
MediaAdapter midiaAdapter;
@Override
public void play(String audioType, String fileName) {
//最基础支持mp3
if (audioType.equalsIgnoreCase("mp3"))
System.out.println("playing mp3");
//扩展播放vlc和MP4
else if (audioType.equalsIgnoreCase("vlc") || audioType.equalsIgnoreCase("mp4")) {
midiaAdapter = new MediaAdapter(audioType);
midiaAdapter.play(audioType, fileName);
}
}
}
public class AdapterPatternDemo {
public static void main(String[] args) {
AudioPlayer audioPlayer = new AudioPlayer();
audioPlayer.play("mp3", "Demons.mp3");
audioPlayer.play("mp4", "alone.mp4");
audioPlayer.play("vlc", "go ahead.vlc");
audioPlayer.play("avi", "Mrs Cang.avi");
}
}

桥接模式

用于把抽象化与实现化解耦,使得二者可以独立变化。这种模式涉及到一个作为桥接的接口,使得实体类的功能独立于接口实现类。桥接模式替换了普通的继承,体现了 对象组合 的优势。
桥接模式的概念非常抽象,我们使用画图这个例子来说明。任务要求是这样的:要求产生形状A和B,通过方式C和D来画出。如果按照一般的方法在继承A的示例中分别实现C和D,那么如果更改需求,增加形状或者增加方式都会造成类的个数指数级上涨,这就是所谓的类爆炸。这种情况下,那就应该使用桥接模式了。
接下来是另一个例子,分别是“车”和“路”这两个维度的元素的组合。首先分别抽象这两个维度,在内部定义其关系。在子类中分别实现这些抽象。最终在客户端中分别实现实例然后通过内部组合的关系得到结果。

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
abstract class AbstractCar{
void run(){};
}
abstract class AbstractRoad{
AbstractCar aCar;
void run(){};
}
class Street extends AbstractRoad{
@Override
void run() {
super.run();
aCar.run();
System.out.println("在市区街道行驶");
}
}
class SpeedWay extends AbstractRoad{
@Override
void run() {
super.run();
aCar.run();
System.out.println("在高速公路行驶");
}
}
class Car extends AbstractCar{
@Override
void run() {
super.run();
System.out.print("小汽车");
}
}
class Bus extends AbstractCar{
@Override
void run() {
super.run();
System.out.print("公交车");
}
}
public static void main(String[] args){
AbstractRoad speedWay = new SpeedWay();
speedWay.aCar = new Car();
speedWay.run();

AbstractRoad street = new Street();
street.aCar = new Bus();
street.run();
}

过滤器模式

过滤器模式(Filter Pattern)或标准模式(Criteria Pattern)指的是一种模式。这种模式允许开发人员使用不同的标准来过滤对象,通过逻辑运算以解耦的方式将它们连接起来。可以这样说,小萌走入了一个理发店,出来了变成了小明。这个过程中,小萌这个人没什么变化,变化的只是发型,而理发店就是我们所说的“过滤器”,修剪了发型。
接下来看这个例子:模拟了数据库的查询功能。分别实现了单项查询(姓名,性别,婚姻状况)和组合查询(与,或)。输入为数据,通过过滤器将结果进行输出。

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
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
//基础类,有3个属性
class Person {
private String name;
private String gender;
private String maritalStatus;
public Person(String name, String gender, String maritalStatus) {
super();
this.name = name;
this.gender = gender;
this.maritalStatus = maritalStatus;
}
public String getName() {
return name;
}
public String getGender() {
return gender;
}
public String getMaritalStatus() {
return maritalStatus;
}
}
//过滤的接口,也就是查询标准
interface Criteria {
public List<Person> meetCriteria(List<Person> persons);
}
//具体的查询类
class CriterialMale implements Criteria {
@Override
public List<Person> meetCriteria(List<Person> persons) {
List<Person> malePersons = new ArrayList<>();
for (Person person : persons)
if (person.getGender().equalsIgnoreCase("MALE"))
malePersons.add(person);
return malePersons;
}
}
class CriterialFemale implements Criteria {
@Override
public List<Person> meetCriteria(List<Person> persons) {
List<Person> femalePersons = new ArrayList<>();
for (Person person : persons)
if (person.getGender().equalsIgnoreCase("FEMALE"))
femalePersons.add(person);
return femalePersons;
}
}
class CriterialSingle implements Criteria {
@Override
public List<Person> meetCriteria(List<Person> persons) {
List<Person> singlePersons = new ArrayList<>();
for (Person person : persons)
if (person.getMaritalStatus().equalsIgnoreCase("SINGLE"))
singlePersons.add(person);
return singlePersons;
}
}
//高级查询类
class AndCriteria implements Criteria {
private Criteria criteria;
private Criteria otherCriteria;
public AndCriteria(Criteria criteria, Criteria otherCriteria) {
super();
this.criteria = criteria;
this.otherCriteria = otherCriteria;
}
@Override
public List<Person> meetCriteria(List<Person> persons) {
List<Person> firstPersons = criteria.meetCriteria(persons);
return otherCriteria.meetCriteria(firstPersons);
}
}
class OrCriteria implements Criteria {
private Criteria criteria;
private Criteria otherCriteria;
public OrCriteria(Criteria criteria, Criteria otherCriteria) {
super();
this.criteria = criteria;
this.otherCriteria = otherCriteria;
}
@Override
public List<Person> meetCriteria(List<Person> persons) {
List<Person> firstPersons = criteria.meetCriteria(persons);
List<Person> otherPersons = otherCriteria.meetCriteria(persons);
for (Person person : otherPersons)
if (!firstPersons.contains(person))
firstPersons.add(person);
return firstPersons;
}
}
//客户端
public class FilterPatternDemo {
public static void main(String[] args) {
List<Person> persons = new ArrayList<>();

persons.add(new Person("Wang", "Male", "Single"));
persons.add(new Person("Li", "Male", "Merried"));
persons.add(new Person("Hou", "Female", "Single"));
persons.add(new Person("Zhao", "Male", "Merried"));
persons.add(new Person("Han", "Female", "Merried"));

Criteria male = new CriterialMale();
Criteria female = new CriterialFemale();
Criteria single = new CriterialSingle();
Criteria singleMale = new AndCriteria(single, male);
Criteria singleOrFemale = new OrCriteria(single, female);

System.out.println("Males:");
printPersons(male.meetCriteria(persons));

System.out.println("Females:");
printPersons(female.meetCriteria(persons));

System.out.println("Singles:");
printPersons(single.meetCriteria(persons));

System.out.println("Single Males:");
printPersons(singleMale.meetCriteria(persons));

System.out.println("single or female:");
printPersons(singleOrFemale.meetCriteria(persons));
}

public static void printPersons(List<Person> persons) {
for (Person person : persons)
System.out.printf("Person:%s, Gender:%s, Merital Status:%s\n",
person.getName(), person.getGender(), person.getMaritalStatus());
}
}

组合模式

组合模式,又叫部分整体模式,把一组想死的对象当做一个单一的对象。组合模式根据树形结构来进行组合,用来表示每一部分的层次信息。这种模式创建了一个包含自己对象组的类,提供了修改对象组的方式。
组合模式最适用于 分级结构 。关于分级数据结构的一个普遍性的例子是我们每次使用电脑时所遇到的:文件系统。文件系统由目录和文件组成。每个目录都可以装内容。目录的内容可以是文件,也可以是目录。按照这种方式,计算机的文件系统就是以递归结构来组织的。如果想要描述这样的数据结构,那么可以使用组合模式Composite。
组合模式主要有以下成员:

  1. Component:是组合中的对象声明接口,在适当的情况下,实现所有类共有接口的默认行为。声明一个接口用于访问和管理Component
    子部件。
  2. Composite:定义有枝节点行为,用来存储子部件,在Component接口中实现与子部件有关操作,如增加(add)和删除(remove)等。
  3. Leaf:在组合中表示叶子结点对象,叶子结点没有子结点。

一句话总结,组合模式就是树形模式,一层套用一层。
下面来看看这个例子:

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
//组件类,内部包含了增删改查的方法
class Employee {
private String name;
private String dept;
private int salary;
private List<Employee> subordinates;
public Employee(String name, String dept, int salary) {
super();
this.name = name;
this.dept = dept;
this.salary = salary;
subordinates = new ArrayList<Employee>();
}
public void add(Employee e) {
subordinates.add(e);
}
public void remove(Employee e) {
subordinates.remove(e);
}
public List<Employee> getSubordinates() {
return subordinates;
}
@Override
public String toString() {
return "Employee[Name:" + name + ", dept:" + dept + ", salary:" + salary + "]";
}
}
//客户端,可以发现是一个很简单的互相嵌套
public class CompositePattern {
public static void main(String[] args) {
Employee CEO = new Employee("John", "CEO", 30000);
Employee headSales = new Employee("Rob", "Head Sale", 20000);
Employee headMarketing = new Employee("Mic", "Head Marketing", 20000);
Employee clerk1 = new Employee("Luna", "Marketing", 10000);
Employee clerk2 = new Employee("Bob", "Marketing", 10000);
Employee sale1 = new Employee("Rich", "Sales", 10000);
//生成树形结构
CEO.add(headMarketing);
CEO.add(headSales);
headMarketing.add(clerk2);
headMarketing.add(clerk1);
headSales.add(sale1);

System.out.println(CEO);
for (Employee headEmployee : CEO.getSubordinates()) {
System.out.println(headEmployee);
for (Employee employee : headEmployee.getSubordinates())
System.out.println(employee);
}
}
}

装饰器模式

装饰器模式允许向一个现有对象添加新功能,同时又不改变其结构。这种模式创建了一个装饰类,用来包装原有的类,并在保持原方法的基础上,提供了额外的功能。我们平时使用的IO包就是典型的装饰着模式,一层套用一层,从而实现不同的功能。
当我们想要在不增加很多子类的情况下扩展类,那么酒可以将具体的功能职责划分,同时继承,来使用装饰器模式。
装饰器模式是基于对象组合的方式,最重要的应用就是 为对象增加功能 ,其本质是动态组合,在需要的地方进行拼接。

  1. Component:组件对象的接口,可以给这些对象动态的添加职责;
  2. ConcreteComponent:具体的组件对象,实现了组件接口。该对象通常就是被装饰器装饰的原始对象,可以给这个对象添加职责;
  3. Decorator:所有装饰器的父类,需要定义一个与组件接口一致的接口(主要是为了实现装饰器功能的复用,即具体的装饰器A可以装饰另外一个具体的装饰器B,因为装饰器类也是一个Component),并持有一个Component对象,该对象其实就是被装饰的对象。如果不继承组件接口类,则只能为某个组件添加单一的功能,即装饰器对象不能在装饰其他的装饰器对象。
  4. ConcreteDecorator:具体的装饰器类,实现具体要向被装饰对象添加的功能。用来装饰具体的组件对象或者另外一个具体的装饰器对象。

我们采用这样一个例子:Shape接口和Shape类是基础。然后我们创建一个实现了Shape接口的抽象装饰类ShapeDecorator,并把Shape作为其实例变量。

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
interface DShape {
void draw();
}
class DRectangle implements DShape {
@Override
public void draw() {
System.out.println("Shape:rectangle.");
}
}
class DCircle implements DShape {
@Override
public void draw() {
System.out.println("Shape:circle.");
}
}

abstract class ShapeDecorator implements DShape {
protected DShape decoratedShape;
public ShapeDecorator(DShape decoratedShape) {
this.decoratedShape = decoratedShape;
}
public void draw() {
decoratedShape.draw();
}
}

//expand
class RedShapeDecorator extends ShapeDecorator {
public RedShapeDecorator(DShape decoratedShape) {
super(decoratedShape);
}
@Override
public void draw() {
super.draw();
//add new method
setRedBorder(decoratedShape);
}
private void setRedBorder (DShape decoratedShape) {
System.out.println("Border Color:Red");
}
}

public class DecoratorPatternDemo {
public static void main(String[] args) {
DShape circle = new DCircle();
DShape redCircle = new RedShapeDecorator(new DCircle());
DShape redRectangle = new RedShapeDecorator(new DRectangle());
circle.draw();
redCircle.draw();
redRectangle.draw();
}
}

外观模式

外观模式隐藏系统的复杂性,向客户端提供了可以访问系统的接口。这种模式设计到单一的类,提供了客户端请求的简化方法和对现有系统方法的委托调用。简而言之,外观模式使应用程序只能看到外观对象,而看不到具体对象。
外观模式适用于降低复杂系统的内部子系统的复杂度,简化客户端与之的接口。
外观模式可以减少系统的互相依赖,提高灵活性和安全性。但不符合“开发封闭”原则,修改起来很麻烦,继承重写都不合适。
这个例子是外观模式的一个很好的体现,客户端只能看到ShapeMaker类,并在这个类中调用各种方法,而这些方法都是分别在各自的对象中实现的。对于客户端来说,只知道调用方法即可,而无须了解方法实现的具体步骤和实现过程,也无须自己创建中间对象,这一切都由外观对象自己实现。

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
interface FShape {
void draw();
}
class FRectangle implements FShape {
public void draw() {
System.out.println("Rectangle!");
}
}
class FSqure implements FShape {
public void draw() {
System.out.println("Squre!");
}
}

class FCircle implements FShape {
public void draw() {
System.out.println("Circle!");
}
}
//facade
class ShapeMaker{
private FShape circle;
private FShape rectangle;
private FShape squre;
public ShapeMaker() {
circle = new FCircle();
rectangle = new FRectangle();
squre = new FSqure();
}
public void drawCircle() {
circle.draw();
}
public void drawRectangle() {
rectangle.draw();
}
public void drawSqure() {
squre.draw();
}
}

//facade draw
public class FacadePattern {
public static void main(String[] args) {
ShapeMaker shapeMaker = new ShapeMaker();
shapeMaker.drawCircle();
shapeMaker.drawSqure();
shapeMaker.drawRectangle();
}
}

享元模式

享元模式主要用于减少创建对象的数量,以减少内存占用和提高性能。享元模式尝试重用现有的同类对象,如果没有此对象,则创建一个新的实例。在java中,String就是一个典型的享元模式。所有的String对象都在常量池当中,加以公用。此外,Integer.valueOf()在128内的对象也放在常量池中。
在这种情况下使用享元模式是一个很好的选择:系统中有大量很占内存的对象,这些对象是中立的,没有依赖的状态,且对象状态可以外部化。
注意 :享元模式不能滥用。在并发条件下使用享元模式,那么必须考虑并发性问题。如果为了减少内存的开销而要牺牲并发性,那么是得不偿失的。String是一个经典的享元模式例子:String是final的,也就是不可变的,这种对象被new出来后就不会变化。如果我们创建享元模式的对象是可变的,那么复用的时候务必要小心对象当前的状态。
一般使用Map来设计享元模式。在这个例子中,ShapeFactory无论收到什么请求,都会画圆。不过,每次都会检查是否已经存在同一颜色对象,如果没有那么新建,反之则复用。

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
class FFCircle implements FShape {
private String color;
private int x;
private int y;
private int radius;
public FFCircle(String color) {
this.color = color;
}
public void setX(int x) {
this.x = x;
}
public void setY(int y) {
this.y = y;
}
public void setRadius(int radius) {
this.radius = radius;
}
@Override
public void draw() {
System.out.printf("Circle[color:%s, x:%d, y:%d, radius:%d]\n", color, x, y, radius);
}
}

class ShapeFactory {
private static final HashMap<String, FShape>circleMap = new HashMap<>();
public static FShape getCircle(String color) {
FFCircle circle = (FFCircle) circleMap.get(color);
if (circle == null) {
circle = new FFCircle(color);
circleMap.put(color, circle);
System.out.println("Create circle of color:" + color);
}
return circle;
}
}

public class FlyweightPatternDemo {
private static final String[] colors = {"Red", "Green", "Blue", "White"};
public static void main(String[] args) {
for (int i = 0; i < 20; i++) {
FFCircle circle = (FFCircle) ShapeFactory.getCircle(getRandomColor());
circle.setX(getRandomNum());
circle.setY(getRandomNum());
circle.setRadius(getRandomNum());
circle.draw();
}
}
public static String getRandomColor() {
return colors[(int) (Math.random() * 3)];
}
public static int getRandomNum() {
return (int) (Math.random() * 100);
}
}

代理模式

代理模式中,一个类代表另一个类的功能,为其他对象提供了一种代理以控制这个对象的访问。
在直接访问的情况下有时会出现问题,比如要访问的对象在远程的机器上。在面向对象系统中,有些对象由于某些原因(比如对象创建开销很大,或者某些操作需要安全控制,或者需要进程外的访问),直接访问会给使用者或系统结构带来很多麻烦。此时我们可以在访问此对象时加上一个对此对象的访问层。
与适配器模式区别:适配器模式主要改变所改变对象的接口,代理模式并不改变接口;与装饰器模式的区别:装饰器模式为了增强功能,而代理模式主要是增加控制。
下面这个例子使用了代理模式,通过一个对象来调用另一个,同时增加了对null检查的功能。

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
interface Image {
void display();
}
class RealImage implements Image {
private String fileName;
public RealImage(String fileName) {
this.fileName = fileName;
loadFromDisk(fileName);
}
@Override
public void display() {
System.out.println("Display " + fileName);
}
private void loadFromDisk(String fileName) {
System.out.println("Loading " + fileName);
}
}
class ProxyImage implements Image {
private RealImage realImage;
private String fileName;
public ProxyImage(String fileName) {
this.fileName = fileName;
}
@Override
public void display() {
if (realImage == null)
realImage = new RealImage(fileName);
realImage.display();
}
}
public class ProxyPattern {
public static void main(String[] args) {
Image image = new ProxyImage("a.jpg");
//第一次调用,发现是空,创建对象
image.display();
//第二次调用,存在对象,直接使用
image.display();
}
}