设计模式之状态模式、备忘录模式和观察者模式

还剩下最后3个模式了,学完了之后设计模式的笔记就告一段落了。与其说是“写”博客,不如说是记录笔记,学完一遍设计模式,
知道各个模式大概什么样子,以后碰到了再回来翻翻笔记,就很舒服。备战春招的时候再来二刷吧。

状态模式

描述:让一个对象在其内部状态改变的时候,其行为也随之改变。状态模式需要对每一个系统可能获取的状态创立一个状态类的子类。当系统的状态变化时,系统便改变所选的子类。

状态模式

什么时候用状态模式?
当一个对象的行为取决于它的状态,且它必须在运行时刻根据状态改变它的行为时,就考虑用状态模式。状态模式主要解决的是当控制一个对象状态转换的条件表达式过于复杂的情况。把状态的判断逻辑转移到表示不同状态的一系列类中,可以把复杂的判断逻辑简化。

状态模式的3个角色

  • Context(环境角色): 它定义了客户程序需要的接口并维护一个具体状态角色的实例,将与状态相关的操作委托给当前的具体状态对象来处理。

  • State(抽象状态): 定义一个接口以封装使用上下文环境的的一个特定状态相关的行为。

  • ConcreteState(具体状态):实现抽象状态定义的接口。

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
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
//State
abstract class Period {
abstract void eat(MealTime mealTime);
}

//ConcreteState
class Morning extends Period {
@Override
void eat(MealTime mealTime) {
if (mealTime.hour > 6 && mealTime.hour <= 9) {
System.out.println("现在是早上,可以吃包子喝豆浆");
} else {
mealTime.setCurrent(new Noon());
mealTime.eat();
}
}
}

class Noon extends Period {
@Override
void eat(MealTime mealTime) {
if (mealTime.hour > 9 && mealTime.hour <= 13) {
System.out.println("快到中午了,可以吃大碗宽面");
} else {
mealTime.setCurrent(new Afternoon());
mealTime.eat();
}
}
}

class Afternoon extends Period {
@Override
void eat(MealTime mealTime) {
if (mealTime.hour > 13 && mealTime.hour <= 17) {
System.out.println("现在是下午,给阿姨倒杯卡布奇诺");
} else {
mealTime.setCurrent(new Evening());
mealTime.eat();
}
}
}

class Evening extends Period {
@Override
void eat(MealTime mealTime) {
if (mealTime.hour > 17 && mealTime.hour <= 22) {
System.out.println("现在是晚上,可以吃葱油拌面");
} else {
System.out.println("想吃夜宵?不怕长膘?");
}
}
}

//Context
class MealTime {
private Period current;
protected double hour;

public MealTime() {
current = new Morning();
}

public void setCurrent(Period current) {
this.current = current;
}

public double getHour() {
return hour;
}

public void setHour(double hour) {
this.hour = hour;
}

public void eat() {
current.eat(this);
}
}

主程序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class State {
public static void main(String[] args) {
MealTime mealTime = new MealTime();
mealTime.setHour(7);
mealTime.eat();
mealTime.setHour(10);
mealTime.eat();
mealTime.setHour(15);
mealTime.eat();
mealTime.setHour(18);
mealTime.eat();
mealTime.setHour(23);
mealTime.eat();
}
}

可以看到,客户端只更改了状态的某一参数,就有不同的行为。调用具体方法eat()的时候,是看不到有选择的过程的,避免了大量的if else判断。

状态模式的好处
将与特定状态相关的行为局部化,并且将不同状态的行为分割开。可以消除庞大的分支语句,通过定义新的子类可以很容易地增加新的状态和转换。

备忘录模式

描述:备忘录对象是一个用来存储另外一个对象内部状态的快照的对象。备忘录模式的用意是在不破坏封装的条件下,将一个对象的状态捉住,并外部化,存储起来,从而可以在将来合适的时候把这个对象还原到存储起来的状态。

备忘录模式

什么时候用备忘录模式?
备忘录模式适用于功能比较复杂的,但需要维护或记录属性历史的类,或者需要保存的属性只是众多属性的小部分时。

tips: 在某个系统使用命令模式时,需要实现命令的撤销功能,可以使用备忘录模式来存储可撤销操作的状态

备忘录模式的3个角色

  • Originator(原发器):它是一个普通类,可以创建一个备忘录,并存储它的当前内部状态,也可以使用备忘录来恢复其内部状态,一般将需要保存内部状态的类设计为原发器。

  • Memento(备忘录):存储原发器的内部状态,根据原发器来决定保存哪些内部状态。备忘录的设计一般可以参考原发器的设计,根据实际需要确定备忘录类中的属性。需要注意的是,除了原发器本身与负责人类之外,备忘录对象不能直接供其他类使用,原发器的设计在不同的编程语言中实现机制会有所不同。

  • Caretaker(负责人):负责人又称为管理者,它负责保存备忘录,但是不能对备忘录的内容进行操作或检查。在负责人类中可以存储一个或多个备忘录对象,它只负责存储对象,而不能修改对象,也无须知道对象的实现细节。

备忘录模式的核心是备忘录类以及用于管理备忘录的负责人类的设计。

用备忘录模式模拟下棋时的悔棋操作

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
61
62
63
64
65
66
67
68
69
70
71
//Originator
class Chessman {
private String label;
private int x;
private int y;

public Chessman(String label, int x, int y) {
this.label = label;
this.x = x;
this.y = y;
}

public void setPosition(int x, int y) {
this.x = x;
this.y = y;
}

//保存状态
public ChessmanMemento save() {
return new ChessmanMemento(label, x, y);
}

//恢复状态
public void restore(ChessmanMemento memento) {
this.label = memento.getLabel();
this.x = memento.getX();
this.y = memento.getY();
}

public void show() {
System.out.println(String.format("棋子<%s>:当前位置为:<%d, %d>", label, x, y));
}
}

