1. 适配器模式

将一个类的接口转化为客户希望的另外一个接口,使原本接口不兼容的类能够一起工作。
通俗的说就是适配器适用于有相关性但是不兼容的结构

举个例子:
家用电源与USB数据线有相关性,家用电源输入电压,USB输出电压,但是无法兼容,这时就需要一个适配器将电压转化为5v才能工作。

  1. 家用电源输出220V电压
1
2
3
4
5
6
public class HomeBattery {
int supply() {
// 家用电压为220V
return 220;
}
}
  1. USB数据线只能输入5V电压
    1
    2
    3
    4
    5
    6
    7
    public class USBLine {
    void charge(int volt) {
    if (volt != 5) throw new IllegalArgumentException("只能接收5V电压");
    // 如果是5V则能正常充电
    System.out.println("正常充电");
    }
    }
  2. 适配之前是无法使用的
1
2
3
4
5
6
7
8
9
10
11
public class User {
@Test
public void chargeForPhone() {
HomeBattery homeBattery = new HomeBattery();
int homeVolt = homeBattery.supply();
System.out.println("家庭电源提供的电压是 " + homeVolt + "V");

USBLine usbLine = new USBLine();
usbLine.charge(homeVolt);
}
}

输出:

1
2
3
家庭电源提供的电压是 220V

java.lang.IllegalArgumentException: 只能接收 5V 电压
  1. 定义适配器
1
2
3
4
5
public class Adapter {
int convert(int homeVolt) {
return homeVolt - 215;
}
}
  1. 使用适配器后能正常使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class User {
public static void main(String[] args) {
HomeBattery homeBattery = new HomeBattery();
int homeVolt = homeBattery.supply();
System.out.println("家庭电源提供的电压是" + homeVolt + "V");

// 使用适配器适配
Adapter adapter = new Adapter();
int chargeVolt = adapter.convert(homeVolt);
System.out.println("使用适配器将家庭电压转换成了 " + chargeVolt + "V");

USBLine usbLine = new USBLine();
usbLine.charge(chargeVolt);
}
}

输出:

1
2
3
家庭电源提供的电压是220V
使用适配器将家庭电压转换成了 5V
正常充电

2. 桥接模式

将抽象部分与它的实现部分分离,使它们都可以独立地变化。

通俗的说就是如果一个对象有多种分类方式,如形状和颜色,应将每种分类方式分离出来,使用时将它们进行组合,而不是使用继承,继承会使类越来越多。

下面使桥接模式的实现:

颜色接口,包含一个获取颜色的方法

1
2
3
public interface IColor {
String getColor();
}

每种颜色实现该接口:

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
public class Color {
public static class Red implements IColor {
@Override
public String getColor() {
return "红";
}
}


public static class Blue implements IColor {
@Override
public String getColor() {
return "蓝";
}
}


public static class Yellow implements IColor {
@Override
public String getColor() {
return "黄";
}
}

public static class Green implements IColor {
@Override
public String getColor() {
return "绿";
}
}
}

每个形状中桥接该接口

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 Rectangle implements IShape {
private IColor color; // 桥接

public void setColor(IColor color) {
this.color = color;
}

@Override
public void draw() {
System.out.println("绘制" + color.getColor() + "矩形");
}
}

public class Round implements IShape{
private IColor color;

public void setColor(IColor color) {
this.color = color;
}

@Override
public void draw() {
System.out.println("绘制" + color.getColor() + "圆形");
}
}

测试:

1
2
3
4
5
6
7
8
9
10
11
public class Test {
public static void main(String[] args) {
Rectangle rectangle = new Rectangle();
rectangle.setColor(new Color.Red());
rectangle.draw();

Round round = new Round();
round.setColor(new Color.Blue());
round.draw();
}
}

3. 组合模式

当整体与部分具有相似结构时使用组合模式
组合模式最主要的功能就是让用户可以一致对待整体和部分结构,将两者都作为一个相同的组件。

比如公司的人员分布结构:
在这里插入图片描述
该结构中主要分为管理者和和职员,管理者不只要管理职员,也需要管理管理者。
如果不使用组合模式,或造成大量的重复字段,而且不能统一对待管理者和职员

使用组合模式:

  1. 新建一个抽象组件类(提取公共字段)
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 abstract class Component {
// 职位
private String position;
// 工作内容
private String job;

public Component(String position, String job) {
this.position = position;
this.job = job;
}

// 做自己的本职工作
public void work() {
System.out.println("我是" + position + ",我正在" + job);
}

abstract void addComponent(Component component);

abstract void removeComponent(Component component);

abstract void check();

}

管理者继承此类

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
public class Manager extends Component{

// 管理的组件
private List<Component> components = new ArrayList<>();

public Manager(String position, String job) {
super(position, job);
}

@Override
void addComponent(Component component) {
components.add(component);
}

@Override
void removeComponent(Component component) {
components.remove(components.size() - 1);
}

@Override
void check() {
work();
for (Component component : components) {
component.check();
}
}
}

