国产一级a片免费看高清,亚洲熟女中文字幕在线视频,黄三级高清在线播放,免费黄色视频在线看

打開(kāi)APP
userphoto
未登錄

開(kāi)通VIP,暢享免費(fèi)電子書等14項(xiàng)超值服

開(kāi)通VIP
從零開(kāi)始學(xué)習(xí)設(shè)計(jì)模式之行為型模式(2)

在設(shè)計(jì)模式系列文章專題中,我們已經(jīng)將 5 種構(gòu)建型模式和 7 種結(jié)構(gòu)型模式介紹完畢。在 里我們已經(jīng)介紹了 2 種行為型模式:責(zé)任鏈模式命令模式。

本篇我們將介紹 4 種行為型模式,分別是

  • 解釋器模式

  • 迭代器模式

  • 中介者模式

  • 備忘錄模式

一、解釋器模式

我國(guó) IT 界歷來(lái)有一個(gè)漢語(yǔ)編程夢(mèng),雖然各方對(duì)于漢語(yǔ)編程爭(zhēng)論不休,甚至上升到民族大義的高度,本文不討論其對(duì)與錯(cuò),但我們不妨來(lái)嘗試一下,定義一個(gè)簡(jiǎn)單的中文編程語(yǔ)法。

在設(shè)計(jì)模式中,解釋器模式就是用來(lái)自定義語(yǔ)法的,它的定義如下。

解釋器模式(Interpreter Pattern):給定一門語(yǔ)言,定義它的文法的一種表示,并定義一個(gè)解釋器,該解釋器使用該表示來(lái)解釋語(yǔ)言中的句子。

解釋器模式較為晦澀難懂,但本文我們?nèi)匀簧钊霚\出,通過(guò)一個(gè)簡(jiǎn)單的例子來(lái)學(xué)習(xí)解釋器模式:使用中文編寫出十以內(nèi)的加減法公式。比如:

  • 輸入“一加一”,輸出結(jié)果 2

  • 輸入“一加一加一”,輸出結(jié)果 3

  • 輸入“二加五減三”,輸出結(jié)果 4

  • 輸入“七減五加四減一”,輸出結(jié)果 5

  • 輸入“九減五加三減一”,輸出結(jié)果 6

看到這個(gè)需求,我們很容易想到一種寫法:將輸入的字符串分割成單個(gè)字符,把數(shù)字字符通過(guò) switch-case 轉(zhuǎn)換為數(shù)字,再通過(guò)計(jì)算符判斷是加法還是減法,對(duì)應(yīng)做加、減計(jì)算,最后返回結(jié)果即可。

計(jì)劃的確可行,但這實(shí)在太面向過(guò)程了,眾所周知面向過(guò)程編程會(huì)有耦合度高,不易擴(kuò)展等缺點(diǎn)。接下來(lái)我們嘗試按照面向?qū)ο蟮膶懛▉?lái)實(shí)現(xiàn)這個(gè)功能。

按照面向?qū)ο蟮木幊趟枷?,我們?yīng)該為公式中不同種類的元素建立一個(gè)對(duì)應(yīng)的對(duì)象。那么我們先分析一下公式中的成員:

  • 數(shù)字:零到九 對(duì)應(yīng) 0 ~ 9

  • 計(jì)算符:加、減 對(duì)應(yīng) +、-

公式中僅有這兩種元素,其中對(duì)于數(shù)字的處理比較簡(jiǎn)單,只需要通過(guò) switch-case 將中文名翻譯成阿拉伯?dāng)?shù)字即可。

計(jì)算符怎么處理呢?計(jì)算符左右兩邊可能是單個(gè)數(shù)字,也可能是另一個(gè)計(jì)算公式。但無(wú)論是數(shù)字還是公式,兩者都有一個(gè)共同點(diǎn),那就是他們都會(huì)返回一個(gè)整數(shù):數(shù)字返回其本身,公式返回其計(jì)算結(jié)果。

所以我們可以根據(jù)這個(gè)共同點(diǎn)提取出一個(gè)返回整數(shù)的接口,數(shù)字和計(jì)算符都作為該接口的實(shí)現(xiàn)類。在計(jì)算時(shí),使用棧結(jié)構(gòu)存儲(chǔ)數(shù)據(jù),將數(shù)字和計(jì)算符統(tǒng)一作為此接口的實(shí)現(xiàn)類壓入棧中計(jì)算。

