今天來講解 裝飾者模式 (Decorator Pattern) CSS就很像裝飾者
裝飾者模式的核心思想是:在不改變原有物件結構的情況下,動態地、漸進式地為一個物件增加額外的功能或職責。
裝飾者模式的核心思想是:在不改變原有物件結構的情況下,動態地、漸進式地為一個物件增加額外的功能或職責。
它就像一層層地把功能「包」上去,而不是透過繼承來產生一個功能更強大的子類別。
現在,我們來看看 CSS:
原始物件 (Component):
在網頁中,一個最單純的 HTML 元素,例如 <div>
或 <p>
,就是我們的原始物件。它有自己最基本的功能和樣貌。
<div>這是一個基本的區塊</div>
裝飾者 (Decorators):
CSS 的每一條規則,例如 border
, background-color
, padding
, box-shadow
,都可以看作是一個「裝飾者」。每一條規則都為原始的 <div>
增加了一項新的「外觀職責」。
動態組合 (Dynamic Composition):
最關鍵的地方來了:你可以自由地、動態地將這些 CSS 規則(裝飾者)組合起來應用到 <div>
(原始物件)上。
.my-box {
border: 1px solid black; /* 裝飾者A:加上邊框 */
background-color: #f0f0f0; /* 裝飾者B:加上背景色 */
box-shadow: 5px 5px 10px #888; /* 裝飾者C:加上陰影 */
}
你不需要為了一個「有邊框和背景色的 div」去發明一個新的 HTML 標籤。你只需要將這些裝飾動態地「附加」到原始物件上即可。這種方式遠比繼承(例如,創造一個 <DivWithBorderAndBackground>
類別)要靈活得多。
這個比喻完美地詮釋了裝飾者模式的精髓:保持原始物件的純粹,並透過外部包裝來擴充功能。
裝飾者模式主要用來解決**「類別爆炸 (Class Explosion)」**的問題。
假設我們在做一個飲料店系統。飲料有紅茶、綠茶、咖啡。調味料有牛奶、糖、珍珠。
如果使用繼承,你需要為每種組合都建立一個子類別:
紅茶
加牛奶的紅茶
加糖的紅茶
加牛奶和糖的紅茶
加牛奶和糖和珍珠的紅茶
才兩種飲料、三種調味料,組合就已經多到難以管理了。這就是「類別爆炸」。裝飾者模式透過「組合」優雅地解決了這個問題。
首先,定義一個所有飲料和調味料(裝飾者)都必須遵守的共同介面 Beverage
。
// Component: 所有被裝飾者和裝飾者都必須實現的介面
public interface Beverage {
String getDescription();
double cost();
}
這是我們的基礎飲料,例如濃縮咖啡 Espresso
。
// Concrete Component: 基礎的、被裝飾的物件
public class Espresso implements Beverage {
public String getDescription() {
return "濃縮咖啡";
}
public double cost() {
return 60.0; // 價格
}
}
這是模式的關鍵之一。它是一個抽象類別,它實現了 Beverage
介面,但同時內部又持有一個 Beverage
物件的參考。這就是「包裝」的體現。
// Abstract Decorator: 所有調味料裝飾者的父類別
public abstract class CondimentDecorator implements Beverage {
// 透過組合,持有一個被裝飾的 Beverage 物件
protected Beverage beverage;
public CondimentDecorator(Beverage beverage) {
this.beverage = beverage;
}
// 將 getDescription 的職責委派給被裝飾者
public String getDescription() {
return beverage.getDescription();
}
}
現在我們可以建立具體的調味料,例如「牛奶 (Milk)」。它繼承自 CondimentDecorator
。
// Concrete Decorator: 牛奶
public class Milk extends CondimentDecorator {
public Milk(Beverage beverage) {
super(beverage);
}
@Override
public String getDescription() {
// 在被裝飾者原有的描述基礎上,加上自己的描述
return beverage.getDescription() + ", 加牛奶";
}
@Override
public double cost() {
// 在被裝飾者原有的價格基礎上,加上自己的價格
return beverage.cost() + 15.0;
}
}
// Concrete Decorator: 摩卡
public class Mocha extends CondimentDecorator {
public Mocha(Beverage beverage) {
super(beverage);
}
@Override
public String getDescription() {
return beverage.getDescription() + ", 加摩卡";
}
@Override
public double cost() {
return beverage.cost() + 20.0;
}
}
來看看客戶端如何像堆積木一樣,自由組合出一杯客製化飲料。
public class CoffeeShop {
public static void main(String[] args) {
// 點一杯最基本的濃縮咖啡
Beverage beverage1 = new Espresso();
System.out.println("飲料1: " + beverage1.getDescription() + " NT$" + beverage1.cost());
System.out.println("---------------------------------");
// 現在,我們想做一杯「雙份摩卡加牛奶的濃縮咖啡」
// 1. 先從一杯濃縮咖啡開始
Beverage beverage2 = new Espresso();
// 2. 用 Milk 包裝它
beverage2 = new Milk(beverage2);
// 3. 用 Mocha 包裝它
beverage2 = new Mocha(beverage2);
// 4. 再用 Mocha 包裝一次(雙份摩卡)
beverage2 = new Mocha(beverage2);
System.out.println("飲料2: " + beverage2.getDescription() + " NT$" + beverage2.cost());
// 預期價格: 60 (咖啡) + 15 (牛奶) + 20 (摩卡) + 20 (摩卡) = 115
}
}
飲料1: 濃縮咖啡 NT$60.0
---------------------------------
飲料2: 濃縮咖啡, 加牛奶, 加摩卡, 加摩卡 NT$115.0
正如你所見,我們不需要建立 EspressoWithMilkAndDoubleMocha
這種子類別,而是透過一層層的包裝,動態地組合出了我們想要的功能和價格。