Java设计模式_结构型模式

概述

设计模式总结,结构型设计模式篇.

结构型设计模式

结构型设计模式,主要是改变代码的结构来达到解耦的目的.使得代码更加容易维护和扩展.

代理模式

用一个代理对象来隐藏具体实现类的实现细节,通常用于在真实的实现逻辑中做增强操作(AOP).

代理指的是,对客户端隐藏真实的实现,由代理对象来负责接收客户端的所有请求.代理类只是代理,并不会完成实际业务的逻辑.

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
//真实的接口
public interface PhoneService {
Phone makeIphone();
Phone makeP30();
}

//真实的实现类
public class PhoneServiceImpl implements PhoneService {
public Phone makeIphone() {
Phone ip = new Iphone();
ip.setSoC("A12");
return ip;
}

public Phone makeP30() {
Phone p30 = new P30();
p30.setSoC("kylin980");
return p30;
}
}

//代理类,必须实现真实的接口.
public class PhoneServiceProxy implements PhoneService {
//在代理类内部创建一个真实的实现类对象,并且私有化起来.
private PhoneService phoneService = new PhoneServiceImpl();
//实现接口的方法
public Phone makeIphone() {
System.out.println("开始生产");
//核心的实现,通过调用真实实现类对象中的方法.
Phone iPhone = phoneService.makeIphone();
//增强操作
iPhone.addEarbuds("EarPods");
System.out.println("结束生产");
return iPhone;
}

public Phone makeP30() {
System.out.println("开始生产");
Phone p30 = phoneService.makeP30();
System.out.println("结束生产");
return p30;
}
}

调用,通过多态的方式创建代理对象

1
2
3
4
//创建代理对象
PhoneService phoneService = new PhoneServiceProxy();
//通过代理对象调用方法
phoneService.makeIphone();

通过代理模式,可以将具体的实现方式隐藏起来,并且可以在代理类中实现切面编程的操作.

AOP其实就是动态代理的过程.在Spring中,我们自己不定义代理类,Spring会帮我们动态定义代理,然后帮我们把贴上@Before,@After,@Around注解的代码动态的添加到代理类中.

Spring动态代理

JDK动态代理.只能对实现了接口的类生成代理,而不能针对类.JDK动态代理是利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理.

CGLIB动态代理.如果没有定义接口,Spring 会采用 CGLIB 进行动态代理,可以使用是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法.CGLIB动态代理是利用asm开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理.

目标对象实现了接口 目标对象没有实现接口
默认情况下会采用JDK的动态代理实现AOP 必须采用CGLIB库,spring会自动在JDK动态代理和CGLIB之间转换
可以强制使用CGLIB实现AOP

设配器模式

将一个类的接口转换成客户希望的另外一个接口.适配器模式使得原本由于接口不兼容而不能在一起工作的那些类可以一起工作.《设计模式:可复用面向对象软件的基础》

设配器模式一般分为三种.一种是默认适配器模式,对象适配器模式,类适配器模式.

默认适配器模式(Default Adapter)

Apache的commons-io包作为默认适配器模式的例子.

此接口定义了很多方法,用于对文件或文件夹进行监控,一旦发生了对应的操作,就会触发相应的方法.

1
2
3
4
5
6
7
8
9
10
11
public interface FileAlterationListener {
//对文件,文件夹的相关操作方法.
void onStart(final FileAlterationObserver observer);
void onDirectoryCreate(final File directory);
void onDirectoryChange(final File directory);
void onDirectoryDelete(final File directory);
void onFileCreate(final File file);
void onFileChange(final File file);
void onFileDelete(final File file);
void onStop(final FileAlterationObserver observer);
}

这个接口的问题是抽象方法太多.假如要使用这个接口就必须实现这个接口里的所有方法,但是假如只需要这个接口中的一两个方法,还是得实现这个接口的所有方法.

因此需要一个适配器,先实现这个接口的.但是所有的实现方法都是空的.

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 FileAlterationListenerAdaptor implements FileAlterationListener {
//所有的方法都是空的
public void onStart(final FileAlterationObserver observer) {
}

public void onDirectoryCreate(final File directory) {
}

public void onDirectoryChange(final File directory) {
}

public void onDirectoryDelete(final File directory) {
}

public void onFileCreate(final File file) {
}

public void onFileChange(final File file) {
}

public void onFileDelete(final File file) {
}

public void onStop(final FileAlterationObserver observer) {
}
}

调用的话,只需要自定义的类继承这个适配器就行了

1
2
3
4
5
6
7
8
9
10
11
12
//只需要继承该适配器类,然后实现想要的方法.
public class FileMonitor extends FileAlterationListenerAdaptor {
public void onFileCreate(final File file) {
// 文件创建
doSomething();
}

public void onFileDelete(final File file) {
// 文件删除
doSomething();
}
}
对象适配器模式

将一个对象当成另一个类的对象来使用.

将”鸡”当成”鸭”来使用,把”鸡”适配成”鸭”.因为现在”鸭”这个接口没有适合的实现类可以使用,所以需要适配器.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public interface Duck {
//鸭子嘎嘎叫
public void quack();
//飞
public void fly();
}

