设计模式总结(2)【策略模式、观察者模式、装饰者模式,适配器模式】

策略模式

一、基本概念

策略模式是行为型设计模式。

其用意是针对一组算法,将每一个算法封装到具有共同接口的独立的类中,从而使得它们可以相互替换

策略模式使得算法可以在不影响到客户端的情况下发生变化。

二、结构

下面是基本结构:

三类角色:

  • 环境(Context)角色:持有一个Strategy的引用。
  • 抽象策略(Strategy)角色:这是一个抽象角色,通常由一个接口或抽象类实现。此角色给出所有的具体策略类所需的接口。
  • 具体策略(ConcreteStrategy)角色:包装了相关的算法或行为。

三、案例

案例实现:

定义一族算法,可以实现对两个数的操作,例如+、-、*、/等。

实现代码整体框架图:

pic

具体代码实现:

Context类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* Context 是一个使用了某种策略的类。 实现了 Strategy 接口的实体策略类
*/
public class Context {

private Strategy strategy;

public Context(Strategy strategy) {
this.strategy = strategy;
}

public int executeStrategy(int num1, int num2){
return strategy.doOperation(num1, num2);
}
}

Strategy接口:

1
2
3
4
/**策略的公共接口*/
public interface Strategy {
int doOperation(int num1, int num2);
}

三种对应的算法实现:

1
2
3
4
5
6
7
/** 算法1: 实现加法 */
public class OperationAdd implements Strategy {
@Override
public int doOperation(int num1, int num2) {
return num1 + num2;
}
}
1
2
3
4
5
6
7
/** 算法2:实现减法 */
public class OperationSubstract implements Strategy{
@Override
public int doOperation(int num1, int num2) {
return num1 - num2;
}
}
1
2
3
4
5
6
7
/** 算法3: 实现乘法 */
public class OperationMultiply implements Strategy{
@Override
public int doOperation(int num1, int num2) {
return num1 * num2;
}
}

测试类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class MyTest {
public static void main(String[] args) {

Context context = new Context(new OperationAdd());

System.out.println("10 + 5 = " + context.executeStrategy(10, 5));

context = new Context(new OperationSubstract());
System.out.println("10 - 5 = " + context.executeStrategy(10, 5));

context = new Context(new OperationMultiply());
System.out.println("10 * 5 = " + context.executeStrategy(10, 5));
}
}

程序输出:

1
2
3
10 + 5 = 15
10 - 5 = 5
10 * 5 = 50

四、总结

  • 策略模式的重心不是如何实现算法,而是如何组织、调用这些算法;
  • 策略模式一个很大的特点就是各个策略算法的平等性。对于一系列具体的策略算法,大家的地位是完全一样的,正因为这个平等性,才能实现算法之间可以相互替换;
  • 使用策略模式可以避免使用多重条件(if-else)语句;

观察者模式

一、基本概念

观察者模式是行为型设计模式。

  • 定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新
  • 被依赖的对象为Subject(被观察者),依赖的对象为ObserverSubject通知Observer变化;
  • Subject : 登记注册register/attach、移除remove、通知notify
  • Observer : 接收变化update
  • 可以把观察者模式想象成订报纸一样,出版者+订阅者 = 观察者模式

二、结构

观察者模式所涉及的角色有:

  ● 抽象主题(Subject)角色:抽象主题角色把所有对观察者对象的引用保存在一个聚集(比如List对象)里。抽象主题提供一个接口,可以增加和删除观察者对象,抽象主题角色又叫做抽象被观察者(Observable)角色;

  ● 具体主题(ConcreteSubject)角色:将有关状态存入具体观察者对象;在具体主题的内部状态改变时,给所有登记过的观察者发出通知。具体主题角色又叫做具体被观察者(Concrete Observable)角色;

  ● 抽象观察者(Observer)角色:为所有的具体观察者定义一个接口,在得到主题的通知时更新自己,这个接口叫做更新接口。

  ● 具体观察者(ConcreteObserver)角色:存储与主题的状态自恰的状态。具体观察者角色实现抽象观察者角色所要求的更新接口,以便使本身的状态与主题的状态相协调。如果需要,具体观察者角色可以保持一个指向具体主题对象(Concrete Subject)的引用;

02_observer_02.png

三、案例

案例:

实现的功能是气象站的管理,给你一个WeatherData类(被观察者),提供了获取温度,湿度,和气压的函数,要你设计类并添加一些公告板(观察者),可以显示相关的信息;

基本结构图:

02_observer_01.png

下面使用自定义的观察者和Java内置观察者实现

1、自定义的观察者实现

基本代码结构组织图:

02_observer_03.png

先看被观察者包subjects里面的两个:

Subject接口:

1
2
3
4
5
6
/**被观察者接口*/
public interface Subject {
void registerObservers(Observer o);//注册
void removeObservers(Observer o); //移除
void notifyObservers(); //通知
}

WeatherData类 (被观察者的实现):

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
/**
* 被观察者的实现
* 里面有:
* 1、观察者接口的集合数据结构
*  2、实现添加观察者方法(registerObservers)
* 3、移除观察者方法(removeObservers)
* 4、通知所有观察者的方法(notifyObservers)
*/
public class WeatherData implements Subject {

private double temperature;
private double humidity;
private double pressure;

// 可以提供getter()方法

private ArrayList<Observer>observers;

public WeatherData() {
observers = new ArrayList<>();
}

public void setData(double temperature, double humidity, double pressure){
this.temperature = temperature;
this.humidity = humidity;
this.pressure = pressure;

dataChanged();//更新完信息就马上通知观察者
}

//数据改变之后就通知观察者(从气象站得到更新的观测值之后,通知观察者)
public void dataChanged(){
notifyObservers();
}

@Override
public void registerObservers(Observer o) {
observers.add(o);
}

@Override
public void removeObservers(Observer o) {
int index = observers.indexOf(o);
if(index >= 0){
observers.remove(o);
}
}

@Override
public void notifyObservers() {
for(int i = 0; i < observers.size(); i++){
Observer observer = observers.get(i);
observer.update(temperature, humidity, pressure);
}
}
}

然后再看observers包的接口和实现类:

Observer接口:

1
2
3
4
/** 观察者 接口*/
public interface Observer {
void update(double temperature, double humidity, double pressure);
}

两个实现类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/** 观察者1 */
public class CurrentConditionDisplay implements Observer {

private double temperature;
private double humidity;
private double pressure;

@Override
public void update(double temperature, double humidity, double pressure) {
this.temperature = temperature;
this.humidity = humidity;
this.pressure = pressure;
display();
}

public void display() {
System.out.println("CurrentDisplay : " +
"[" + temperature +
", " + humidity +
", " + pressure + "]");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/** 观察者2  明天的天气展示 : Math.random * 当前设置值, 主要是为了展示和上一个观察者的不同*/
public class TomorrowConditionDisplay implements Observer {

private double temperature;
private double humidity;
private double pressure;

@Override
public void update(double temperature, double humidity, double pressure) {
this.temperature = temperature;
this.humidity = humidity;
this.pressure = pressure;
display();
}

// ( (int)(10 * Math.random())/2 + 1) 生成 [1,5]的随机数
public void display() {
System.out.println("TomorrowDisplay : " +
"[" + temperature * ( (int)(10 * Math.random())/2 + 1) +
", " + humidity * ( (int)(10 * Math.random())/2 + 1) +
", " + pressure * ( (int)(10 * Math.random())/2 + 1) + "]");
}
}

最后测试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class MyTest {
public static void main(String[] args) {
WeatherData weatherData = new WeatherData(); // 创建一个被观察者(Subject的实现类)

// 创建两个观察者 (Observer的实现类)
CurrentConditionDisplay current = new CurrentConditionDisplay();
TomorrowConditionDisplay tomorrow = new TomorrowConditionDisplay();

// 在被观察者的List中注册两个观察者
weatherData.registerObservers(current);
weatherData.registerObservers(tomorrow);

// 设置完(天气更新)就会自动通知两个观察者
weatherData.setData(10,100,50);

System.out.println("----------移除Tomorrow公告板----------");
weatherData.removeObservers(tomorrow);

weatherData.setData(20,200,25);
}
}

程序输出:

1
2
3
4
CurrentDisplay : [10.0, 100.0, 50.0]
TomorrowDisplay : [10.0, 100.0, 150.0]
----------移除Tomorrow公告板----------
CurrentDisplay : [20.0, 200.0, 25.0]

2、Java内置观察者实现

被观察者继承自Observable类,观察者实现Observer接口:

  • Observable类中有addObserver()方法,类似于我们的registerObserver()
  • Observable类中有deleteObserver()方法,类似与我们的removeObserver()
  • 此外Observable类中还有两个notifyObservers()方法。为什么两个呢?
    • Java内置的被观察者更新的方法有两种,一种是推,一种是拉;
    • public void notifyObservers(Object arg)对应的是”推”;意思就是推送给观察者;
    • public void notifyObservers()对应的是”拉”;意思就是需要观察者自己拉取数据;
  • 内置的和自己定义的在更新的时候有一个很大的不同就是: Java内置观察者在更新的时候,需要先调用一个setChanged()方法,标记状态已经被改变的事实。这个可以更加灵活的使用观察者模式(在调用setChanged()之前添加一些条件);

基本代码结构组织图:

02_observer_04.png

代码如下:

首先看被观察者:

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
// 这里需要继承Java的Observable类
public class WeatherData extends Observable {

private double temperature;
private double humidity;
private double pressure;


public double getTemperature() {
return temperature;
}

public double getHumidity() {
return humidity;
}

public double getPressure() {
return pressure;
}

public void setData(double temperature, double humidity, double pressure){
this.temperature = temperature;
this.humidity = humidity;
this.pressure = pressure;
dataChanged();//更新完信息就马上通知观察者
}

//数据改变之后就通知观察者(从气象站得到更新的观测值之后,通知观察者)
public void dataChanged(){
this.setChanged(); //这个很重要,一定要设置这个,java底层有一个boolean值 changed = true; , 可以不那么灵活 (可以设置一些条件然后调用setChanged()方法)
notifyObservers(new Data(temperature, humidity, pressure)); //这个是 "推" 数据
// notifyObservers(); // 靠观察者自己 "拉" 数据
}

// 这个类的作用就是为了 适应Observable里面的这个方法(推数据) : public void notifyObservers(Object arg)
public static class Data{

private double temperature;
private double humidity;
private double pressure;

public Data(double temperature, double humidity, double pressure) {
this.temperature = temperature;
this.humidity = humidity;
this.pressure = pressure;
}

public double getTemperature() {
return temperature;
}

public double getHumidity() {
return humidity;
}

public double getPressure() {
return pressure;
}
}
}

然后是两个观察者:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class CurrentConditionDisplay implements Observer{

private double temperature;
private double humidity;
private double pressure;


//这里表示的是直接接受 被观察者的数据("推" 过来的数据 )  --> 也可以自己获取("拉")数据
@Override
public void update(Observable o, Object data) { //注意这里还有被观察者的引用
this.temperature = ((WeatherData.Data)data).getTemperature(); //强制类型转换一下
this.humidity = ((WeatherData.Data)data).getHumidity();
this.pressure = ((WeatherData.Data)data).getPressure();
display();
}

public void display() {
System.out.println("CurrentDisplay : " +
"[" + temperature +
", " + humidity +
", " + pressure + "]");
}
}
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 TomorrowConditionDisplay implements Observer{

private double temperature;
private double humidity;
private double pressure;

// 这个就是
@Override
public void update(Observable observable, Object data) {
// 两种设值方式 "推" | "拉"
// "推" 过来的
// this.temperature = ((WeatherData.Data)data).getTemperature();
// this.humidity = ((WeatherData.Data)data).getHumidity();
// this.pressure = ((WeatherData.Data)data).getPressure();

// 自己 "拉" 过来的
this.temperature = ((WeatherData)observable).getTemperature();
this.humidity = ((WeatherData)observable).getHumidity();
this.pressure = ((WeatherData)observable).getPressure();
display();
}

// ( (int)(10 * Math.random())/2 + 1) 生成 [1,5]的随机数
public void display() {
System.out.println("TomorrowDisplay : " +
"[" + temperature * ( (int)(10 * Math.random())/2 + 1) +
", " + humidity * ( (int)(10 * Math.random())/2 + 1) +
", " + pressure * ( (int)(10 * Math.random())/2 + 1) + "]");
}
}

测试:

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

public static void main(String[] args) {
WeatherData weatherData = new WeatherData();// 创建被观察者

// 创建两个观察者
CurrentConditionDisplay current = new CurrentConditionDisplay();
TomorrowConditionDisplay tomorrow = new TomorrowConditionDisplay();

weatherData.addObserver(current);
weatherData.addObserver(tomorrow);

weatherData.setData(11,222,33);

System.out.println("----------移除Tomorrow公告板----------");
weatherData.deleteObserver(tomorrow);
weatherData.setData(22,444,66);
}
}

输出:

1
2
3
4
TomorrowDisplay : [33.0, 666.0, 33.0]
CurrentDisplay : [11.0, 222.0, 33.0]
----------移除Tomorrow公告板----------
CurrentDisplay : [22.0, 444.0, 66.0]

四、总结

使用场景

  • 一个抽象模型有两个方面,其中一个方面依赖于另一个方面。将这些方面封装在独立的对象中使它们可以各自独立地改变和复用。
  • 一个对象的改变将导致其他一个或多个对象也发生改变,而不知道具体有多少对象将发生改变,可以降低对象之间的耦合度。
  • 一个对象必须通知其他对象,而并不知道这些对象是谁。
  • 需要在系统中创建一个触发链,A对象的行为将影响B对象,B对象的行为将影响C对象……,可以使用观察者模式创建一种链式触发机制。

五、实际应用

在spring中,通过ApplicationEvent类和ApplicationListener接口,可以实现ApplicationContext事件处理,这种机制就是利用的观察者模式
spring有一些内置的事件,当完成某种操作时会发出某些事件动作。比如监听ContextRefreshedEvent事件,当所有的bean都初始化完成并被成功装载后会触发该事件,实现ApplicationListener接口可以收到监听动作,然后可以写自己的逻辑。
使用时实现 ApplicationListener接口并传入ContextRefreshedEvent参数

1
2
3
4
5
6
7
8
  @Component
public class TestApplicationListener implements ApplicationListener<ContextRefreshedEvent>{
@Override
public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) {
System.out.println(contextRefreshedEvent);
System.out.println("TestApplicationListener............................");
}
}

另外,jdk中的诸多listener都是用的这种设计模式
java.util.EventListener
javax.servlet.http.HttpSessionBindingListener
javax.servlet.http.HttpSessionAttributeListener
javax.faces.event.PhaseListener

装饰者模式

一、基本概念

装饰者模式是结构型设计模式。

装饰模式以对客户端透明的方式扩展对象的功能,是继承关系的一个替代方案。

允许向一个现有的对象添加新的功能。同时又不改变其结构,它是作为现有的类的一个包装。

主要解决的问题: 一般我们为了扩展一个类经常使用继承方式实现,由于继承为类引入静态特征,并且随着扩展功能的增多,子类会很膨胀

二、结构

结构:

  • 装饰者(Decorator)和具体组件(ConcreteComponent)都继承自组件(Component);
  • 所谓装饰,就是把这个装饰者套在被装饰者之上,从而动态扩展被装饰者的功能;
  • 装饰者的方法有一部分是自己的,这属于它的功能(半透明的装饰者模式)。然后调用被装饰者的方法实现,从而也保留了被装饰者的功能;

03_decorator_02.png

三、案例

1、装饰者模式案例

模拟在餐馆点饮料,我们可以点咖啡,而咖啡有Decaf咖啡和Espresso咖啡,而这两种咖啡都可以加牛奶和巧克力进去。

具体的代码组织结构图:

03_decorator_01.png

具体代码:

先看最高的component包下的Drink类:

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
/**
* Component的超类
* 单品和装饰者都要继承自这个类
*/
public abstract class Drink {

private String description = ""; //一开始没有描述
private double price = 0; //一开始价格为0

/**
* 抽象方法
* 1、如果是单品的话就直接是自己的价格
* 2、如果是装饰者的话就还要加上装饰品自己的价格
*/
public abstract double cost();


// setter getter

public String getDescription() {
return description;
}
public double getPrice() {
return price;
}
public void setDescription(String description) { //描述的时候顺便把价格描述一下
this.description = description;
}
public void setPrice(double price) {
this.price = price;
}
}

下面看两个具体的Component:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/** ConcreteComponent 1*/
public class Decaf extends Drink {

public Decaf() {
super.setDescription("Decaf");
super.setPrice(3); //3块钱
}

@Override
public double cost() {
return getPrice();//super.getPrice()//这个就是父类的价格(自己什么也没加 (没有被装饰))
}

// 重写getter 后面加上自己的花费
@Override
public String getDescription() {
return super.getDescription() + "-" + cost();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/** ConcreteComponent 2
* 也可以在ConcreteComponent和Drink类有一个过渡的类) (比如Coffee类)
*/
public class Espresso extends Drink {

public Espresso(){
super.setDescription("Espresso");
super.setPrice(4);
}

@Override
public double cost() {
return getPrice();//super.getPrice()//这个就是父类的价格(自己什么也没加)
}

@Override
public String getDescription() {
return super.getDescription() + "-" + cost();
}
}

下面看decorator下的三个类:

第一个是装饰者的超类,继承自Drink类:

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
public class Decorator extends Drink{
/**
* 这个引用很重要,可以是单品,也可以是被包装过的类型,所以使用的是超类的对象
* 这个就是要被包装的单品(被装饰的对象)
*/
private Drink drink; //这里要拿到父类的引用,因为要控制另一个分支(具体的组件)

public Decorator(Drink drink) {
this.drink = drink;
}

/**
* 如果drink是已经被装包过的,那么就会产生递归调用  最终到单品
*/
@Override
public double cost() {
return super.getPrice() + drink.cost(); // 自己的价格和被包装单品的价格
}

@Override
public String getDescription() {
return super.getDescription() + "-" + super.getPrice()
+ " && " + drink.getDescription();
}
}

然后是两个装饰者:

1
2
3
4
5
6
7
8
9
10
11
/**
* 这个是具体的装饰者() --> 继承自中间的装饰着Decorator
*/
public class Chocolate extends Decorator{

public Chocolate(Drink drink) { //如果父类搞了一个 带参数的构造函数,子类必须显示的使用super调用
super(drink);
super.setDescription("Chocolate");
super.setPrice(1);
}
}
1
2
3
4
5
6
7
8
public class Milk extends Decorator{

public Milk(Drink drink) {
super(drink); //调用父类的构造函数
super.setDescription("Milk");
super.setPrice(3);
}
}

测试类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class MyTest {
public static void main(String[] args) {
//只点一个单品 (Decaf 咖啡)
Drink order = new Decaf();
System.out.println("order description : " + order.getDescription());
System.out.println("order price : " + order.cost());

System.out.println("---------------加了调料的----------------");

order = new Milk(order);// 加了牛奶
order = new Chocolate(order);
order = new Chocolate(order); // 加了两个巧克力
System.out.println("order description : " + order.getDescription());
System.out.println("order price : " + order.cost());
}
}

程序输出:

1
2
3
4
5
order description : Decaf-3.0
order price : 3.0
---------------加了调料的----------------
order description : Chocolate-1.0 && Chocolate-1.0 && Milk-3.0 && Decaf-3.0
order price : 8.0

2、JavaIO中使用装饰者模式

由于Java I/O库需要很多性能的各种组合,如果这些性能都是用继承的方法实现的,那么每一种组合都需要一个类,这样就会造成大量性能重复的类出现,所以Java IO使用的是装饰者设计模式。

所以我们可以定义自己的装饰者。

这里我们定义一个流,这个流将读入的小写字母转换成大写字母。

UpperCaseInputStream代码如下:

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
/**
* 自己定义的输入流  
* 扩展FilterInputStream(这个类就是我们的Decorator) 中间装饰者  
* 所以我们只要继承这个就可以扩展自己的输入流装饰者 
*/
public class UpperCaseInputStream extends FilterInputStream{

protected UpperCaseInputStream(InputStream in) { //这个InputStream就是我们的Drink 类(超类)
super(in);
}

// 实现两个read()方法,将大写转化成小写的读入

//重写 相当于cost和description
@Override
public int read() throws IOException {
int index = super.read(); //读取一个字节
return index == -1 ? index : Character.toUpperCase((char)(index)); //小写转换成大写
}

//字节数组
@Override
public int read(byte[] b, int off, int len) throws IOException {
int index = super.read(b, off, len);
for(int i = 0; i < index; i++)
b[i] = (byte)Character.toUpperCase((char)(b[i]));
return index;
}
}

测试一下使用这个类:

1
2
3
4
5
6
7
8
9
10
public class MyTest {

public static void main(String[] args) throws IOException {
InputStream in = new UpperCaseInputStream(new BufferedInputStream(new FileInputStream("/home/zxzxin/Java_Maven/DesignPatterns/src/main/java/decorator/java/in.txt")));// 将这个in.txt文件读入的内容转换成大写
int len;
while((len = in.read()) >= 0)
System.out.print((char)(len));
in.close();
}
}

输出结果演示:

03_decorator_04.png

四、总结

优缺点:

  • 优点 : 装饰类和被装饰类可以独立发展,不会相互耦合,装饰模式是继承的一个替代模式,装饰模式可以动态扩展一个实现类的功能。
  • 缺点 : 多层装饰比较复杂。

实际应用:  大多数情况下,装饰模式的实现都要比上面给出的示意性例子要简单。

  • 如果只有一个ConcreteComponent类,那么可以考虑去掉抽象的Component类(接口),把Decorator作为一个ConcreteComponent子类;
  •  如果只有一个ConcreteDecorator类,那么就没有必要建立一个单独的Decorator类,而可以把Decorator和ConcreteDecorator的责任合并成一个类。

可以把装饰模式理解为不需要实现接口就可以扩展某些实例的功能
更加详细的解释具体可以看这篇博客

适配器模式

一、基本概念

将一个类的接口,转换成客户期望的另一个接口。适配器让原本接口不兼容的类可以合作无间。

可以将适配器理解为我们日常用的电脑充电器: 家庭电压为220V,而电脑充电频率是20V左右,所以需要适配。

二、结构

适配器可以分为两种: 对象适配器和类适配器。

对象适配器:

07_adapter_01.png

从用户的角度看不到被适配者

用户调用适配器转换出来的目标接口方法。适配器再调用被适配者的相关接口方法。

用户收到反馈结果,感觉只是和目标接口交互。

类适配器:

07_adapter_05.png

通过多重继承目标接口和被适配者类方式来实现适配。

三、案例

案例: 将火鸡冒充成鸭子。

1、对象适配器模式

逻辑图:

07_adapter_03.png

代码组织结构图:

07_adapter_02.png

被适配者火鸡Turkey:

1
2
3
4
public interface Turkey {
void gobble(); // 火鸡叫声
void fly();
}
1
2
3
4
5
6
7
8
9
10
11
12
/** 野火鸡 */
public class WildTurkey implements Turkey{
@Override
public void gobble() {
System.out.println("Go Go!");
}

@Override
public void fly() {
System.out.println("I am Flying a short distance!");
}
}

目标对象Duck:

1
2
3
4
5
/** 鸭子的接口 */
public interface Duck {
void quack();//鸭子叫声
void fly();
}

适配器TurkeyAdapter:

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

/**
* 在外面表现是 鸭子(目标),但是实质是火鸡(被适配者)
*/
public class TurkeyAdapter implements Duck { //实现目标的接口

private Turkey turkey; //这种对象型适配器必须要组合 被适配者,也就是要有适配者的引用

public TurkeyAdapter(Turkey turkey) {
this.turkey = turkey;
}

// 实际是火鸡在叫
@Override
public void quack() {
turkey.gobble();//外面表现是quack,但是内部是turkey.gobble()
}

@Override
public void fly() {
//由于火鸡飞的短,所以多飞几次,让火鸡更像鸭子
for(int i = 0; i < 6; i++){
turkey.fly();
}
}
}

测试:

1
2
3
4
5
6
7
8
9
public class MyTest {

public static void main(String[] args) {
WildTurkey turkey = new WildTurkey();
Duck duck = new TurkeyAdapter(turkey);
duck.quack(); //看似是鸭子,其实内置是火鸡
duck.fly();
}
}

输出:

1
2
3
4
5
6
7
Go Go!
I am Flying a short distance!
I am Flying a short distance!
I am Flying a short distance!
I am Flying a short distance!
I am Flying a short distance!
I am Flying a short distance!

2、类适配器模式

基本结构图:

07_adapter_04.png

虽然Java不支持多继承,但是可以实现的同时继承。

只有TurkeyAdapter有一些代码变动,其他不变:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
* 和 对象适配器模式唯一的不同就是 : 适配器直接继承 被适配者 (而不是组合)
*/
public class TurkeyAdapter extends WildTurkey implements Duck {

@Override
public void quack() {
super.gobble(); //直接继承 被适配者
}

@Override
public void fly() {
//让火鸡飞6次,飞的像鸭子
super.fly();
super.fly();
super.fly();
super.fly();
super.fly();
super.fly();
}
}

测试:

1
2
3
4
5
6
7
8
public class MyTest {

public static void main(String[] args) {
TurkeyAdapter duck = new TurkeyAdapter();//直接new即可
duck.quack();
duck.fly();
}
}

输出和上面对象适配器一样。

3、Java中从以前枚举器Enumeration到迭代器Iterator的适配

EnumerationIterator适配的结构图:

07_adapter_06.png

一个实例代码:(下面EnumerationIterator就是一个适配器)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/** 对象适配器的举例 java中的枚举到迭代器 */
public class EnumerationIterator implements Iterator<Object> {
private Enumeration enumeration; //枚举 : 被适配者

public EnumerationIterator(Enumeration enumeration) {
this.enumeration = enumeration;
}

@Override
public boolean hasNext() {
return enumeration.hasMoreElements(); //实际调用的是 被适配者 的方法
}

@Override
public Object next() {
return enumeration.nextElement();
}

//这个可以说是适配器的缺点, 有些不能适配,比如两个插孔的插头不能适配为三个插孔的插头
@Override
public void remove() {
throw new UnsupportedOperationException();
}
}

四、总结

对象适配器和类适配器使用了不同的方法实现适配,对象适配器使用组合,类适配器使用继承
总的来说,这种模式的思想就是把不能使用在特定环境的对象或类,通过适配器类,转换为可以用在该环境下的类或对象。

实际使用

除了上面提到的使用外,在springmvc中,这种模式也有使用
Spring MVC中的Controller种类众多,不同类型的Controller通过不同的方法来对请求进行处理。如果不利用适配器模式的话,DispatcherServlet直接获取对应类型的Controller,需要的自行来判断,每增加一个controller,就会多一层判断
为了实现适配器模式,首先定义一个接口

1
2
3
4
5
6
7
8
9
10
11
public interface HandlerAdapter {


boolean supports(Object handler);


ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception;



}

supports()方法传入处理器(宽泛的概念Controller,以及HttpRequestHandler,Servlet,等等)判断是否与当前适配器支持如果支持则从DispatcherServlet中的HandlerAdapter实现类中返回支持的适配器实现类。handler方法就是代理Controller来执行请求的方法并返回结果。

在DispatchServlert中的doDispatch方法中

1
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

此代码通过调用DispatchServlert 中getHandlerAdapter传入Controller(宽泛的概念Controller,以及HttpRequestHandler,Servlet,等等),来获取对应的HandlerAdapter 的实现子类,从而做到使得每一种Controller有一种对应的适配器实现类