设计模式扫盲系列(三):结构型模式集

【引言】此篇主要讨论7种常见的结构型模式:适配器模式(Adapter Pattern)、桥接模式(Bridge Pattern)、组合模式(Composite Pattern)、装饰器模式(Decorator Pattern)、外观模式(Facade Pattern)、享元模式(Flyweight Pattern)、代理模式(Proxy Pattern)。点击查看完整示例代码


适配器模式(Adapter Pattern)


定义

  适配器模式(Adapter Pattern)是作为两个不兼容的接口之间的桥梁。这种类型的设计模式属于结构型模式,它结合了两个独立接口的功能。这种模式涉及到一个单一的类,该类负责加入独立的或不兼容的接口功能。
  通常在需要开发的具有某种业务功能的组件在现有的组件库中已经存在了,但是与当前系统的接口规范又不大兼容,重新开发成本又很高,这时就应该想到适配器模式,它就能能很好地解决这类问题。

角色

  适配器模式(Adapter)包含以下主要角色。

  • 目标(Target)接口:当前系统业务所期待的接口,它可以是抽象类或接口。
  • 适配者(Adaptee)类:它是被访问和适配的现存组件库中的组件接口。
  • 适配器(Adapter)类:它是一个转换器,通过继承或引用适配者的对象,把适配者接口转换成目标接口,让客户按目标接口的格式访问适配者。

类图

  通过类图,我们可以发现原理性的适配器模式,就是通过一层适配(实际使用时可能扩展为多层)将原本不适配的组件转换为可适配的组件,屏蔽了原组件的特性,对外只会看到Adaptor的存在,而并不会被调用方发现Adaptor内部实际上使用的还是Adaptee来进行适配实现的。

实践

适配器接口

  由于只是对模式进行原理性的模拟,接口定义很简单,这个适配器接口相当于就是我们需要对外提供的新的契约接口,外部调用只会认可Adaptor的operation接口,而不会认可其他接口,这也就导致了我们必须做适配。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.linc.dp.Adaptor;

/**
* 适配器接口
*
* @author Lin.C
* @date 2019/6/3 7:49
*/
public interface Adaptor {

/**
* 对外提供的服务(待适配的操作)
*/
void operation();
}

被适配的实体定义

  被适配的实体,也就是实际干活的,它一般是已存在的组件,因为接口规范或者各种其他原因不能很好地与外部对接兼容,所以它需要做一个默默无闻的后台工作者存在。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package com.linc.dp.Adaptor;

/**
* 被适配的实体定义
*
* @author Lin.C
* @date 2019/6/3 7:50
*/
public class Adaptee {

/**
* 既有组件的传统操作
*/
public void adaptedOperation() {
System.out.println("I am operation from adaptee.");
}
}

适配实现

  对外,我是一个Adaptor,调用者需要进行operation都是通过我,但是剖开我的外衣,其实内部支撑的还是原来那个Adaptee,只不过它不愿抛头露面而已。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package com.linc.dp.Adaptor;

/**
* 适配实现
*
* @author Lin.C
* @date 2019/6/3 7:50
*/
public class ConcreteAdaptor implements Adaptor {

private Adaptee adaptee = new Adaptee();

@Override
public void operation() {
// 直接通过被适配对象进行操作,但对外接口的兼容性已改变
adaptee.adaptedOperation();
}
}

验证

  作为客户端,它只认可Adaptor,只要你Adaptor完成了我需要的功能,我管你实际是张三还是李四在实际干活呢?我从抽象层面依赖你Adaptor,你去屏蔽你的细节,我只需要你的接口功能完备即可。

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
package com.linc.client;

import com.linc.dp.Adaptor.Adaptor;
import com.linc.dp.Adaptor.ConcreteAdaptor;

/**
* 适配器模式客户端
*
* @author Lin.C
* @date 2019/6/3 7:51
*/
public class AdaptorClient {

/**
* Main
*
* @param args
*/
public static void main(String[] args) {
Adaptor adaptor = new ConcreteAdaptor();
adaptor.operation();
}
}

// Console Output
I am operation from adaptee.

