一個(gè)旅館提供各種飲料(Beverage), 比如有HouseBlend,DarkRoast等,每個(gè)飲料還有很多配料比如Milk,Soy等。如何寫一個(gè)程序能夠方便的輸出每種飲料的價(jià)格呢?(包括飲料+配料)
最笨的方法如下:也就是每種飲料,每種飲料加配料的組合,都寫一個(gè)類。

package javaapplication33;
public class Main {
public static void main(String[] args) {
Beverage b=new HouseBlendWithMilk();
System.out.println(b.getDescription());
System.out.println(b.cost());
}
}
abstract class Beverage {
public abstract String getDescription();
public abstract int cost();
}
class HouseBlend extends Beverage {
@Override
public String getDescription() {
return "A cup of HouseBlend";
}
@Override
public int cost() {
return 10;
}
}
class DarkRoast extends Beverage {
@Override
public String getDescription() {
return "A cup of DarkRoast";
}
@Override
public int cost() {
return 8;
}
}
class HouseBlendWithMilk extends Beverage{
@Override
public String getDescription() {
return "A cup of HouseBlend with a soap of milk.";
}
@Override
public int cost() {
return 12;
}
}
問(wèn)題來(lái)了,如果飲料及其配料的排列組合有20種,30種,這很可能。那么是不是就要繼承Beverage寫上20,30個(gè)類呢?這倒不是關(guān)鍵,比如萬(wàn)一Milk的價(jià)格變動(dòng)了,那么所有有關(guān)Milk的類都要把cost()方法重寫。。。。這才恐怖呢。原因是我們沒(méi)有把變化的部分封裝起來(lái)。
OK,我們開(kāi)始封裝。為方便起見(jiàn),我們?nèi)园阎饕娘嬃?/span>HouseBlend, DarkRoast等寫成父類Beverage的子類。我們把造成變化的配料Milk,Soy等把它們封裝起來(lái),封裝到哪?封裝到父類里面去。看看下面這樣行不行:

package javaapplication33;
public class Main {
public static void main(String[] args) {
Beverage b = new HouseBlend();
b.setMilk(true); //增加一個(gè)配料
System.out.println(b.cost());
b.setSoy(true);
System.out.println(b.getDescription());
System.out.println(b.cost());
b.setMilk(false);
System.out.println(b.cost());
b.setSoy(false);
System.out.println(b.cost());
System.out.println(b.getDescription());
}
}
abstract class Beverage {
int MILKPRICE = 2;
int SOYPRICE = 3;
public boolean milk;
public boolean soy;
int cost;
String description;
public void setMilk(boolean b) {
milk = b;
}
public void setSoy(boolean b) {
soy = b;
}
public int cost() {
cost = 0;
if (milk) {
cost += MILKPRICE;
}
if (soy) {
cost += SOYPRICE;
}
return cost;
}
public String getDescription() {
description = "";
if (milk) {
description += " with a soap of milk";
}
if (soy) {
description += " with a bottle of soy";
}
return description;
}
}
class HouseBlend extends Beverage {
@Override
public String getDescription() {
return "a cup of HouseBlend" + super.getDescription();
}
@Override
public int cost() {
return super.cost() + 10;
}
}
class DarkRoast extends Beverage {
@Override
public String getDescription() {
return "a cup of DarkRoast" + super.getDescription();
}
@Override
public int cost() {
return super.cost() + 8;
}
}
這樣,每當(dāng)我需要一個(gè)飲料加幾個(gè)配料時(shí),僅需要new這個(gè)飲料,再set配料即可。貌似這個(gè)程序沒(méi)問(wèn)題了。
貌似!
(1) 當(dāng)配料的價(jià)格改動(dòng)時(shí),Beverage這個(gè)類需要改動(dòng)。設(shè)計(jì)模式的原則是在提供擴(kuò)展的同時(shí),盡可能不改動(dòng)原有的程序的。
(2) 如果新增,或者減少某幾種配料,同理,Beverage仍需改動(dòng)。
(3) 如果我們要雙份的Milk呢?
崩潰!
以上,第一種思路是把所有的排列組合都寫成子類,當(dāng)一個(gè)配料變動(dòng),會(huì)導(dǎo)致所有相關(guān)的組合都要變動(dòng)。
第二種思路是把飲料寫成子類,配料的設(shè)置封裝到父類中去,當(dāng)配料變動(dòng)時(shí),會(huì)導(dǎo)致原有的父類變動(dòng),并且無(wú)法同時(shí)提供兩份同樣的配料。
其實(shí)當(dāng)?shù)谝环N思路被否定掉的時(shí)候就有一種沖動(dòng),就是把飲料和配料都寫成子類,繼承抽象的父類Beverage。這個(gè)其實(shí)很容易做到。只是主程序在調(diào)用時(shí),比如我要一個(gè)HouseBlend配上Milk的時(shí)候,我該怎么生成呢?new一個(gè)HouseBlend,再new一個(gè)Milk,然后呢?怎么讓它打印出“A Houseblend with a cup of milk”這樣,并且算價(jià)格的時(shí)候直接兩者的cost就能疊加呢?
我們想到了在讀寫文件操作時(shí)的樣子: new XXX(new XXX(new XXXXX())); 如果飲料加上配料可以這樣生成,豈不是全都解決了?
package javaapplication32;
public class Main {
public static void main(String[] args) {
Baverage b = new Milk(new HouseBland(new Soy()));
System.out.println(b.getDescription());
System.out.println(b.getCost());
}
}
abstract class Baverage {
abstract String getDescription();
abstract int getCost();
}
class HouseBland extends Baverage {
Baverage baverage;
HouseBland() {
}
HouseBland(Baverage baverage) {
this.baverage = baverage;
}
@Override
String getDescription() {
if (baverage != null) {
return "A cup of HouseBland" + baverage.getDescription();
}
else {
return "A cup of HouseBland";
}
}
@Override
int getCost() {
if (baverage != null) {
return 10 + baverage.getCost();
}
else {
return 10;
}
}
}
class Milk extends Baverage {
Baverage baverage;
Milk() {
}
Milk(Baverage baverage) {
this.baverage = baverage;
}
@Override
String getDescription() {
if (baverage != null) {
return baverage.getDescription() + " And a soap Milk";
}
else {
return " And a soap Milk";
}
}
@Override
int getCost() {
if (baverage != null) {
return baverage.getCost() + 5;
}
else {
return 5;
}
}
}
class Soy extends Baverage {
Baverage baverage;
Soy() {
}
Soy(Baverage baverage) {
this.baverage = baverage;
}
@Override
String getDescription() {
if (baverage != null) {
return baverage.getDescription() + " And a bottle of Soy";
}
else {
return " And a bottle of Soy";
}
}
@Override
int getCost() {
if (baverage != null) {
return baverage.getCost() + 7;
}
else {
return 7;
}
}
}
問(wèn)題解決了?可不可以更完美呢?我們發(fā)現(xiàn)每一個(gè)子類里面都用了大量的判斷語(yǔ)句
if (baverage != null) {…..} else {…..}
幾乎都是一樣的,為什么每個(gè)都要寫呢?原因是我們沒(méi)有人為的規(guī)定Baverage b = new Milk(new HouseBland(new Soy()));這個(gè)語(yǔ)句的順序。是“配料(new 飲料())” 還是“飲料(new 配料)”?如果我們規(guī)定一下順序的話,就有很多前面的if判斷語(yǔ)句不需要反復(fù)寫。
這個(gè)改進(jìn)看起來(lái)好像不重要,但是,在實(shí)際工作中,像HouseBlend這樣的類就像是原先寫好的類,或者幾乎長(zhǎng)時(shí)間不會(huì)改變的類;而想配料Milk這樣的類,就如同經(jīng)常改動(dòng)或者新增進(jìn)去的類。也就是說(shuō)前者不怎么改動(dòng),而后者會(huì)經(jīng)常變。那么我們就要盡量保證前者的穩(wěn)定和簡(jiǎn)單(越簡(jiǎn)單越不易出錯(cuò))。
下面的程序,我們刪除了HouseBlend中的繁雜的判斷語(yǔ)句,在主函數(shù)生成飲料加配料時(shí),我們要保證“new 配料(new 配料(….(new 飲料())))的模式(先配料后飲料)。(因?yàn)橐粋€(gè)飲料可以配多個(gè)配料而一個(gè)配料不可以配多個(gè)飲料)
源代碼如下:
package javaapplication32;
public class Main {
public static void main(String[] args) {
Baverage b = new Milk(new Soy(new HouseBlend()));
System.out.println(b.getDescription());
System.out.println(b.getCost());
}
}
abstract class Baverage {
abstract String getDescription();
abstract int getCost();
}
class HouseBlend extends Baverage {
@Override
String getDescription() {
return "A cup of HouseBland";
}
@Override
int getCost() {
return 10;
}
}
class Milk extends Baverage {
Baverage baverage;
Milk() {
}
Milk(Baverage baverage) {
this.baverage = baverage;
}
@Override
String getDescription() {
if (baverage != null) {
return baverage.getDescription() + " And a soap Milk";
}
else {
return " And a soap Milk";
}
}
@Override
int getCost() {
if (baverage != null) {
return baverage.getCost() + 5;
}
else {
return 5;
}
}
}
class Soy extends Baverage {
Baverage baverage;
Soy() {
}
Soy(Baverage baverage) {
this.baverage = baverage;
}
@Override
String getDescription() {
if (baverage != null) {
return baverage.getDescription() + " And a bottle of Soy";
}
else {
return " And a bottle of Soy";
}
}
@Override
int getCost() {
if (baverage != null) {
return baverage.getCost() + 7;
}
else {
return 7;
}
}
}
其實(shí)我們還沒(méi)揭示Decorator pattern的核心含義:配料跟主料其實(shí)不是一回事,但是為了實(shí)現(xiàn)主料的新功能,又要保持主料的穩(wěn)定,我們就用配料繼承主料,或者繼承主料的父函數(shù),目的就是獲得相通的類型(type match)。
這樣,我們可以在其他程序調(diào)用時(shí),隨時(shí)給主料添加配料的新功能。這就是composition組合的魅力!
最后,我們對(duì)上面的程序做一下最終的改進(jìn),原因是由于在類的層次設(shè)計(jì)的時(shí)候,我們沒(méi)有區(qū)分飲料和配料之間的關(guān)系,因?yàn)樗鼈兌计叫械睦^承了同一個(gè)抽象類。在Decorator pattern里面,往往會(huì)出現(xiàn)這樣的情況:就是飲料的種類很穩(wěn)定,而配料的種類卻很繁雜。為了讓程序看上去更清晰(一眼就能看出誰(shuí)是主,誰(shuí)是配),我們用特定的一個(gè)抽象類繼承原先的父類,再讓所有的配料繼承該抽象類。

