设计模式入门

OO基础

抽象、封装、多态、继承

OO原则

  • 封装编号,

  • 多用组合少用继承

  • 针对接口编程,不针对实现编程

“针对接口编程”真正的意思是“针对超类型(supertype)编程”。

这里所谓的“接口”有多个含义,接口是一个“概念”,也是一种Java的interface构造。你可以在不涉及Java interface的情况下,“针对接口编程”,关键就在多态。利用多态,程序可以针对超类型编程,执行时会根据实际状况执行到真正的行为,不会被绑死在超类型的行为上。“针对超类型编程”这句话,可以更明确地说成“变量的声明类型应该是超类型,通常是一个抽象类或者是一个接口,如此,只要是具体实现此超类型的类所产生的对象,都可以指定给这个变量。这也意味着,声明类时不用理会以后执行时的真正对象类型!”

策略模式

策略模式定义了算法族,分别封装起来,让它们之间可以互相替换,此模式让算法的变化独立于使用算法的客户。在策略模式(Strategy Pattern)中,一个类的行为或其算法可以在运行时更改。这种类型的设计模式属于行为型模式。

应用示例,我们现在都有超类Duck,具有共同的特征会游泳,后续客户应要求又新生了两种行为:嘎嘎叫和飞行。我们最开始都在父类上增加方法能力,但是问题来了。比如 FlyDuck鸭子是不会嘎嘎叫的,现在客户知道Duck类内的fly()和quack()会随着鸭子的不同而改变,现在客户要求只能有指定的行为。

1
2
3
4
5
6
public abstract class Duck {

public void swim() {
System.out.println("这是我们共同的特征,都会游泳");
}
}

我们将把它们从Duck类中取出来,建立一组新类来代表每个行为。

1
2
3
4
5
6
7
public interface FlyBehavior {
void fly();
}

public interface QuackBehavior {
void quack();
}

这次不让鸭子类实现Flying与Quacking接口,提供两个行为类,由行为类实现行为接口 。比如实现Flying接口:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 不会飞
public class FlyNoWay implements FlyBehavior {
@Override
public void fly() {
System.out.println("I can't fly");
}
}

// 会飞的
public class FlyWithWings implements FlyBehavior {
@Override
public void fly() {
System.out.println("I'm flying");
}
}

以前是行为来自Duck超类的具体实现或是继承某个接口并有子类自行实现而来。这都是依赖于“ 实现 ”,我们就被实现绑的死死的。

在现在我们的鸭子子类将使用接口(FlyBehavior与QuackBehavior)所表示的行为。

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
public abstract class Duck {

public void swim() {
System.out.println("这是我们共同的特征,都会游泳");
}

protected FlyBehavior flyBehavior;

protected QuackBehavior quackBehavior;

public void performFly() {
flyBehavior.fly();
}

public void performQuack() {
quackBehavior.quack();
}
}

// ===>
public class FlyDuck extends Duck {

public FlyDuck() {
flyBehavior = new FlyWithWings();
quackBehavior = new MuteQuack() ;
}

public void display() {
System.out.println("I'm a real Fly and mute Duck");
}

}

有了提供的行为,鸭子现在只需要去叫quackBehavior去沉默或者呱呱叫就好

1
2
3
4
5
6
7
public class Test {
public static void main(String[] args) {
Duck flyDuck = new FlyDuck();
flyDuck.performFly();
flyDuck.performQuack();
}
}

主要解决:在有多种算法相似的情况下,使用 if…else 所带来的复杂和难以维护。

何时使用:一个系统有许多许多类,而区分它们的只是他们直接的行为。

如何解决:将这些算法封装成一个一个的类,任意地替换。

优点: 1、算法可以自由切换。 2、避免使用多重条件判断。 3、扩展性良好。

缺点: 1、策略类会增多。 2、所有策略类都需要对外暴露。

使用场景: 1、如果在一个系统里面有许多类,它们之间的区别仅在于它们的行为,那么使用策略模式可以动态地让一个对象在许多行为中选择一种行为。 2、一个系统需要动态地在几种算法中选择一种。 3、如果一个对象有很多的行为,如果不用恰当的模式,这些行为就只好使用多重的条件选择语句来实现。

注意事项:如果一个系统的策略多于四个,就需要考虑使用混合模式,解决策略类膨胀的问题。

观察者模式

定义了对象之间的一对多依赖,这样一来,当一个对象改变状态时,它的所有依赖者都会收到通知并自动更新。

img)

下面我们从一个示例说起,建立一个应用,利用WeatherData对象取得数据,并更新三个布告板:目前状况、气象统计和天气预报。我们需要在气象站更新最新数据时,布告板也能够及时显示最新的。

我们首先创建一个主题Subject接口,它有着允许观察者注册、移除及通知观察者状态改变

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public interface Subject {
/**
* 需要观察者作为变量来 注册观察者到主题中
* @param observer
*/
public void registerObserver(Observer observer);

/**
* 需要观察者作为变量来 移出主题中的观察者到
* @param observer
*/
public void removeObserver(Observer observer);

/**
* 当主题状态改变,调用通知观察者
*/
public void notifyObserver();
}

既然有了主题,那么肯定也需要观察者Observer接口的

1
2
3
4
5
6
7
8
9
public interface Observer {
/**
* 当气象观测值改变时,主题会把这些状态值当作方法的参数,传送给观察者
* @param temp
* @param humidity
* @param pressure
*/
void update(float temp, float humidity, float pressure);
}

然后我们定义气象台,它也是一个主题,所以需要实现顶层主题Subject接口

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
public class WeatherData implements  Subject{

// 记录观察者,在构造器中建立
private ArrayList observers;
// 检测的数据值:温度、湿度、压力
private float temperature;
private float humidity;
private float pressure;

public WeatherData(ArrayList observers) {
this.observers = observers;
}

public WeatherData() {
this.observers = new ArrayList();
}

/*注册观察者*/
@Override
public void registerObserver(Observer observer) {
observers.add(observer);
}

/*移出观察者*/
@Override
public void removeObserver(Observer observer) {
int index = observers.indexOf(observer);
if (index >= 0) {
observers.remove(index);
}
}

/*把状态数据更新给每一个观察者*/
@Override
public void notifyObserver() {
for (int i = 0; i < observers.size(); i++) {
Observer observer = (Observer)observers.get(i);
observer.update(this.temperature, this.humidity, this.pressure);
}
}

/*当从气象站得到更新观测值时,我们通知观察者*/
public void measurementsChanged() {
this.notifyObserver();
}

/*我们想要每本书都随书赠送一个小型气象站,但是出版社不肯。所以和从装置中读取实际的气象数据相比,我们宁愿利用此方法来测试通知布告板数据*/
public void setMeasurements(float temperature, float humidity, float pressure) {
this.temperature = temperature;
this.humidity = humidity;
this.pressure = pressure;
this.measurementsChanged();
}
}

我们最终目的是需要主题状态改变后观察者能够被通知更新并展示,所以布告板在先定义前需要有展示功能

1
2
3
4
5
6
7
public interface DisplayElement {
/**
* 当布告板需要显示时,调用此方法.
* 所有的观察者实现此方法
*/
void display();
}

最后就是定义我们所需要的布告板了

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
/**
* 此布告板实现了Observer接口,所以可以从WeatherData对象中获得改变
* 也实现了DisplayElement布告板接口
*/
public class CurrentConditionsDisplay implements Observer, DisplayElement{
private float temperature;
private float humidity;
private Subject weatherData; // 主题

/*注册方法,保留对Subject主题的引用,防止以后需要移除自身*/
public CurrentConditionsDisplay(Subject weatherData) {
this.weatherData = weatherData;
// 注册自己成观察者
weatherData.registerObserver(this);
}

@Override
public void display() {
System.out.println("Current conditions: " + this.temperature + "F degrees and " +
this.humidity + "% humidity");
}

/*被调用时,我们把温度和适度保存起来,然后调用display方法展示*/
@Override
public void update(float temp, float humidity, float pressure) {
this.temperature = temp;
this.humidity = humidity;
display();
}
}

诚然,我们还可以依次定制多种布告板类型。最后,我们来测试下:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Test {
public static void main(String[] args) {
WeatherData weatherData = new WeatherData();

CurrentConditionsDisplay currentConditionsDisplay = new CurrentConditionsDisplay(weatherData);
CurrentConditionsDisplay currentConditionsDisplay1 = new CurrentConditionsDisplay(weatherData);
CurrentConditionsDisplay currentConditionsDisplay2 = new CurrentConditionsDisplay(weatherData);

weatherData.setMeasurements(80, 60, 30.4f);
weatherData.setMeasurements(82, 70, 34.4f);
weatherData.setMeasurements(78, 90, 29.4f);
}
}

输出结果:

1
2
3
4
5
6
7
8
Current conditions: 80.0F degrees and 60.0% humidity
Current conditions: 80.0F degrees and 60.0% humidity
Current conditions: 80.0F degrees and 60.0% humidity
Current conditions: 82.0F degrees and 70.0% humidity
Current conditions: 82.0F degrees and 70.0% humidity
Current conditions: 82.0F degrees and 70.0% humidity
Current conditions: 78.0F degrees and 90.0% humidity
...

主要解决:一个对象状态改变给其他对象通知的问题,而且要考虑到易用和低耦合,保证高度的协作。

何时使用:一个对象(目标对象)的状态发生改变,所有的依赖对象(观察者对象)都将得到通知,进行广播通知。

如何解决:使用面向对象技术,可以将这种依赖关系弱化。

关键代码:在抽象类里有一个 ArrayList 存放观察者们。

应用实例: 1、拍卖的时候,拍卖师观察最高标价,然后通知给其他竞价者竞价。

优点: 1、观察者和被观察者是抽象耦合的。 2、建立一套触发机制。

缺点: 1、如果一个被观察者对象有很多的直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间。 2、如果在观察者和观察目标之间有循环依赖的话,观察目标会触发它们之间进行循环调用,可能导致系统崩溃。 3、观察者模式没有相应的机制让观察者知道所观察的目标对象是怎么发生变化的,而仅仅只是知道观察目标发生了变化。

使用场景:

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

注意事项: 1、JAVA 中已经有了对观察者模式的支持类。 2、避免循环引用。 3、如果顺序执行,某一观察者错误会导致系统卡壳,一般采用异步方式。

装饰器模式

装饰器模式(Decorator Pattern)允许向一个现有的对象添加新的功能,同时又不改变其结构。这种类型的设计模式属于结构型模式,它是作为现有的类的一个包装。

这种模式创建了一个装饰类,用来包装原有的类,并在保持类方法签名完整性的前提下,提供了额外的功能。

下面我们提供一个饮料然后搭配多种调料满足客户要求,研制出多种新品的饮料

具体思路如下:

首先我们还是定义基类-饮料

1
2
3
4
5
6
7
8
9
10
11
12
public abstract class Beverage {

/*setDescription()已经在此实现了*/
String description = "Unknown Beverage";

public String getDescription() {
return description;
}

/*必须在子类中实现*/
public abstract double cost();
}

然后就是我们需要研发出的新饮料

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 Espresso extends Beverage{

public Espresso() {
description = "Espresso 饮料";
}

@Override
public double cost() {
return 1.99;
}
}

public class Coco extends Beverage{

public HouseBlend() {
description = "Coco 饮料";
}

@Override
public double cost() {
return 2.5;
}
}

饮料有了,剩下的就是饮料所需要的调料了,我们先统一定义一个调料的装饰者,而且需要继承Beverage类

1
2
3
4
5
6
public abstract class CondimentDecorator extends Beverage {

/*所有的调料装饰者都必须重新实现此方法*/
@Override
public abstract String getDescription();
}

与此同时,我们还应该要有更详细的调料装饰者。比如Mocha调料等

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

/*让mocha引用一个Beberage,用一个实例变量记录饮料,也就是被装饰者*/
private Beverage beverage;

/*把饮料当作构造器的参数,再由构造器将此饮料记录在实例变量中*/
public Mocha(Beverage beverage) {
this.beverage = beverage;
}

/*计算Mocha饮料价格,首先调用委托给被装饰对象计算价格,然后加上Mocha的价钱*/
@Override
public double cost() {
return 0.2 + beverage.cost();
}

@Override
public String getDescription() {
return beverage.getDescription() + "搭配调料Mocha:";
}

}

测试

1
2
3
4
5
6
7
8
9
10
11
12
public class Test {
public static void main(String[] args) {
Beverage espresso = new Espresso();
System.out.println(espresso.getDescription() + " $" + espresso.cost());
espresso = new Mocha(espresso);
System.out.println("espresso:" + espresso.getDescription() + " $" + espresso.cost());

Coco coco = new Coco();
coco = new Mocha(Coco);
System.out.println("Coco:" + coco.getDescription() + " $" + coco.cost());
}
}

输出结果:

1
2
3
Espresso饮料 $1.99
espresso:Espresso 搭配调料Mocha: $2.19
coco:coco 搭配调料Mocha: $2.7

意图:动态地给一个对象添加一些额外的职责。就增加功能来说,装饰器模式相比生成子类更为灵活。

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

何时使用:在不想增加很多子类的情况下扩展类。

如何解决:将具体功能职责划分,同时继承装饰者模式。

关键代码: 1、Component 类充当抽象角色,不应该具体实现。 2、修饰类引用和继承 Component 类,具体扩展类重写父类方法。

应用实例: 1、孙悟空有 72 变,当他变成”庙宇”后,他的根本还是一只猴子,但是他又有了庙宇的功能。 2、不论一幅画有没有画框都可以挂在墙上,但是通常都是有画框的,并且实际上是画框被挂在墙上。在挂在墙上之前,画可以被蒙上玻璃,装到框子里;这时画、玻璃和画框形成了一个物体。

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

缺点:多层装饰比较复杂。

使用场景: 1、扩展一个类的功能。 2、动态增加功能,动态撤销。

注意事项:可代替继承。