尾声

  适配器有很多不同的实现方式,常见的就有:类适配器模式(class adapter pattern,以继承现有类的方式转换)、对象的适配器模式(object adapter pattern,以聚合对象实例的方式转换)、接口的适配器模式(default adapter pattern,以实现接口的方式转换);三种模式都可以实现适配器这种效果,殊途同归,今天这个实践因为属于扫盲性质的,所以暂时不会细致的研究每种实现的区别,具体对这三类适配器的细节讨论会在后续的提升专题进行。


桥接模式(Bridge Pattern)


定义

  桥接(Bridge)是用于把抽象化与实现化解耦,使得二者可以独立变化,它通过提供抽象化和实现化之间的桥接结构,来实现二者的解耦。这种模式涉及到一个作为桥接的接口,使得实体类的功能独立于接口实现类。这种模式的意图就是将抽象部分与实现部分分离,使它们都可以独立的变化。

角色

  桥接(Bridge)模式包含以下主要角色。

  • 抽象化(Abstraction)角色:定义抽象类,并包含一个对实现化对象的引用。
  • 扩展抽象化(Refined Abstraction)角色:是抽象化角色的子类,实现父类中的业务方法,并通过组合关系调用实现化角色中的业务方法。
  • 实现化(Implementor)角色:定义实现化角色的接口,供扩展抽象化角色调用。
  • 具体实现化(Concrete Implementor)角色:给出实现化角色接口的具体实现。

类图

  这个类图涉及到了桥接模式关键的几个点(但是没有将客户端包含进来),Abstraction对应的就是桥接模式的桥(一般情况下,它内部是包含了一个Implementor的成员变量的),通过这座桥,从而将实际的操作交由具体的Implementor来操作,这个就是桥接模式最基本的用途。

实践

操作服务接口

  此接口,用于定义操作的契约(还是那句老话,面向抽象编程),所以我们需要这个接口来定义它对外暴露的服务。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.linc.dp.Bridge;

/**
* 具体操作服务接口
*
* @author Lin.C
* @date 2019/6/11 7:41
*/
public interface Implementor {

/**
* Operations
*/
void operationImpl();
}

操作服务实现

  这个实现类可以有N个,因为只是个演示demo这里就不一一列举了,每个实现类在实现接口方法时都有自己具体的逻辑,这个实际上也是使用接口的好处之一(一个定义,多重实现)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.linc.dp.Bridge;

/**
* 实际操作实现类A
*
* @author Lin.C
* @date 2019/6/11 7:41
*/
public class ConcreteImplementorA implements Implementor {

@Override
public void operationImpl() {
System.out.println("I am operation implementor A, i am working.");
}
}

桥的定义

  桥的定义,实际上写到这里的时候,就有点懵圈了,桥看起来和其他一些模式都有些神似,但是又有点细微的差别,具体这些模式之间的区别,我们会在后面的提升系列里单独讨论,这里不做扩展。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package com.linc.dp.Bridge;

/**
* 桥
*
* @author Lin.C
* @date 2019/6/11 7:41
*/
public class Abstraction {

private Implementor implementor;

public Abstraction(Implementor implementor) {
this.implementor = implementor;
}

public void operation() {
implementor.operationImpl();
}
}

客户类

  通过客户类调用,可以很直观的看出来,客户只需要跟Abstraction来打交道即可,需要用到A就让它启动A,需要用到B就让它启动B,通过Abstraction实现了桥接。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.linc.client;

import com.linc.dp.Bridge.Abstraction;
import com.linc.dp.Bridge.ConcreteImplementorA;
import com.linc.dp.Bridge.ConcreteImplementorB;

/**
* 桥接模式的客户调用
*
* @author Lin.C
* @date 2019/6/11 7:45
*/
public class BridgeClient {

public static void main(String[] args) {
Abstraction abstractionA = new Abstraction(new ConcreteImplementorA());
abstractionA.operation();

Abstraction abstractionB = new Abstraction(new ConcreteImplementorB());
abstractionB.operation();
}
}


组合模式(Composite Pattern)


定义

  组合模式(Composite Pattern),又叫部分整体模式,是用于把一组相似的对象当作一个单一的对象。组合模式依据树形结构来组合对象,用来表示部分以及整体层次。组合模式主要有以下三个组件:

  • 组合部件(Component):它是一个抽象角色,为要组合的对象提供统一的接口。
  • 叶子(Leaf):在组合中表示子节点对象,叶子节点不能有子节点。
  • 合成部件(Composite):定义有枝节点的行为,用来存储部件,实现在Component接口中的有关操作,如增加(Add)和删除(Remove)。

角色

  组合模式包含以下主要角色。

  • 抽象构件(Component)角色:它的主要作用是为树叶构件和树枝构件声明公共接口,并实现它们的默认行为。在透明式的组合模式中抽象构件还声明访问和管理子类的接口;在安全式的组合模式中不声明访问和管理子类的接口,管理工作由树枝构件完成。
  • 树叶构件(Leaf)角色:是组合中的叶节点对象,它没有子节点,用于实现抽象构件角色中 声明的公共接口。
  • 树枝构件(Composite)角色:是组合中的分支节点对象,它有子节点。它实现了抽象构件角色中声明的接口,它的主要作用是存储和管理子部件,通常包含 Add()、Remove()、GetChild() 等方法。

类图

  通过上一节的定义和下图对应就能很清楚的看出组合模式的各个组件,但是单独来看类图还是着实有些抽象,打个实际的比方比较切合这个模式的就是我们操作系统的资源管理器,每个文件夹下面都会包含子文件夹或者文件,组合模式实现的就是类似的效果。

实践

Component

  这里省略了Component的抽象层(实际使用时也是需要视情况而定具体采用何种方式来实现的),所以这里的逻辑就比较简单了(甚至更简单一些连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
package com.linc.dp.Composite;

import java.util.ArrayList;
import java.util.List;

/**
* 合成部件
*
* @author Lin.C
* @date 2019/6/11 8:04
*/
public class Component {

private String name;

private List<Component> list;

public Component(String name) {
this.name = name;
list = new ArrayList<>();
}

public void operation() {
}

public void add(Component e) {
list.add(e);
}

public void remove(Component e) {
list.remove(e);
}
}

Leaf

  叶子结点实际上也是一种Component,只是它对operation进行了重新的定义而已;个人认为实际开发中若没有这类的操作区别,也没有必要为了抽象而抽象的非要挤出一个Leaf类来。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package com.linc.dp.Composite;

/**
* 叶子结点
*
* @author Lin.C
* @date 2019/6/11 8:03
*/
public class Leaf extends Component {

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

@Override
public void operation() {
System.out.println("I am a leaf ------> I am a pretty leaf.");
}
}

客户类

  鉴于这个结构打印起来比较麻烦,所以没有花太多时间来重写Component的toString方法,而是直接偷懒了一下,把Debug出来的Component的最终结构直接copy过来的,看起来也清晰透彻的发现这个树真的是一棵树。

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
package com.linc.client;

import com.linc.dp.Composite.Component;
import com.linc.dp.Composite.Leaf;

/**
* 组合模式客户类
*
* @author Lin.C
* @date 2019/6/11 8:12
*/
public class CompositeClient {

public static void main(String[] args) {

Component leaf1A = new Leaf("Level 1-A");
Component leaf1B = new Leaf("Level 1-B");

Component componentB = new Component("Level 2");
componentB.add(leaf1A);
componentB.add(leaf1B);

Component componentA = new Component("Level 3");
componentA.add(componentB);

System.out.println(componentA);
}
}

// Output by debug
componentA = {Component@626}
name = "Level 3"
list = {ArrayList@628} size = 1
0 = {Component@625}
name = "Level 2"
list = {ArrayList@631} size = 2
0 = {Leaf@623}
name = "Level 1-A"
list = {ArrayList@634} size = 0
1 = {Leaf@624}
name = "Level 1-B"
list = {ArrayList@636} size = 0


装饰器模式(Decorator Pattern)


定义

  装饰器模式(Decorator Pattern)允许向一个现有的对象添加新的功能,同时又不改变其结构。就好比我们买煎饼果子一样,有个最基础的煎饼果子实体,在这个实体上你可以添加各种装饰(比如:加煎蛋,加烤肠,加熏肉等等)。

角色

  装饰模式主要包含以下角色。

  • 抽象构件(Component)角色:定义一个抽象接口以规范准备接收附加责任的对象。
  • 具体构件(Concrete Component)角色:实现抽象构件,通过装饰角色为其添加一些职责。
  • 抽象装饰(Decorator)角色:继承抽象构件,并包含具体构件的实例,可以通过其子类扩展具体构件的功能。
  • 具体装饰(ConcreteDecorator)角色:实现抽象装饰的相关方法,并给具体构件对象添加附加的责任。

类图

  如果单纯看类图,有时候真的不那么容易理解,这里涉及到的Decorator实际上就是装饰器了,它内部包含了Component的实例,在具体使用时,完全可以在Operation之前再添加一些额外的装饰功能,从而实现装饰器。

实践

Component

  这个就是我们的实体接口Component,它内部定义了该接口服务对外需要提供的接口方法。

1
2
3
4
5
6
7
8
9
10
11
12
package com.linc.dp.Decorator;

/**
* Component
*
* @author Lin.C
* @date 2019/6/11 8:56
*/
public interface Component {

void operation();
}

ConcreteComponent

  实现一下前面的实体接口,完成基本服务的定义。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.linc.dp.Decorator;

/**
* Component实现类
*
* @author Lin.C
* @date 2019/6/11 8:58
*/
public class ConcreteComponent implements Component {

@Override
public void operation() {
System.out.println("I am the ConcreteComponent, i am doing my work.");
}
}

Decorator

  Decorator里面是包含了Component的,但是这里实际上有两个容易产生疑问的地方:

既然Decorator内部已经有了Component实例对象了,为何还要实现Component接口呢?
个人理解:从外部调用来考虑,对外我们开放的只是Component接口而不是Decorator,所以这里需要将Decorator作为一个Component提供

这里的operation操作实际上已经可以实现装饰效果了吧?那为何还要衍生一个子类来装饰呢?
个人理解:为了实现装饰器本身的可扩展性,比如我要实现多种装饰效果,就不必写多个Decorator了,而是基于一个Decorator做多种实现即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.linc.dp.Decorator;

/**
* Decorator
*
* @author Lin.C
* @date 2019/6/11 8:56
*/
public class Decorator implements Component {

public Component component;

public Decorator(Component component) {
this.component = component;
}

@Override
public void operation() {
// 实际上在这里已经可以做装饰了,但为何还要衍生一个子类ConcreteDecorator?还不是太明白
component.operation();
}
}

ConcreteDecorator

  基于上面对Decorator的疑问分析,基本上对这个实现类我们也不会有太多疑问了;当这里有多个Decorator的实现时可能更好理解一些。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package com.linc.dp.Decorator;

/**
* Decorator添加了装饰的实现类
*
* @author Lin.C
* @date 2019/6/11 8:57
*/
public class ConcreteDecorator extends Decorator {

public ConcreteDecorator(Component component) {
super(component);
}

@Override
public void operation() {
// 这里可以做很多事情了,额外调用一些方法什么的都ok
System.out.println("Wow, I am the decorator itself, i can add sth as i want.");
super.operation();
}
}

客户类

  通过客户类我们可以发现,对外我们只暴露了Component这个接口,但是实际使用的却是加了装饰的具体实现,在不改变Component既有的方法签名完整性的基础上实现了功能的扩展。

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
package com.linc.client;

import com.linc.dp.Decorator.ConcreteComponent;
import com.linc.dp.Decorator.ConcreteDecorator;
import com.linc.dp.Decorator.Decorator;

/**
* 装饰器模式客户类
*
* @author Lin.C
* @date 2019/6/11 9:02
*/
public class DecoratorClient {

public static void main(String[] args) {
Component component = new ConcreteDecorator(new ConcreteComponent());
component.operation();
}
}

// Output
Wow, I am the decorator itself, i can add sth as i want.
I am the ConcreteComponent, i am doing my work.

Process finished with exit code 0


外观模式(Facade Pattern)


定义

  外观模式(Facade Pattern)隐藏了系统的复杂性,并向客户端提供了一个客户端可以访问系统的接口。这么描述,看起来还是云里雾里的。
  稍微通俗一些的定义:外观模式定义了一个高层的功能,为子系统中的多个模块协同的完成某种功能需求提供简单的对外功能调用方式,使得这一子系统更加容易被外部使用。

角色

  外观(Facade)模式包含以下主要角色。

  • 外观(Facade)角色:为多个子系统对外提供一个共同的接口。
  • 子系统(Sub System)角色:实现系统的部分功能,客户可以通过外观角色访问它。
  • 客户(Client)角色:通过一个外观角色访问各个子系统的功能。

类图

  这个类图真的是简单到写不了什么解释性的话语了,对外就是一个Facade,内部封装了所有复杂的操作。

实践

  鉴于此处Demo的方法都比较抽象而且也很简单,所以很多方法注释都省略掉了。

Component

  外观模式内部封装的组件接口定义,实际上也是一套普通的操作接口,它也可以独立对外的提供服务。

1
2
3
4
5
6
7
8
9
10
11
12
package com.linc.dp.Facade;

/**
* 被封装的组件接口
*
* @author Lin.C
* @date 2019/6/11 9:32
*/
public interface Component {

void operation();
}

ConcreteComponentX

  服务实现类,可以有多个,也只有在有多个的情况下,才有外观模式的用武之地,外观模式简单理解也就是为调用方屏蔽掉了多实现之间的调用逻辑。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.linc.dp.Facade;

/**
* Component实现类
*
* @author Lin.C
* @date 2019/6/11 9:33
*/
public class ConcreteComponentA implements Component {

@Override
public void operation() {
System.out.println("This is component A operating.");
}
}

Facade

  外观实际上有些地方也被称为门面模式,它就像是对外提供的一个大门一样,通过它你可以知道你能使用哪些功能,但具体它是怎么在内部运转的,都不重要。

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
package com.linc.dp.Facade;

/**
* 对外提供的服务(实际上也可以抽象一层接口,此处偷个懒)
*
* @author Lin.C
* @date 2019/6/11 9:31
*/
public class Facade {

private Component componentA;

private Component componentB;

public Facade() {
componentA = new ConcreteComponentA();
componentB = new ConcreteComponentB();
}

public void operationA() {
componentA.operation();
}

public void operationB() {
componentB.operation();
}
}

客户类

  客户调用就显得相对简单了许多了,它不用关注具体谁来干这个活儿,只需要告诉门面我要完成某项功能,门面就在内部替它把这些细节给组织好完成就可以了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.linc.client;

import com.linc.dp.Facade.Facade;

/**
* 客户类
*
* @author Lin.C
* @date 2019/6/11 9:36
*/
public class FacadeClient {

public static void main(String[] args) {
Facade facade = new Facade();
facade.operationA();
facade.operationB();
}
}

// Output
This is component A operating.
This is component B operating.


享元模式(Flyweight Pattern)


定义

  说实话,23中设计模式中,个人觉得名字最怪异的就是享元模式,也不知最初的译者有没有考虑到我等知识有限,观其名却不知其意。
  享元模式(Flyweight Pattern)主要是用于减少创建对象的数量的,以减少内存占用和提高性能。那具体怎么减少的呢?实际上是使用共享技术实现相同或者相似对象的重用,也就是说实现了相同或者相似对象的代码共享。
  说到享元模式,第一个想到的应该就是池技术了,String常量池、数据库连接池、缓冲池等等都是享元模式的应用,所以说享元模式是池技术的重要实现方式。

角色

  享元模式的主要角色有如下。

  • 抽象享元角色(Flyweight):是所有的具体享元类的基类,为具体享元规范需要实现的公共接口,非享元的外部状态以参数的形式通过方法传入。
  • 具体享元(Concrete Flyweight)角色:实现抽象享元角色中所规定的接口。
  • 非享元(Unsharable Flyweight)角色:是不可以共享的外部状态,它以参数的形式注入具体享元的相关方法中。
  • 享元工厂(Flyweight Factory)角色:负责创建和管理享元角色。当客户对象请求一个享元对象时,享元工厂检査系统中是否存在符合要求的享元对象,如果存在则提供给客户;如果不存在的话,则创建一个新的享元对象。

类图

  Flyweight是抽象享元角色,它是产品的一个抽象类,同时定义出对象的外部状态和内部状态的接口或实现;ConcreteFlyweight是具体享元角色,是具体的产品类,实现抽象角色定义的业务;UnsharedConcreteFlyweight是不可共享的享元角色,一般不会出现在享元工厂中;FlyweightFactory是享元工厂,它用于构造一个池容器,同时提供从池中获得对象的方法。

实践

抽象享元

  定义一个带内部和外部状态的享元抽象角色,内部状态就是可共享的状态,外部状态则是个性化的状态,组合起来就是一个完整的享元角色了。

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
package com.linc.dp.Flyweight;

import lombok.Getter;
import lombok.Setter;

/**
* 抽象享元角色
*
* @author Lin.C
* @date 2019/6/13 7:14
*/
public abstract class Flyweight {

@Setter
@Getter
// 内部状态
public String intrinsic;

// 外部状态
protected final String extrinsic;

/**
* 通过构造方法强制享元角色必须接受一个外部状态参数
*/
public Flyweight(String extrinsic) {
this.extrinsic = extrinsic;
}

public abstract void operate(int extrinsic);
}

共享享元

  在共享相似对象的情况下,使用此享元结合类单例模式的工厂,即可实现享元模式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package com.linc.dp.Flyweight;

/**
* 实际享元角色
*
* @author Lin.C
* @date 2019/6/13 7:12
*/
public class ConcreteFlyweight extends Flyweight {

public ConcreteFlyweight(String extrinsic) {
super(extrinsic);
}

@Override
public void operate(int extrinsic) {
System.out.println("I am ConcreteFlyweight, extrinsic :" + extrinsic);
}
}

不共享享元

  并不是所有的享元类都需要被共享,有些时候需要有一些个性化的东西,这是的享元类就不能被共享,可以通过本享元类来实例一个非共享享元对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package com.linc.dp.Flyweight;

/**
* 实际享元角色(不需要共享)
*
* @author Lin.C
* @date 2019/6/13 7:12
*/
public class UnsharedConcreteFlyweight extends Flyweight {

public UnsharedConcreteFlyweight(String extrinsic) {
super(extrinsic);
}

@Override
public void operate(int extrinsic) {
System.out.println("I am UnsharedConcreteFlyweight, extrinsic :" + extrinsic);
}
}

享元工厂

  享元模式的核心在于享元工厂类,享元工厂类的作用在于提供一个用于存储享元对象的享元池,用户需要对象时,首先从享元池中获取,如果享元池中不存在,则创建一个新的享元对象返回给用户,并在享元池中保存该新增对象。

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
package com.linc.dp.Flyweight;

import java.util.HashMap;

/**
* 享元工厂
*
* @author Lin.C
* @date 2019/6/13 7:15
*/
public class FlyweightFactory {

// 享元管理池
private static HashMap<String, Flyweight> pool = new HashMap<>();

/**
* 获取一个享元角色对象
*
* @param extrinsic
* @return
*/
public static Flyweight getFlyweight(String extrinsic) {
if (!pool.containsKey(extrinsic)) {
pool.put(extrinsic, new ConcreteFlyweight(extrinsic));
System.out.println("Create new Flyweight by " + extrinsic);
} else {
// 此处纯粹是为了演示需要的无效分支
System.out.println("Use existing Flyweight by " + extrinsic);
}
return pool.get(extrinsic);
}
}

客户类

  当我们需要用到享元时,通过工厂获取即可,当不需要使用共享这种方式时,手动创建一个非共享的享元也是可以的(遗留问题:那UnsharedConcreteFlyweight在这个模式中还有存在价值么?)。

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
package com.linc.client;

import com.linc.dp.Flyweight.Flyweight;
import com.linc.dp.Flyweight.FlyweightFactory;
import com.linc.dp.Flyweight.UnsharedConcreteFlyweight;

/**
* 享元模式客户类
*
* @author Lin.C
* @date 2019/6/13 7:19
*/
public class FlyweightClient {

public static void main(String[] args) {
int extrinsic = 10;

System.out.println("-------------------------------------------------------");
Flyweight flyweightX = FlyweightFactory.getFlyweight("X");
flyweightX.operate(++extrinsic);

System.out.println("-------------------------------------------------------");
Flyweight flyweightY = FlyweightFactory.getFlyweight("Y");
flyweightY.operate(++extrinsic);

System.out.println("-------------------------------------------------------");
Flyweight flyweightReX = FlyweightFactory.getFlyweight("X");
flyweightReX.operate(++extrinsic);

System.out.println("-------------------------------------------------------");
Flyweight flyweightUnX = new UnsharedConcreteFlyweight("X");
flyweightUnX.operate(++extrinsic);
}
}

// Output
-------------------------------------------------------
Create new Flyweight by X
I am ConcreteFlyweight, extrinsic :11
-------------------------------------------------------
Create new Flyweight by Y
I am ConcreteFlyweight, extrinsic :12
-------------------------------------------------------
Use existing Flyweight by X
I am ConcreteFlyweight, extrinsic :13
-------------------------------------------------------
I am UnsharedConcreteFlyweight, extrinsic :14

Process finished with exit code 0

扩展

  在实际使用过程中,有时候会稍加改变,即存在两种特殊的享元模式:单纯享元模式和复合享元模式;这个区分也解决了前一节最后的关于UnsharedConcreteFlyweight存在价值的疑问。简单理解呢,单纯享元模式就是不带UnsharedConcreteFlyweight的,所有的具体享元类都是可以共享的;复合享元模式中的有些享元对象是由一些单纯享元对象组合而成的。以下是两种模式的类图:


代理模式(Proxy Pattern)


定义

  相信大多数人都有点印象的,Java面试,基本上出现频率最高的两种设计模式之一就是代理模式了(另一种自然是单例),那为什么大家这么喜欢它呢?我们就来好好研究一下。
  通常,由于某些原因需要给某对象提供一个代理以控制对该对象的访问;这时,访问对象不适合或者不能直接引用目标对象,代理对象作为访问对象和目标对象之间的中介。跟现实生活类似,比如房产中介就是房主的代理,律师就是委托人的代理,都是一个性质。

角色

  代理模式的主要角色如下。

  • 抽象主题(Subject)类:通过接口或抽象类声明真实主题和代理对象实现的业务方法。
  • 真实主题(Real Subject)类:实现了抽象主题中的具体业务,是代理对象所代表的真实对象,是最终要引用的对象。
  • 代理(Proxy)类:提供了与真实主题相同的接口,其内部含有对真实主题的引用,它可以访问、控制或扩展真实主题的功能。

类图

  代理模式的类图也很简单,既然你是我的代理,那么很多事情通过你对外发布,但是实际操作还是我来,那么必然我们要有一套共同的约定,这个约定就是一个统一的接口Subject;既然是代理,那么就有代理角色(Proxy)和被代理角色(RealSubject),而代理角色也必须获得被代理角色的授权(包含一个对被代理角色的引用)。

实践

代理接口

  代理接口用于定义一个统一的对外开放的服务。

1
2
3
4
5
6
7
8
9
10
11
12
package com.linc.dp.Proxy;

/**
* 代理接口
*
* @author Lin.C
* @date 2019/6/13 8:02
*/
public interface Subject {

void request();
}

被代理者

  被代理者,实际干活的角色,拥有代理接口的服务功能,但自己不对外抛头露面。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.linc.dp.Proxy;

/**
* 被代理者
*
* @author Lin.C
* @date 2019/6/13 8:02
*/
public class RealSubject implements Subject {

@Override
public void request() {
System.out.println("I am RealSubject dealing request.");
}
}

代理

  代理,对外服务的具体角色,外部调用方只知道代理的存在,所有的事务都是通过代理来提交;代理本身内部又将该事务转交给被代理者来执行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package com.linc.dp.Proxy;

/**
* 代理
*
* @author Lin.C
* @date 2019/6/13 8:03
*/
public class Proxy implements Subject {

private RealSubject realSubject;

public Proxy(RealSubject realSubject) {
this.realSubject = realSubject;
}

@Override
public void request() {
System.out.println("I am Proxy starting");
realSubject.request();
System.out.println("I am Proxy ending");

}
}

客户类

  客户只看到Subject以及Proxy(其实也可以将RealSubject的初始化放在Proxy内部),所有的请求都是通过代理类来受理的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package com.linc.client;

import com.linc.dp.Proxy.Proxy;
import com.linc.dp.Proxy.RealSubject;
import com.linc.dp.Proxy.Subject;

/**
* 代理模式客户类
*
* @author Lin.C
* @date 2019/6/13 8:06
*/
public class ProxyClient {

public static void main(String[] args) {
Subject subject = new Proxy(new RealSubject());
subject.request();
}
}

扩展

  相信很多同学都知道这里讲的只是静态代理,在实际使用时我们更常用的却是动态代理,一般我们常见的有两种动态代理:JDKProxy和CGLib,当然也有一些其他第三方库只是很少用到,也很少被提及。这里因为只做扫盲我们就不做过多的扩展,留在提升章节详细讨论不同的实现方式有什么区别。

------2019 Lin.C ------