职员继承此类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class Employee extends Component {

public Employee(String position, String job) {
super(position, job);
}

@Override
void addComponent(Component component) {
System.out.println("职员没有管理权限");
}

@Override
void removeComponent(Component component) {
System.out.println("职员没有管理权限");
}

@Override
void check() {
work();
}
}

客户端测试

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
public class Test {
public static void main(String[] args) {
Component boss = new Manager("老板", "唱怒放的生命");
Component HR = new Employee("人力资源", "聊微信");
Component PM = new Manager("产品经理", "不知道干啥");
Component CFO = new Manager("财务主管", "看剧");
Component CTO = new Manager("技术主管", "划水");
Component UI = new Employee("设计师", "画画");
Component operator = new Employee("运营人员", "兼职客服");
Component webProgrammer = new Employee("程序员", "学习设计模式");
Component backgroundProgrammer = new Employee("后台程序员", "CRUD");
Component accountant = new Employee("会计", "背九九乘法表");
Component clerk = new Employee("文员", "给老板递麦克风");
boss.addComponent(HR);
boss.addComponent(PM);
boss.addComponent(CFO);
PM.addComponent(UI);
PM.addComponent(CTO);
PM.addComponent(operator);
CTO.addComponent(webProgrammer);
CTO.addComponent(backgroundProgrammer);
CFO.addComponent(accountant);
CFO.addComponent(clerk);

boss.check();
}
}

输出:

1
2
3
4
5
6
7
8
9
10
11
我是老板,我正在唱怒放的生命
我是人力资源,我正在聊微信
我是产品经理,我正在不知道干啥
我是设计师,我正在画画
我是技术主管,我正在划水
我是程序员,我正在学习设计模式
我是后台程序员,我正在CRUD
我是运营人员,我正在兼职客服
我是财务主管,我正在看剧
我是会计,我正在背九九乘法表
我是文员,我正在给老板递麦克风

使用组合模式后将公有字段移动到了抽象类中,降低了代码重复,在客户端统一对待管理者和职员为Component

透明方式

在 Component 中声明所有管理子对象的方法,包括 add 、remove 等,这样继承自 Component 的子类都具备了 add、remove 方法。对于外界来说叶节点和枝节点是透明的,它们具备完全一致的接口

优点: 让 Manager 类和 Employee 类具备完全一致的行为接口,调用者可以一致对待它们。
缺点: Employee 类并不支持管理子对象,不仅违背了接口隔离原则,而且客户端可以用 Employee 类调用 addComponent 和 removeComponent 方法,导致程序出错,所以这种方式是不安全的。

安全方式

将 addComponent 和 removeComponent 方法移到 Manager 子类中去单独实现
将接口中某个对象独有的方法单独放到子类实现

优点: 符合接口隔离原则
缺点: 由于接口不一致所以无法统一对待对象

组合模式透明方式和安全方式的区别

  • 透明方式:在Component中声明所有管理子对象的方法,所有实现该接口的子类都具备一样的接口
  • 安全方式: 在Component中不声明管理子对象的方法,叶子节点无需实现管理子对象的方法,只需在枝节节点中实现管理子对象的方法

4.装饰器模式

  • 增强一个类原有的功能
  • 为一个类添加新功能

增强功能的装饰模式

增强功能的装饰模式,不改变类的原有功能因此可以无限装饰,也叫透明装饰模式。

例子:
颜值接口

1
2
3
public interface IBeauty {
int getBeautyValue();
}

新建Me类实现颜值接口

1
2
3
4
5
6
public class Me implements IBeauty{
@Override
public int getBeautyValue() {
return 100;
}
}

新建戒指装饰类

1
2
3
4
5
6
7
8
9
10
public class RingDecorator implements IBeauty{
private final IBeauty me;
public RingDecorator(IBeauty me) {
this.me = me;
}
@Override
public int getBeautyValue() {
return me.getBeautyValue() + 20;
}
}

测试:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Client {
public static void main(String[] args) {
IBeauty me = new Me();
System.out.println("我本来的颜值:" + me.getBeautyValue());

IBeauty meWithRing = new RingDecorator(me);
System.out.println("戴上了戒指后,我的颜值:" + meWithRing.getBeautyValue());

// 透明装饰模式中可以多次装饰
RingDecorator meWithRingRing = new RingDecorator(meWithRing);
System.out.println("戴上两个戒指后,我的颜值:" + meWithRingRing.getBeautyValue());
}
}

输出:

1
2
3
我本来的颜值:100
戴上了戒指后,我的颜值:120
戴上两个戒指后,我的颜值:140

用于添加功能的装饰器模式

不能多次装饰,被装饰者对于客户端可以是实现任何接口的对象所以是透明的,装饰者添加了新的功能所以要区别对待,是不透明的,所以是半透明装饰模式

例子:

房屋接口:

1
2
3
public interface IHouse {
void live();
}

实现类:

1
2
3
4
5
6
public class House implements IHouse{
@Override
public void live() {
System.out.println("房屋的原有功能:居住功能");
}
}

新增粘钩装饰器,继承房屋接口:

1
2
3
public interface IStickyHookHouse extends IHouse{
void hangThings();
}

装饰器实现类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class StickyHookDecorator implements IStickyHookHouse{
private final IHouse house;

public StickyHookDecorator(IHouse house) {
this.house = house;
}

@Override
public void handThings() {
System.out.println("有了粘钩功能后,新增了挂东西的功能");
}

@Override
public void live() {
house.live();
}
}

测试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class Client {
public static void main(String[] args) {
House house = new House();
house.live();

IStickyHookHouse stickyHookHouse = new StickyHookDecorator(house);
stickyHookHouse.live();
stickyHookHouse.handThings();

IMirrorHouse mirrorDecorator = new MirrorDecorator(house);
mirrorDecorator.live();
mirrorDecorator.lookMirror();

// 仿照透明模式的写法同时添加粘钩和镜子
IMirrorHouse mirrorDecorator1 = new MirrorDecorator(stickyHookHouse);
mirrorDecorator1.lookMirror();
mirrorDecorator1.live();
// mirrorDecorator1.handThings(); // 没有此方法, 半透明装饰模式中无法多次装饰

}
}

输出:

1
2
3
房屋的原有功能:居住功能
房屋的原有功能:居住功能
有了粘钩功能后,新增了挂东西的功能

5. 外观模式

外部与一个子系统的通信必须通过一个统一的外观对象进行,为子系统中的一组接口提供一个一致的界面,外观模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。外观模式又称为门面模式。
将多个子系统封装起来,提供一个更为简洁的接口供外部调用

在这里插入图片描述

6. 享元模式

运用共享技术有效地支持大量细粒度对象的复用。系统只使用少量的对象,而这些对象都很相似,状态变化很小,可以实现对象的多次复用。由于享元模式要求能够共享的对象必须是细粒度对象,因此它又称为轻量级模式。
通俗的说就是尽可能共享对象提高复用性

7. 代理模式

给某一个对象提供一个代理,并由代理对象控制对原对象的引用。

静态代理

例子 :

在网络请求前后,分别打印将要发送的数据和接收到数据作为日志信息。此时我们就可以新建一个网络请求的代理类,让它代为处理网络请求,并在代理类中打印这些日志信息。

网络请求接口:

1
2
3
4
5
public interface IHttp {
void request(String sendData);

void onSuccess(String receivedData);
}

http请求工具类:

1
2
3
4
5
6
7
8
9
10
11
public class HttpUtil implements IHttp {
@Override
public void request(String sendData) {
System.out.println("网络请求...");
}

@Override
public void onSuccess(String receiveData) {
System.out.println("网络请求完成");
}
}

http请求代理类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class HttpProxy implements IHttp {

private final IHttp http;

public HttpProxy(IHttp http) {
this.http = http;
}

@Override
public void request(String sendData) {
System.out.println("发送数据" + sendData);
http.request(sendData);
}

@Override
public void onSuccess(String receiveData) {
System.out.println("收到数据" + receiveData);
http.request(receiveData);
}
}

客户端测试:

1
2
3
4
5
6
7
8
9
10
public class Client {
@Test
public void test() {
HttpUtil httpUtil = new HttpUtil();
// 代理模式是为了进行控制, 装饰模式是为了增强或添加功能
HttpProxy httpProxy = new HttpProxy(httpUtil);
httpProxy.request("request data");
httpProxy.onSuccess("received result");
}
}

输出:

1
2
3
4
发送数据request data
网络请求...
收到数据received result
网络请求...

代理模式主要是为了加以控制

动态代理

动态代理,需要把一个类传入,然后根据它正在调用的方法名判断是否需要加以控制。利用了反射技术

实现动态代理的代码如下:

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 HttpProxy implements InvocationHandler {

private HttpUtil httpUtil;

public IHttp getInstance(HttpUtil httpUtil) {
this.httpUtil = httpUtil;
return (IHttp) Proxy.newProxyInstance(HttpUtil.class.getClassLoader(),
httpUtil.getClass().getInterfaces(), this);
}


@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = null;
if (method.getName().equals("request")) {
System.out.println("发送数据" + args[0]);
result = method.invoke(httpUtil, args);
} else if (method.getName().equals("onSuccess")) {
System.out.println("收到数据" + args[0]);
result = method.invoke(httpUtil, args);
}
return result;
}
}

客户端测试:

1
2
3
4
5
6
7
8
public class Client {
public static void main(String[] args) {
HttpUtil httpUtil = new HttpUtil();
IHttp proxy = new HttpProxy().getInstance(httpUtil);
proxy.request("request data");
proxy.onSuccess("received result");
}
}

输出:

1
2
3
4
发送数据request data
网络请求...
收到数据received result
网络请求完成

Git仓库

https://github.com/wuzheng228/designPatternGuide

参考

《深入浅出设计模式》