public interface Cock {
//鸡的咯咯叫
public void gobble();
//飞
public void fly();
}

//一个实现类鸡的类
public class WildCock implements Cock {
public void gobble() {
System.out.println("咯咯叫");
}
public void fly() {
System.out.println("鸡也会飞哦");
}
}

要把”鸡”当成”鸭”来用.”鸭”接口有现成的fly();方法,但是没有quack();方法.这个时候就需要适配器进行适配.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//创建一个"鸡"适配器,要当成"鸭"来用,必须先实现"鸭"的接口.
public class CockAdapter implements Duck {
//定义一个"鸡"的变量
Cock cock;
//构造方法中需要一个鸡的实例,此类就是将这只"鸡"适配成"鸭"来用
public CockAdapter(Cock cock) {
this.cock = cock;
}

//实现"鸭"嘎嘎叫的方法
@Override
public void quack() {
//内部其实是一只鸡的咯咯叫
cock.gobble();
}

//实现"鸭"的fly()方法
@Override
public void fly() {
cock.fly();
}
}

调用方式

1
2
3
4
5
6
7
public static void main(String[] args) {
//有一只野鸡
Cock wildCock = new WildCock();

//成功将野鸡适配成鸭
Duck duck = new CockAdapter(wildCock);
}

适配器模式,解决的痛点.当我们需要一只”鸭”对象,但是我们只有一只”鸡”对象.此时就需要定义一个适配器.由这个适配器作为”鸭”的类,内部的实现方式还是通过”鸡”对象来实现的.

类适配器模式
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 interface Target {
void method1();
void method2();
void method3();
}

public class SomeThing {
//大部分实现方法
public void method1() {
...
}

public void method2() {
...
}
}

//通过继承SomeThing的方式,SomeAdapter已经获得了大部分实现方法.
public class SomeAdapter extends SomeThing implements Target {
void method3() {
//需要自己实现的方法3
...
}
}

调用方式

1
2
3
4
5
6
public static void main(String[] args) {
target t = new SomeAdapter();
t.method1();
t.method2();
t.method3();
}

因为SomeAdapter已经通过继承的方式获得了SomeThing的大部分方法.所有可以直接调用.

类适配 对象适配
继承 组合
静态实现 动态实现,需要多实例化一个对象.
使用较少 使用较多
适配器模式 代理模式
将A包装成B,把A当成B来使用.A和B之间原本没有继承关系 增强原方法

桥梁模式

桥梁模式,主要是代码的抽象和解耦.

首先需要一个桥梁.定义一个接口,提供接口方法.

1
2
3
4
//桥梁
public interface DrawAPI {
public void draw(int radius, int x, int y);
}

通过实现类实现接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class RedPen implements DrawAPI {
@Override
public void draw(int radius, int x, int y) {
System.out.println("用红色笔画图,radius:" + radius + ", x:" + x + ", y:" + y);
}
}

public class GreenPen implements DrawAPI {
@Override
public void draw(int radius, int x, int y) {
System.out.println("用绿色笔画图,radius:" + radius + ", x:" + x + ", y:" + y);
}
}

public class BluePen implements DrawAPI {
@Override
public void draw(int radius, int x, int y) {
System.out.println("用蓝色笔画图,radius:" + radius + ", x:" + x + ", y:" + y);
}
}

定义一个抽象类,此类的实现类都需要使用DrawAPI接口

1
2
3
4
5
6
7
8
9
public abstract class Shape {
//私有化接口
protected DrawAPI drawAPI;
//提供构造方法
protected Shape (DrawAPI drawAPI){
this.drawAPI = drawAPI;
}
public abstract void draw();
}

定义抽象类的子类

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
//圆形
public class Circle extends Shape {
//半径
private int radius;
//调用父类的构造方法
public Circle(int radius, DrawAPI drawAPI) {
super(drawAPI);
this.radius = radius;
}

public void draw() {
drawAPI.draw(radius, 0, 0);
}
}

// 长方形
public class Rectangle extends Shape {
private int x;
private int y;

public Rectangle(int x, int y, DrawAPI drawAPI) {
super(drawAPI);
this.x = x;
this.y = y;
}

public void draw() {
drawAPI.draw(0, x, y);
}
}

调用

1
2
3
4
5
6
7
8
public static void main(String[] args) {

Shape greenCircle = new Circle(10, new GreenPen());
Shape redRectangle = new Rectangle(4, 8, new RedPen());

greenCircle.draw();
redRectangle.draw();
}

通过最终的调用方式,可以知道桥梁模式的优点是解耦.通过把接口DrawAPI作为桥梁,而各种颜色画笔的类实现了该接口.通过定义抽象类,在抽象类中使用DrawAPI接口,同时图形(圆形或者长方形)的具体实现类又继承自该抽象类,在子类中通过子类的构造器调用父类构造器中的DrawAPI接口,就可以根据具体传入的参数(颜色画笔对象),获得颜色画笔的实现类.达到解耦的目的.

简而言之,就是通过定义一个接口.把该接口的实现类作为桥梁(参数)传入.通过继承图形的抽象类,定义需要具体的DrawAPI接口对象.