package javaapplication32;
public class Main {
public static void main(String[] args) {
Baverage b = new Milk(new Soy(new HouseBlend()));
System.out.println(b.getDescription());
System.out.println(b.getCost());
}
}
abstract class Baverage {
abstract String getDescription();
abstract int getCost();
}
class HouseBlend extends Baverage {
@Override
String getDescription() {
return "A cup of HouseBland";
}
@Override
int getCost() {
return 10;
}
}
abstract class Decorator extends Baverage {
abstract String getDescription();
abstract int getCost();
}
class Milk extends Decorator {
Baverage baverage;
Milk() {
}
Milk(Baverage baverage) {
this.baverage = baverage;
}
@Override
String getDescription() {
if (baverage != null) {
return baverage.getDescription() + " And a soap Milk";
}
else {
return " And a soap Milk";
}
}
@Override
int getCost() {
if (baverage != null) {
return baverage.getCost() + 5;
}
else {
return 5;
}
}
}
class Soy extends Decorator {
Baverage baverage;
Soy() {
}
Soy(Baverage baverage) {
this.baverage = baverage;
}
@Override
String getDescription() {
if (baverage != null) {
return baverage.getDescription() + " And a bottle of Soy";
}
else {
return " And a bottle of Soy";
}
}
@Override
int getCost() {
if (baverage != null) {
return baverage.getCost() + 7;
}
else {
return 7;
}
}
}
其實(shí)java API里面有很多使用Decorator Pattern的例子,比如讀寫文件:
