策略模式(Strategy Pattern)
定義了算法族,分別封裝起來,讓它們之間可以相互替換,此模式讓算法的變化獨立於使用算法的客戶。
設計原則
- 找出應用中可能需要變化之處,把它們獨立出來,不要和那些不需要變化的代碼混在一起
- 針對接口編程,而不是針對實現編程
- 多用組合,少用繼承
模擬鴨子游戲
有一款模擬鴨子游戲,遊戲中會出現各種鴨子,一邊游泳,一邊呱呱叫。根據面向對象技術,設計了一個鴨子的超類,讓各種鴨子繼承這個超類。
public abstract class Duck {
public void quack() {
System.out.println("呱呱叫");
}
public void swim() {
System.out.println("游泳");
}
public abstract void display();
}
class MallardDuck extends Duck {
@Override
public void display(){
//外觀是綠頭
System.out.println("外觀是綠頭");
}
}
public class RedheadDuck extends Duck {
@Override
public void display() {
//外觀是紅頭
System.out.println("外觀是紅頭");
}
}
測試代碼:
public class Main {
public static void main(String[] args) {
MallardDuck mallardDuck = new MallardDuck();
mallardDuck.display();
mallardDuck.quack();
mallardDuck.swim();
RedheadDuck redheadDuck = new RedheadDuck();
redheadDuck.display();
redheadDuck.quack();
redheadDuck.swim();
}
}
運行結果:
外觀是綠頭
呱呱叫
游泳
外觀是紅頭
呱呱叫
游泳
讓鴨子會飛
某一天,增加了一個需求,需要讓鴨子會飛。於是在 Duck 類中增加了一個 fly() 方法。
可怕的問題來了,某天增加了一種橡皮鴨(不會飛、會吱吱叫)、一種誘餌鴨(不會飛、不會叫)。
由於繼承的原因,橡皮鴨、誘餌鴨都變成了會飛、會呱呱叫。解決辦法就是在橡皮鴨、誘餌鴨類中覆蓋 fly() 和 quack() 方法。
public abstract class Duck {
public void quack() {
System.out.println("呱呱叫");
}
public void swim() {
System.out.println("游泳");
}
public void fly() {
System.out.println("飛");
}
public abstract void display();
}
public class RubberDuck extends Duck {
@Override
public void display() {
System.out.println("外觀是橡皮鴨");
}
@Override
public void quack() {
System.out.println("吱吱叫");
}
@Override
public void fly() {
System.out.println("不會飛");
}
}
public class DecoyDuck extends Duck{
@Override
public void display() {
System.out.println("外觀是誘餌鴨");
}
@Override
public void quack() {
System.out.println("不會叫");
}
@Override
public void fly() {
System.out.println("不會飛");
}
}
測試代碼:
public class Main {
public static void main(String[] args) {
MallardDuck mallardDuck = new MallardDuck();
mallardDuck.display();
mallardDuck.quack();
mallardDuck.swim();
mallardDuck.fly();
RedheadDuck redheadDuck = new RedheadDuck();
redheadDuck.display();
redheadDuck.quack();
redheadDuck.swim();
redheadDuck.fly();
RubberDuck rubberDuck = new RubberDuck();
rubberDuck.display();
rubberDuck.quack();
rubberDuck.swim();
rubberDuck.fly();
DecoyDuck decoyDuck = new DecoyDuck();
decoyDuck.display();
decoyDuck.quack();
decoyDuck.swim();
decoyDuck.fly();
}
}
運行結果:
外觀是綠頭
呱呱叫
游泳
飛
外觀是紅頭
呱呱叫
游泳
飛
外觀是橡皮鴨
吱吱叫
游泳
不會飛
外觀是誘餌鴨
不會叫
游泳
不會飛
利用繼承來提供Duck的行爲,會導致以下問題:
- 代碼在多個子類中重複
- 運行時的行爲不容易改變
- 很難知道所有鴨子的全部行爲
- 改變會牽一髮動全身,造成其他鴨子不想要的改變
利用接口實現
把 fly() 從超類中提取出來,放進一個 Flyable 接口,這麼一來,只有會飛的鴨子才實現。同樣也增加一個 Quackable 接口,因爲有的鴨子不會叫。
public abstract class Duck {
public void swim(){
System.out.println("游泳");
}
public abstract void display();
}
public interface Flyable {
public void fly();
}
public interface Quackable {
public void quack();
}
public class MallardDuck extends Duck implements Flyable,Quackable {
@Override
public void display() {
System.out.println("外觀是綠頭");
}
@Override
public void fly() {
System.out.println("飛");
}
@Override
public void quack() {
System.out.println("呱呱叫");
}
}
public class RedheadDuck extends Duck implements Flyable,Quackable {
@Override
public void display() {
System.out.println("外觀是紅頭");
}
@Override
public void fly() {
System.out.println("飛");
}
@Override
public void quack() {
System.out.println("呱呱叫");
}
}
public class RubberDuck extends Duck implements Quackable {
@Override
public void display() {
System.out.println("外觀是橡皮鴨");
}
@Override
public void quack() {
System.out.println("吱吱叫");
}
}
public class DecoyDuck extends Duck {
@Override
public void display() {
System.out.println("外觀是誘餌鴨");
}
}
測試代碼:
/**
* 利用接口來實現,重複代碼變多
*/
public class Main {
public static void main(String[] args) {
MallardDuck mallardDuck = new MallardDuck();
mallardDuck.display();
mallardDuck.quack();
mallardDuck.swim();
mallardDuck.fly();
RedheadDuck redheadDuck = new RedheadDuck();
redheadDuck.display();
redheadDuck.quack();
redheadDuck.swim();
redheadDuck.fly();
RubberDuck rubberDuck = new RubberDuck();
rubberDuck.display();
rubberDuck.quack();
rubberDuck.swim();
// 不會飛
// rubberDuck.fly();
DecoyDuck decoyDuck = new DecoyDuck();
decoyDuck.display();
// 不會叫
//decoyDuck.quack();
decoyDuck.swim();
// 不會飛
//decoyDuck.fly();
}
}
運行結果:
外觀是綠頭
呱呱叫
游泳
飛
外觀是紅頭
呱呱叫
游泳
飛
外觀是橡皮鴨
吱吱叫
游泳
外觀是誘餌鴨
游泳
我們知道,並非所有的子類都具有飛行和呱呱叫的行爲,所以繼承並不是適當的解決方法。雖然利用 Flyable 和 Quackable 接口可以解決一部分問題,但是會造成代碼無法複用,重複代碼變多(假如有48種鴨子,每種鴨子都要去實現 fly() 和 quack() 方法)。
分開變化和不會變化的部分
將鴨子會變化的行爲 fly 和 quack 放在分開的類中,此類專門提供某行爲接口的實現。
這樣的設計,飛行和呱呱叫的行爲已經和鴨子無關,可以被其他對象複用。
整合鴨子的行爲
在鴨子類中加入兩個實例變量 flyBehavior 、quackBehavior,聲明爲接口類型。
每個鴨子都會動態地設置這兩個變量以在運行時引用正確的行爲類型(FlyWithWings 、Squeak 等)
定義行爲:
public interface FlyBehavior {
public void fly();
}
public class FlyWithWings implements FlyBehavior {
@Override
public void fly() {
System.out.println("飛");
}
}
public class FlyNoWay implements FlyBehavior {
@Override
public void fly() {
System.out.println("不會飛");
}
}
public interface QuackBehavior {
public void quack();
}
public class Quack implements QuackBehavior {
@Override
public void quack() {
System.out.println("呱呱叫");
}
}
public class Squeak implements QuackBehavior {
@Override
public void quack() {
System.out.println("吱吱叫");
}
}
public class MuteQuack implements QuackBehavior {
@Override
public void quack() {
System.out.println("不會叫");
}
}
鴨子類:
public abstract class Duck {
FlyBehavior flyBehavior;
QuackBehavior quackBehavior;
public abstract void display();
public void swim() {
System.out.println("游泳");
}
public void performQuack() {
quackBehavior.quack();
}
public void performFly() {
flyBehavior.fly();
}
public void setFlyBehavior(FlyBehavior flyBehavior) {
this.flyBehavior = flyBehavior;
}
public void setQuackBehavior(QuackBehavior quackBehavior) {
this.quackBehavior = quackBehavior;
}
}
綠頭鴨實現類:
public class MallardDuck extends Duck {
@Override
public void display() {
System.out.println("外觀是綠頭");
}
public MallardDuck(){
quackBehavior = new Quack();
flyBehavior = new FlyWithWings();
}
}
測試代碼:
public class TestMallardDuck {
public static void main(String[] args) {
Duck mallard = new MallardDuck();
mallard.display();
mallard.swim();
mallard.performFly();
mallard.performQuack();
}
}
運行結果:
外觀是綠頭
游泳
飛
呱呱叫
動態設定行爲
創建一個模型鴨,一開始是不會飛的,後面增加一個火箭飛行的行爲,讓模型鴨可以飛。
public class ModelDuck extends Duck {
@Override
public void display() {
System.out.println("外觀是模型鴨");
}
public ModelDuck(){
flyBehavior = new FlyNoWay();
quackBehavior = new Quack();
}
}
public class FlyRocketPowered implements FlyBehavior {
@Override
public void fly() {
System.out.println("火箭飛行");
}
}
測試代碼:
public class TestModelDuck {
public static void main(String[] args) {
Duck model = new ModelDuck();
model.display();
model.swim();
model.performFly();
model.performQuack();
// 動態改變行爲
model.setFlyBehavior(new FlyRocketPowered());
model.performFly();
}
}
運行結果:
外觀是模型鴨
游泳
不會飛
呱呱叫
火箭飛行
總結
多用組合,少用繼承。
使用組合建立系統具有很大的彈性,不僅可將算法族封裝成類,更可以在運行時動態改變行爲。