本篇介紹裝飾者模式(Decorator Pattern)。
裝飾者模式屬於GoF中的Structural Pattern。
裝飾者模式的使用時機為,當你想要增加類別的功能,但不想修改到現有類別的程式碼;或是要動態地添加新功能於類別上;或是該類別搭配不同的特性會有多種組合的情況。
在Java中使用裝飾者模式的典型例子為Java IO,其抽像類別InputStream
的抽象方法read()
分別由不同的裝飾者類別如BufferedInputStream
,ObjectInputStream
,DataInputStream
等來實作各種讀取串流(stream)的方法並提供額外的功能。而每一裝飾類別的建構式皆以其父抽像類別InputStream
為輸入參數。
裝飾者模式的好處是可以動態地為某個類別提供多種額外的功能。如果以透過繼承的方式就沒有這樣的彈性,因為你必須一開始定義好擴增功能的子類別,如果可增加的功能一多或有多種組合,那就得建立非常多的子類別而難以管理。又或不透過繼承而透過在原有的介面新增方法,這樣又造成所有繼承的類別都要實作該方法。
例如有一個介面Weapon
,提供基本功能如下
Weapon
public interface Weapon {
public String getName(); // 取得武器名稱
public int getHitPoint(); // 取得攻擊點數
public void attack(); // 攻擊
}
下面為實作Weapon
的具體類別Sword
。
Sword
public class Sword implements Weapon {
private String name = "劍";
private int hitPoint = 10;
@Override
public String getName() {
return name;
}
@Override
public int getHitPoint() {
return hitPoint;
}
@Override
public void attack() {
System.out.println(name + "造成" + hitPoint + "點傷害");
}
}
如果今天要有提供火屬性的劍,並提供特殊效果及額外的攻擊點數,若以繼承的方式來實現則新增一個FireSword
類別
FireSword
public class FireSword extends Sword {
@Override
public String getName() {
return "火" + super.getName();
}
@Override
public int getHitPoint() {
return 3 + hitPoint;
}
@Override
public void attack() {
System.out.println("火" + name + "造成" + (3 + hitPoint) + "點傷害");
special();
}
@Override
public void special() {
System.out.println("特殊效果:火焰造成 5點傷害");
}
}
然而上面的問題是,提供的special()
方法必須先在Weapon
介面中添加,這樣就修改到原本的程式碼。更大的問題是,如果今天不止只有一個屬性,而是有多種屬性的組合,例如火加聖屬性,或火加暗屬性,或火加聖加毒之類的情況,若以同樣的繼承手段則要新增的子類別就太多了,且有很多部份重覆的情況變得難以管理,而且在程式中無法依情況動態的添加功能。
若改以裝飾者模式來添加額外的功能則可以避免上述的狀況,我們可以以組件的方式隨意添加組合新的特性或功能,而不用更動程式或繼承原有的類別。
裝飾飾者模式有幾個關鍵的原件如下:
- Component:原有類別實作的介面,定義標準方法。
- ConcreteComponent:原有類別
- Decorator:裝飾者類別,繼承原有類別的介面。
- ComcreteDecorator:裝飾者類別的實作。
裝飾者模式的特徵中除了以上必要的原件外,裝飾者(Decorator)及其子類別(ComcreteDecorator)的建構式皆以Component介面為傳入參數來保有原有的類別。
在上例中,Weapon
即為Component,Sword
即為ComcreteComponent,而我們要新增額外的火屬性或聖屬性及附加的特殊效果,所以先設定一個裝飾者類別WeaponDecorator
如下。
WeaponDecorator
public abstract class WeaponDecorator implements Weapon {
protected String name;
protected int hitPoint;
protected Weapon weapon;
public WeaponDecorator(String name, int hitPoint, Weapon weapon) {
this.name = name;
this.hitPoint = hitPoint;
this.weapon = weapon;
}
@Override
public String getName() {
return name + weapon.getName();
}
@Override
public int getHitPoint() {
return hitPoint + weapon.getHitPoint();
}
protected void special() {
if (weapon instanceof WeaponDecorator) {
WeaponDecorator deco = (WeaponDecorator) weapon;
deco.special();
}
}
public void attack() {
System.out.println(getName() + "造成" + (hitPoint + weapon.getHitPoint()) + "點傷害");
this.special();
}
public Weapon getWeapon() {
return this.weapon;
}
}
在WeaponDecorator
加入了special()
方法來提供額外的能力。
接著提供具體的裝飾類別火屬性FirePower
及聖屬性HolyPower
,皆繼承抽像類別WeaponDecorator
,且同樣地以Weapon
為建構式的參數並保存。
FirePower
public class FirePower extends WeaponDecorator {
public FirePower(Weapon weapon) {
super("火", 5, weapon);
}
@Override
protected void special() {
super.special();
System.out.println("特殊效果:火焰造成 5點傷害");
}
}
HolyPower
public class HolyPower extends WeaponDecorator {
public HolyPower(Weapon weapon) {
super("聖", 3, weapon);
}
@Override
protected void special() {
super.special();
System.out.println("特殊效果:回復5點HP");
}
}
實作一個使用武器的戰士類別Warrior
Warrior
public class Warrior {
String name;
Weapon weapon;
public Warrior(String name, Weapon weapon) {
this.name = name;
this.weapon = weapon;
}
public void attack() {
System.out.print(name + "使用");
weapon.attack();
System.out.print("\n");
}
public Weapon getWeapon() {
return weapon;
}
public void setWeapon(Weapon weapon) {
this.weapon = weapon;
}
}
測試如下
public class App {
public static void main(String[] args) {
Weapon sword = new Sword();
Weapon fireSword = new FirePower(sword); // 在劍上附加火屬性
Weapon holySword = new HolyPower(sword); // 在劍上附加聖屬性
Weapon holyFireSword = new HolyPower(fireSword); // 在火箭上附加聖屬性
Warrior warrior = new Warrior("あああああ ", sword); //建立一個戰士並配與普通的劍
warrior.attack(); // 攻擊
warrior.setWeapon(fireSword); // 換成火劍
warrior.attack();
warrior.setWeapon(holySword); // 換成聖劍
warrior.attack();
warrior.setWeapon(holyFireSword); // 換成聖火劍
warrior.attack();
}
}
印出結果如下
あああああ 使用劍造成10點傷害 あああああ 使用火劍造成15點傷害 特殊效果:火焰造成 5點傷害 あああああ 使用聖劍造成13點傷害 特殊效果:回復5點HP あああああ 使用聖火劍造成18點傷害 特殊效果:火焰造成 5點傷害 特殊效果:回復5點HP
裝飾者模式透過裝飾類別將原本的功能包裹起來,所以在呼叫原有的功能時,會層層向內執行裝飾類所包裹的功能,類似下面的感覺
+-------------------------+
| HolyPower |
| +-----------------+ |
| | FirePower | |
| | +---------+ | |
| | | Sword | | |
| | | | | |
| | +---------+ | |
| | | |
| +-----------------+ |
| |
+-------------------------+
參考:
沒有留言:
張貼留言