//Memento
class ChessmanMemento {
private String label;
private int x;
private int y;

public ChessmanMemento(String label, int x, int y) {
this.label = label;
this.x = x;
this.y = y;
}

public String getLabel() {
return label;
}

public int getX() {
return x;
}

public int getY() {
return y;
}
}

//Caretaker
class MementoCaretaker {
private ChessmanMemento memento;

public ChessmanMemento getMemento() {
return memento;
}

public void setMemento(ChessmanMemento memento) {
this.memento = memento;
}
}

主程序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Memento {
public static void main(String[] args) {
Chessman chessman = new Chessman("黑子", 1, 1);
chessman.show();

MementoCaretaker caretaker = new MementoCaretaker();
caretaker.setMemento(chessman.save());

chessman.setPosition(2, 3);
chessman.show();

chessman.restore(caretaker.getMemento());
chessman.show();
}
}

观察者模式

描述:在对象间定义一个一对多的联系性,由此当一个对象改变了状态,所有其他相关的对象会被通知并且自动刷新。

观察者模式

观察者模式的动机
将一个系统分割成一系列相互协作的类有很不好的副作用,即需要维护相关对象间的一致性。我们不希望为了维持一致性而使各类紧密耦合,这样会给维护,扩展和重用都带来不便。而观察者模式的关键对象是Subject和Observer,一个Subject可以有任意数目依赖它的Observer,一旦Subject的状态发生变化,所有的Observer都可以得到通知。Subject发出通知时不需要知道它的观察者是谁,任何一个具体观察者也不需要知道其他观察者存在。

什么时候用观察者模式?
当一个对象的改变需要同时改变其他对象,且不知道具体有多少对象有待改变时;一个抽象模型有两个方面,其中一方面依赖于另外一方面,这时用观察者模式可以将这两者封装在独立的对象中使它们各自独立改变和复用。

观察者模式的4个角色

  • Subject(目标):目标又称为主题,它是指被观察的对象。在目标中定义了一个观察者集合,一个观察目标可以接受任意数量的观察者来观察,它提供一系列方法来增加和删除观察者对象,同时它定义了通知方法notify()。目标类可以是接口,也可以是抽象类或具体类。

  • ConcreteSubject(具体目标):具体目标是目标类的子类,通常它包含有经常发生改变的数据,当它的状态发生改变时,向它的各个观察者发出通知;同时它还实现了在目标类中定义的抽象业务逻辑方法(如果有的话)。如果无须扩展目标类,则具体目标类可以省略。

  • Observer(观察者):观察者将对观察目标的改变做出反应,观察者一般定义为接口,该接口声明了更新数据的方法update(),因此又称为抽象观察者。

  • ConcreteObserver(具体观察者):在具体观察者中维护一个指向具体目标对象的引用,它存储具体观察者的有关状态,这些状态需要和具体目标的状态保持一致;它实现了在抽象观察者Observer中定义的update()方法。通常在实现时,可以调用具体目标类的attach()方法将自己添加到目标类的集合中或通过detach()方法将自己从目标类的集合中删除。

举个微信公众号推送的例子,非常契合

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
61
62
63
64
65
66
67
68
69
70
71
//Observer
interface Subscriber {
void receive(String publisher, String articleName);
}

//ConcreteObserver
class WeChatClient implements Subscriber {
private String username;

public WeChatClient(String username) {
this.username = username;
}

@Override
public void receive(String publisher, String articleName) {
System.out.println(String.format("用户<%s> 接收到 <%s>微信公众号 的推送,文章标题为 <%s>", username, publisher, articleName));
}
}

//Subject
class Publisher {
private List<Subscriber> subscribers;
private boolean pubStatus = false;

public Publisher() {
subscribers = new ArrayList<>();
}

public void subscribe(Subscriber subscriber) {
this.subscribers.add(subscriber);
}

public void unsubscribe(Subscriber subscriber) {
if (this.subscribers.contains(subscriber)) {
this.subscribers.remove(subscriber);
}
}

public void notifySubscribers(String publisher, String articleName) {
if (!this.pubStatus) {
return;
}
for (Subscriber subscriber : this.subscribers) {
subscriber.receive(publisher, articleName);
}
this.clearPubStatus();
}

protected void setPubStatus() {
this.pubStatus = true;
}

protected void clearPubStatus() {
this.pubStatus = false;
}
}

//ConcreteSubject
class WeChatAccounts extends Publisher {
private String name;

public WeChatAccounts(String name) {
this.name = name;
}

public void publishArticles(String articleName, String content) {
System.out.println(String.format("\n<%s>微信公众号 发布了一篇推送,文章名称为 <%s>,内容为 <%s> ", this.name, articleName, content));
setPubStatus();
notifySubscribers(this.name, articleName);
}
}

主程序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Observer {
public static void main(String[] args) {
WeChatAccounts accounts = new WeChatAccounts("X博士");

WeChatClient user1 = new WeChatClient("ayahiro");
WeChatClient user2 = new WeChatClient("bxy0516");
WeChatClient user3 = new WeChatClient("tom");

accounts.subscribe(user1);
accounts.subscribe(user2);
accounts.subscribe(user3);

accounts.publishArticles("article1", "something1");

accounts.unsubscribe(user1);
accounts.publishArticles("article2", "something2");
}
}

总的来说,观察者模式所做的工作是在解除耦合,让耦合的双方都依赖于抽象,而不是依赖于具体,是依赖倒转原则的最佳体现。

参考资料:

0%