talk is cheap, show me the code.

數(shù)字和計(jì)算符公共的接口:

interface Expression {
int intercept();
}

上文已經(jīng)說(shuō)到,數(shù)字和計(jì)算符都屬于表達(dá)式的一部分,他們的共同點(diǎn)是都會(huì)返回一個(gè)整數(shù)。從表達(dá)式計(jì)算出整數(shù)的過(guò)程,我們稱之為 解釋(intercept)。

對(duì)數(shù)字類的解釋實(shí)現(xiàn)起來(lái)相對(duì)比較簡(jiǎn)單:

public class Number implements Expression {
int number;

public Number(char word) {
switch (word) {
case '零':
number = 0;
break;
case '一':
number = 1;
break;
case '二':
number = 2;
break;
case '三':
number = 3;
break;
case '四':
number = 4;
break;
case '五':
number = 5;
break;
case '六':
number = 6;
break;
case '七':
number = 7;
break;
case '八':
number = 8;
break;
case '九':
number = 9;
break;
default:
break;
}
}

@Override
public int intercept() {
return number;
}
}

在 Number 類的構(gòu)造函數(shù)中,先將傳入的字符轉(zhuǎn)換為對(duì)應(yīng)的數(shù)字。在解釋時(shí)將轉(zhuǎn)換后的數(shù)字返回即可。

無(wú)論是加法還是減法,他們都是對(duì)左右兩個(gè)表達(dá)式進(jìn)行操作,所以我們可以將計(jì)算符提取出共同的抽象父類:

abstract class Operator implements Expression {
Expression left;
Expression right;

Operator(Expression left, Expression right) {
this.left = left;
this.right = right;
}
}

在此抽象父類中,我們存入了兩個(gè)變量,表達(dá)計(jì)算符左右兩邊的表達(dá)式。

加法類實(shí)現(xiàn)如下:

class Add extends Operator {

Add(Expression left, Expression right) {
super(left, right);
}

@Override
public int intercept() {
return left.intercept() + right.intercept();
}
}

減法類:

class Sub extends Operator {

Sub(Expression left, Expression right) {
super(left, right);
}

@Override
public int intercept() {
return left.intercept() - right.intercept();
}
}

加法類和減法類都繼承自 Operator 類,在對(duì)他們進(jìn)行解釋時(shí),將左右兩邊表達(dá)式解釋出的值相加或相減即可。

數(shù)字類和計(jì)算符內(nèi)都定義好了,這時(shí)我們只需要再編寫一個(gè)計(jì)算類將他們綜合起來(lái),統(tǒng)一計(jì)算即可。

計(jì)算類:

class Calculator {
int calculate(String expression) {
Stack<Expression> stack = new Stack<>();
for (int i = 0; i < expression.length(); i++) {
char word = expression.charAt(i);
switch (word) {
case '加':
stack.push(new Add(stack.pop(), new Number(expression.charAt(++i))));
break;
case '減':
stack.push(new Sub(stack.pop(), new Number(expression.charAt(++i))));
break;
default:
stack.push(new Number(word));
break;
}
}
return stack.pop().intercept();
}
}

在計(jì)算類中,我們使用棧結(jié)構(gòu)保存每一步操作。遍歷 expression 公式:

  • 遇到數(shù)字則將其壓入棧中;

  • 遇到計(jì)算符時(shí),先將棧頂元素 pop 出來(lái),再和下一個(gè)數(shù)字一起傳入計(jì)算符的構(gòu)造函數(shù)中,組成一個(gè)計(jì)算符公式壓入棧中。

需要注意的是,入棧出棧過(guò)程并不會(huì)執(zhí)行真正的計(jì)算,棧操作只是將表達(dá)式組裝成一個(gè)嵌套的類對(duì)象而已。比如:

  • “一加一”表達(dá)式,經(jīng)過(guò)入棧出棧操作后,生成的對(duì)象是 new Add(new Number('一'), new Number('一'))

  • “二加五減三”表達(dá)式,經(jīng)過(guò)入棧出棧操作后,生成的對(duì)象是 new Sub(new Add(new Number('二'), new Number('五')), new Number('三'))`

最后一步 stack.pop().intercept(),將棧頂?shù)脑貜棾觯瑘?zhí)行 intercept() ,這時(shí)才會(huì)執(zhí)行真正的計(jì)算。計(jì)算時(shí)會(huì)將中文的數(shù)字和運(yùn)算符分別解釋成計(jì)算機(jī)能理解的指令。

測(cè)試類:

public class Client {
@Test
public void test() {
Calculator calculator = new Calculator();
String expression1 = "一加一";
String expression2 = "一加一加一";
String expression3 = "二加五減三";
String expression4 = "七減五加四減一";
String expression5 = "九減五加三減一";
// 輸出:一加一 等于 2
System.out.println(expression1 + " 等于 " + calculator.calculate(expression1));
// 輸出:一加一加一 等于 3
System.out.println(expression2 + " 等于 " + calculator.calculate(expression2));
// 輸出:二加五減三 等于 4
System.out.println(expression3 + " 等于 " + calculator.calculate(expression3));
// 輸出:七減五加四減一 等于 5
System.out.println(expression4 + " 等于 " + calculator.calculate(expression4));
// 輸出:九減五加三減一 等于 6
System.out.println(expression5 + " 等于 " + calculator.calculate(expression5));
}
}

這就是解釋器模式,我們將一句中文的公式解釋給計(jì)算機(jī),然后計(jì)算機(jī)為我們運(yùn)算出了正確的結(jié)果。

分析本例中公式的組成,我們可以發(fā)現(xiàn)幾條顯而易見(jiàn)的性質(zhì):

  • 數(shù)字類不可被拆分,屬于計(jì)算中的最小單元;

  • 加法類、減法類可以被拆分成兩個(gè)數(shù)字(或兩個(gè)公式)加一個(gè)計(jì)算符,他們不是計(jì)算的最小單元。

在解釋器模式中,我們將不可拆分的最小單元稱之為終結(jié)表達(dá)式,可以被拆分的表達(dá)式稱之為非終結(jié)表達(dá)式。

解釋器模式具有一定的拓展性,當(dāng)需要添加其他計(jì)算符時(shí),我們可以通過(guò)添加 Operator 的子類來(lái)完成。但添加后需要按照運(yùn)算優(yōu)先級(jí)修改計(jì)算規(guī)則??梢?jiàn)一個(gè)完整的解釋器模式是非常復(fù)雜的,實(shí)際開(kāi)發(fā)中幾乎沒(méi)有需要自定義解釋器的情況。

解釋器模式有一個(gè)常見(jiàn)的應(yīng)用,在我們平時(shí)匹配字符串時(shí),用到的正則表達(dá)式就是一個(gè)解釋器。正則表達(dá)式中,表示一個(gè)字符的表達(dá)式屬于終結(jié)表達(dá)式,除終結(jié)表達(dá)式外的所有表達(dá)式都屬于非終結(jié)表達(dá)式。

二、迭代器模式

設(shè)想一個(gè)場(chǎng)景:我們有一個(gè)類中存在一個(gè)列表。這個(gè)列表需要提供給外部類訪問(wèn),但我們不希望外部類修改其中的數(shù)據(jù)。

public class MyList {
private List<String> data = Arrays.asList("a", "b", "c");
}

通常來(lái)說(shuō),將成員變量提供給外部類訪問(wèn)有兩種方式:

  • 將此列表設(shè)置為 public 變量;

  • 添加 getData() 方法,返回此列表。

但這兩種方式都有一個(gè)致命的缺點(diǎn),它們無(wú)法保證外部類不修改其中的數(shù)據(jù)。外部類拿到 data 對(duì)象后,可以隨意修改列表內(nèi)部的元素,這會(huì)造成極大的安全隱患。

那么有什么更好的方式嗎?使得外部類只能讀取此列表中的數(shù)據(jù),無(wú)法修改其中的任何數(shù)據(jù),保證其安全性。

分析可知,我們可以通過(guò)提供兩個(gè)方法實(shí)現(xiàn)此效果:

  • 提供一個(gè) String next() 方法,使得外部類可以按照次序,一條一條的讀取數(shù)據(jù);

  • 提供一個(gè) boolean hasNext() 方法,告知外部類是否還有下一條數(shù)據(jù)。

代碼實(shí)現(xiàn)如下:

public class MyList {
private List<String> data = Arrays.asList("a", "b", "c");
private int index = 0;

public String next() {
// 返回?cái)?shù)據(jù)后,將 index 加 1,使得下次訪問(wèn)時(shí)返回下一條數(shù)據(jù)
return data.get(index++);
}

public boolean hasNext() {
return index < data.size();
}
}

客戶端就可以使用一個(gè) while 循環(huán)來(lái)訪問(wèn)此列表了:

public class Client {
@Test
public void test() {
MyList list = new MyList();
// 輸出:abc
while (list.hasNext()) {
System.out.print(list.next());
}
}
}

由于沒(méi)有給外部類暴露 data 成員變量,所以我們可以保證數(shù)據(jù)是安全的。

但這樣的實(shí)現(xiàn)還有一個(gè)問(wèn)題:當(dāng)遍歷完成后,hasNext() 方法就會(huì)一直返回 false,無(wú)法再一次遍歷了,所以我們必須在一個(gè)合適的地方把 index 重置成 0。

在哪里重置比較合適呢?實(shí)際上,使用 next() 方法和 hasNext() 方法來(lái)遍歷列表是一個(gè)完全通用的方法,我們可以為其創(chuàng)建一個(gè)接口,取名為 Iterator,Iterator 的意思是迭代器,迭代的意思是重復(fù)反饋,這里是指我們依次遍歷列表中的元素。

public interface Iterator {

boolean hasNext();

String next();
}

然后在 MyList 類中,每次遍歷時(shí)生成一個(gè)迭代器,將 index 變量放到迭代器中。由于每個(gè)迭代器都是新生成的,所以每次遍歷時(shí)的 index 自然也就被重置成 0 了。代碼如下:

public class MyList {
private List<String> data = Arrays.asList("a", "b", "c");

// 每次生成一個(gè)新的迭代器,用于遍歷列表
public Iterator iterator() {
return new Itr();
}

private class Itr implements Iterator {
private int index = 0;

@Override
public boolean hasNext() {
return index < data.size();
}

@Override
public String next() {
return data.get(index++);
}
}
}

客戶端訪問(wèn)此列表的代碼修改如下:

public class Client {
@Test
public void test() {
MyList list = new MyList();
// 獲取迭代器,用于遍歷列表
Iterator iterator = list.iterator();
// 輸出:abc
while (iterator.hasNext()) {
System.out.print(iterator.next());
}
}
}

這就是迭代器模式,《設(shè)計(jì)模式》一書中將其定義如下:

迭代器模式(Iterator Pattern):提供一種方法訪問(wèn)一個(gè)容器對(duì)象中各個(gè)元素,而又不需暴露該對(duì)象的內(nèi)部細(xì)節(jié)。

迭代器模式的核心就在于定義出 next() 方法和 hasNext() 方法,讓外部類使用這兩個(gè)方法來(lái)遍歷列表,以達(dá)到隱藏列表內(nèi)部細(xì)節(jié)的目的。

事實(shí)上,Java 已經(jīng)為我們內(nèi)置了 Iterator 接口,源碼中使用了泛型使得此接口更加的通用:

public interface Iterator<E> {
boolean hasNext();
E next();
}

并且,本例中使用的迭代器模式是仿照 ArrayList 的源碼實(shí)現(xiàn)的,ArrayList 源碼中使用迭代器模式的部分代碼如下:

public class ArrayList<E> {
...

public Iterator<E> iterator() {
return new Itr();
}

private class Itr implements Iterator<E> {
protected int limit = ArrayList.this.size;
int cursor;

public boolean hasNext() {
return cursor < limit;
}

public E next() {
...
}
}
}

我們平時(shí)常用的 for-each 循環(huán),也是迭代器模式的一種應(yīng)用。在 Java 中,只要實(shí)現(xiàn)了 Iterable 接口的類,都被視為可迭代訪問(wèn)的。Iterable 中的核心方法只有一個(gè),也就是剛才我們?cè)?MyList 類中實(shí)現(xiàn)過(guò)的用于獲取迭代器的 iterator() 方法:

public interface Iterable<T> {
Iterator<T> iterator();
}

只要我們將 MyList 類修改為繼承此接口,便可以使用 for-each 來(lái)迭代訪問(wèn)其中的數(shù)據(jù)了:

public class MyList implements Iterable<String> {
private List<String> data = Arrays.asList("a", "b", "c");

@NonNull
@Override
public Iterator<String> iterator() {
// 每次生成一個(gè)新的迭代器,用于遍歷列表
return new Itr();
}

private class Itr implements Iterator<String> {
private int index = 0;

@Override
public boolean hasNext() {
return index < data.size();
}

@Override
public String next() {
return data.get(index++);
}
}
}

客戶端使用 for-each 訪問(wèn):

public class Client {
@Test
public void test() {
MyList list = new MyList();
// 輸出:abc
for (String item : list) {
System.out.print(item);
}
}
}

這就是迭代器模式?;旧厦糠N語(yǔ)言都會(huì)在源碼層面為所有列表提供迭代器,我們只需要直接拿來(lái)用即可,這是一個(gè)比較簡(jiǎn)單又很常用的設(shè)計(jì)模式。

三、中介者模式

顧名思義,中介這個(gè)名字對(duì)我們來(lái)說(shuō)實(shí)在太熟悉了。平時(shí)走在上班路上就會(huì)經(jīng)常見(jiàn)到各種房產(chǎn)中介。他們的工作就是使得買家與賣家不需要直接打交道,只需要分別與中介打交道,就可以完成交易,用計(jì)算機(jī)術(shù)語(yǔ)來(lái)說(shuō)就是減少了耦合度。

當(dāng)類與類之間的關(guān)系呈現(xiàn)網(wǎng)狀時(shí),引入一個(gè)中介者,可以使類與類之間的關(guān)系變成星形。將每個(gè)類與多個(gè)類的耦合關(guān)系簡(jiǎn)化為每個(gè)類與中介者的耦合關(guān)系。

舉個(gè)例子,在我們打麻將時(shí),每?jī)蓚€(gè)人之間都可能存在輸贏關(guān)系。如果每筆交易都由輸家直接發(fā)給贏家,就會(huì)出現(xiàn)一種網(wǎng)狀耦合關(guān)系。

我們用程序來(lái)模擬一下這個(gè)過(guò)程。

玩家類:

class Player {
// 初始資金 100 元
public int money = 100;

public void win(Player player, int money) {
// 輸錢的人扣減相應(yīng)的錢
player.money -= money;
// 自己的余額增加
this.money += money;
}
}

此類中有一個(gè) money 變量,表示自己的余額。當(dāng)自己贏了某位玩家的錢時(shí),調(diào)用 win 方法修改輸錢的人和自己的余額。

需要注意的是,我們不需要輸錢的方法,因?yàn)樵?win 方法中,已經(jīng)將輸錢的人對(duì)應(yīng)余額扣除了。

客戶端代碼:

public class Client {
@Test
public void test() {
Player player1 = new Player();
Player player2 = new Player();
Player player3 = new Player();
Player player4 = new Player();
// player1 贏了 player3 5 元
player1.win(player3, 5);
// player2 贏了 player1 10 元
player2.win(player1, 10);
// player2 贏了 player4 10 元
player2.win(player4, 10);
// player4 贏了 player3 7 元
player4.win(player3, 7);

// 輸出:四人剩余的錢:105,120,88,97
System.out.println("四人剩余的錢:" + player1.money + "," + player2.money + "," + player3.money + "," + player4.money);
}
}

在客戶端中,每?jī)晌煌婕倚枰M(jìn)行交易時(shí),都會(huì)增加程序耦合度,相當(dāng)于每位玩家都需要和其他所有玩家打交道,這是一種不好的做法。

此時(shí),我們可以引入一個(gè)中介類——微信群,只要輸家將自己輸?shù)腻X發(fā)到微信群里,贏家從微信群中領(lǐng)取對(duì)應(yīng)金額即可。網(wǎng)狀的耦合結(jié)構(gòu)就變成了星形結(jié)構(gòu):

此時(shí),微信群就充當(dāng)了一個(gè)中介者的角色,由它來(lái)負(fù)責(zé)與所有人進(jìn)行交易,每個(gè)玩家只需要與微信群打交道即可。

微信群類:

class Group {
public int money;
}

此類中只有一個(gè) money 變量表示群內(nèi)的余額。

玩家類修改如下:

class Player {
public int money = 100;
public Group group;

public Player(Group group) {
this.group = group;
}

public void change(int money) {
// 輸了錢將錢發(fā)到群里 或 在群里領(lǐng)取自己贏的錢
group.money += money;
// 自己的余額改變
this.money += money;
}
}

玩家類中新增了一個(gè)構(gòu)造方法,在構(gòu)造方法中將中介者傳進(jìn)來(lái)。每當(dāng)自己有輸贏時(shí),只需要將錢發(fā)到群里或者在群里領(lǐng)取自己贏的錢,然后修改自己的余額即可。

客戶端代碼對(duì)應(yīng)修改如下:

public class Client {
@Test
public void test(){
Group group = new Group();
Player player1 = new Player(group);
Player player2 = new Player(group);
Player player3 = new Player(group);
Player player4 = new Player(group);
// player1 贏了 5 元
player1.change(5);
// player2 贏了 20 元
player2.change(20);
// player3 輸了 12 元
player3.change(-12);
// player4 輸了 3 元
player4.change(-3);

// 輸出:四人剩余的錢:105,120,88,97
System.out.println("四人剩余的錢:" + player1.money + "," + player2.money + "," + player3.money + "," + player4.money);
}
}

可以看到,通過(guò)引入中介者,客戶端的代碼變得更加清晰了。大家不需要再互相打交道,所有交易通過(guò)中介者完成即可。

事實(shí)上,這段代碼還存在一點(diǎn)不足。因?yàn)槲覀兒雎粤艘粋€(gè)前提:微信群里的錢不可以為負(fù)數(shù)。也就是說(shuō),輸家必須先將錢發(fā)到微信群內(nèi),贏家才能去微信群里領(lǐng)錢。這個(gè)功能可以用我們?cè)?中學(xué)到的 wait/notify 機(jī)制完成,與中介者模式無(wú)關(guān),故這里不再給出相關(guān)代碼,感興趣的讀者可以自行實(shí)現(xiàn)。

總而言之,中介者模式就是用于將類與類之間的 多對(duì)多關(guān)系 簡(jiǎn)化成 多對(duì)一、一對(duì)多關(guān)系 的設(shè)計(jì)模式,它的定義如下:

中介者模式(Mediator Pattern):定義一個(gè)中介對(duì)象來(lái)封裝一系列對(duì)象之間的交互,使原有對(duì)象之間的耦合松散,且可以獨(dú)立地改變它們之間的交互。

中介者模式的缺點(diǎn)也很明顯:由于它將所有的職責(zé)都移到了中介者類中,也就是說(shuō)中介類需要處理所有類之間的協(xié)調(diào)工作,這可能會(huì)使中介者演變成一個(gè)超級(jí)類。所以使用中介者模式時(shí)需要權(quán)衡利弊。

四、備忘錄模式

備忘錄模式最常見(jiàn)的實(shí)現(xiàn)莫過(guò)于游戲中的存檔、讀檔功能了,通過(guò)存檔、讀檔,使得我們可以隨時(shí)恢復(fù)到之前的狀態(tài)。

當(dāng)我們?cè)谕嬗螒驎r(shí),打大 Boss 之前,通常會(huì)將自己的游戲進(jìn)度存檔保存,以防打不過(guò) Boss 的話,還能重新讀檔恢復(fù)狀態(tài)。

玩家類:

class Player {
// 生命值
private int life = 100;
// 魔法值
private int magic = 100;

public void fightBoss() {
life -= 100;
magic -= 100;
if (life <= 0) {
System.out.println("壯烈犧牲");
}
}

public int getLife() {
return life;
}

public void setLife(int life) {
this.life = life;
}

public int getMagic() {
return magic;
}

public void setMagic(int magic) {
this.magic = magic;
}
}

我們?yōu)橥婕叶x了兩個(gè)屬性:生命值和魔法值。其中有一個(gè) fightBoss() 方法,每次打 Boss 都會(huì)扣減 100 點(diǎn)體力。如果生命值小于等于 0,則提示用戶已“壯烈犧牲”。

客戶端實(shí)現(xiàn)如下:

public class Client {
@Test
public void test() {
Player player = new Player();
// 存檔
int savedLife = player.getLife();
int savedMagic = player.getMagic();

// 打 Boss,打不過(guò),壯烈犧牲
player.fightBoss();

// 讀檔,恢復(fù)到打 Boss 之前的狀態(tài)
player.setLife(savedLife);
player.setMagic(savedMagic);
}
}

客戶端中,我們?cè)?fightBoss() 之前,先去存檔,把自己當(dāng)前的生命值和魔法值保存起來(lái)。打完 Boss 發(fā)現(xiàn)自己犧牲之后,再回去讀檔,將自己恢復(fù)到打 Boss 之前的狀態(tài)。

這就是備忘錄模式......嗎?不完全是,事情并沒(méi)有這么簡(jiǎn)單。

還記得我們?cè)谠湍J街?,買的那杯和周杰倫一模一樣的奶茶嗎?開(kāi)始時(shí),為了克隆一杯奶茶,我們將奶茶的各個(gè)屬性分別賦值成和周杰倫買的那杯奶茶一樣。但這樣存在一個(gè)弊端:我們不可能為一千個(gè)粉絲寫一千份挨個(gè)賦值操作。所以最終我們?cè)谀滩桀悆?nèi)部實(shí)現(xiàn)了 Cloneable 接口,定義了 clone() 方法,來(lái)實(shí)現(xiàn)一行代碼拷貝所有屬性。

備忘錄模式也應(yīng)該采取類似的做法。我們不應(yīng)該采用將單個(gè)屬性挨個(gè)存取的方式來(lái)進(jìn)行讀檔、存檔。更好的做法是將存檔、讀檔交給需要存檔的類內(nèi)部去實(shí)現(xiàn)。

新建備忘錄類:

class Memento {
int life;
int magic;

Memento(int life, int magic) {
this.life = life;
this.magic = magic;
}
}

在此類中,管理需要存檔的數(shù)據(jù)。

玩家類中,通過(guò)備忘錄類實(shí)現(xiàn)存檔、讀檔:

class Player {
...

// 存檔
public Memento saveState() {
return new Memento(life, magic);
}

// 讀檔
public void restoreState(Memento memento) {
this.life = memento.life;
this.magic = memento.magic;
}
}

客戶端類對(duì)應(yīng)修改如下:

public class Client {
@Test
public void test() {
Player player = new Player();
// 存檔
Memento memento = player.saveState();

// 打 Boss,打不過(guò),壯烈犧牲
player.fightBoss();

// 讀檔
player.restoreState(memento);
}
}

這才是完整的備忘錄模式。這個(gè)設(shè)計(jì)模式的定義如下:

備忘錄模式:在不破壞封裝的條件下,通過(guò)備忘錄對(duì)象存儲(chǔ)另外一個(gè)對(duì)象內(nèi)部狀態(tài)的快照,在將來(lái)合適的時(shí)候把這個(gè)對(duì)象還原到存儲(chǔ)起來(lái)的狀態(tài)。

備忘錄模式的優(yōu)點(diǎn)是:

  • 給用戶提供了一種可以恢復(fù)狀態(tài)的機(jī)制,可以使用戶能夠比較方便的回到某個(gè)歷史的狀態(tài)

  • 實(shí)現(xiàn)了信息的封裝,使得用戶不需要關(guān)心狀態(tài)的保存細(xì)節(jié)

缺點(diǎn)是:

  • 消耗資源,如果類的成員變量過(guò)多,勢(shì)必會(huì)占用比較大的資源,而且每一次保存都會(huì)消耗一定的內(nèi)存。

總體而言,備忘錄模式是利大于弊的,所以許多程序都為用戶提供了備份方案。比如 IDE 中,用戶可以將自己的設(shè)置導(dǎo)出成 zip,當(dāng)需要恢復(fù)設(shè)置時(shí),再將導(dǎo)出的 zip 文件導(dǎo)入即可。這個(gè)功能內(nèi)部的原理就是備忘錄模式。

本文作者:Alpinist Wang

聲明:本文歸 “力扣” 版權(quán)所有。文章封面圖和文中部分圖片來(lái)源于網(wǎng)絡(luò),如有侵權(quán)聯(lián)系刪除。

本站僅提供存儲(chǔ)服務(wù),所有內(nèi)容均由用戶發(fā)布,如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊舉報(bào)。
打開(kāi)APP,閱讀全文并永久保存 查看更多類似文章
猜你喜歡
類似文章
第17章 行為型模式
簡(jiǎn)說(shuō)設(shè)計(jì)模式——解釋器模式
通俗易懂設(shè)計(jì)模式解析——解釋器模式
折騰Java設(shè)計(jì)模式之解釋器模式
設(shè)計(jì)模式之解釋器模式(C++)
迭代器模式(Iterator)解析例子
更多類似文章 >>
生活服務(wù)
分享 收藏 導(dǎo)長(zhǎng)圖 關(guān)注 下載文章
綁定賬號(hào)成功
后續(xù)可登錄賬號(hào)暢享VIP特權(quán)!
如果VIP功能使用有故障,
可點(diǎn)擊這里聯(lián)系客服!

聯(lián)系客服