策略模式(來自HeadFirst設(shè)計(jì)模式)
今天看了 Head First 設(shè)計(jì)模式的第一個(gè)模式买喧,居然是策略模式,感覺這種模式很實(shí)用男旗,而且書上寫了20多頁趁啸,我這里做一下精簡强缘,給大家分享一下,在最后也有一些自己的思考不傅。既然是精簡旅掂,所以一定會有一些省略,如果要看詳細(xì)的访娶,看看原書吧商虐,寫得很不錯(cuò)!
需求1崖疤,父類實(shí)現(xiàn)
問題由來
現(xiàn)在要設(shè)計(jì)一只鴨子類秘车,所有的鴨子都可以游泳,不同的鴨子樣外貌可能不一樣劫哼。
剛拿到這個(gè)需求叮趴,很簡答的想法設(shè)計(jì)一個(gè)Duck類,擁有共有的方法swim权烧,并且包含一個(gè)抽象方法display展示形態(tài)眯亦。
Duck.java
abstract class Duck{
public void swim(){
System.out.println("I am swimming");
}
public abstract void display();
}
GreenDuck.java
public class GreenDuck extends Duck{
@Override
public void display() {
System.out.println("my color is green");
}
}
RedDuck.java
public class RedDuck extends Duck{
@Override
public void display() {
System.out.println("my color is red");
}
}
為什么swim()方法是普通方法,display() 方法是抽象方法呢般码?
這里是為了體現(xiàn)妻率,所有的都會游泳,并且都一樣板祝。所有鴨子都有外貌宫静,但是是由各自決定,所以必須子類來重寫扔字。
需求2囊嘉,接口實(shí)現(xiàn)
這時(shí)候需求變更,有些鴨子可以飛革为。
注意,這里的關(guān)鍵詞是可以舵鳞。如果這個(gè)時(shí)候我們把 fly() 方法寫在子類 Duck 中震檩,那就表示所有的鴨子都會飛。所以這個(gè)時(shí)候我們想到了接口,定義 Flyable 接口抛虏,讓會飛的鴨子實(shí)現(xiàn)接口博其,并重寫方法。
Flyable.java
public interface Flyable {
void fly();
}
GreenDuck.java
public class GreenDuck extends Duck implements Flyable{
public void fly() {
System.out.println("I am flying with wing");
}
@Override
public void display() {
System.out.println("my color is green");
}
}
假設(shè)GreenDuck會飛迂猴,那么它需要實(shí)現(xiàn)flyable接口慕淡,然后寫自己會飛。RedDuck不會飛沸毁,所以不實(shí)現(xiàn)flyable接口峰髓。
看似問題解決了,但是如果這個(gè)時(shí)候我們系統(tǒng)中有幾十種鴨子息尺,而且會飛只分用翅膀飛携兵,不能飛,用噴氣式火箭飛3種搂誉,如果采用接口來實(shí)現(xiàn)徐紧,幾十種鴨子都需要自己去實(shí)現(xiàn)方法,無法代碼重用炭懊,這種設(shè)計(jì)是不是很不好呢并级,代碼重用率太低。
而且侮腹,如果后面我們還要添加其他的屬性死遭,比如說話Speakable,那我們需要添加一個(gè)新接口凯旋,并且以前的鴨子都要從新實(shí)現(xiàn)一遍呀潭,完全違反了開閉原則。
需求3至非,策略模式實(shí)現(xiàn)
關(guān)子賣完钠署,我們來看看策略模式怎么實(shí)現(xiàn):
策略模式說,我們需要把最公有的相同的方法放在父類中荒椭,將可以變化的方法抽取成接口谐鼎,并通過組合的方式放到父類中,子類通過插入不同的接口實(shí)現(xiàn)趣惠,完成類的配置狸棍。(這個(gè)不是原話,但是我覺得更好理解)
是什么意思呢味悄?
Duck類中草戈,swim() 方法屬于共有并且都相同的方法,display() 屬于都有侍瑟,但是需要自己去實(shí)現(xiàn)的方法唐片。fly和speak是有些子類有的方法丙猬,并且實(shí)現(xiàn)有相同有不同,我們應(yīng)該抽取成接口放在父類中费韭。來看看代碼:
修改Duck類:
abstract class Duck{
protected Flyable flyable;
public void swim(){
System.out.println("I am swimming");
}
public abstract void display();
public void performFly(){
// 通過多態(tài)機(jī)制茧球,動態(tài)決定到底怎么飛
flyable.fly();
}
// 設(shè)置飛的具體實(shí)現(xiàn),并隨時(shí)可以改變
public void setFlayable(Flyable flyable){
//TODO:添加非空判斷
this.flyable = flyable;
}
}
實(shí)現(xiàn)3中飛的行為:
Flyable.java
public interface Flyable {
void fly();
}
FlyNoWay.java 不能飛
public class FlyNoWay implements Flyable{
public void fly() {
System.out.println("can not fly");
}
}
FlyWithWing.java 用翅膀飛
public class FlyWithWing implements Flyable{
public void fly() {
System.out.println("fly with wing");
}
}
FlyWithRocket 用火箭飛(真是掉渣天星持,居然用火箭飛 -.-)
public class FlyWithRocket implements Flyable{
public void fly() {
System.out.println("fly with rocket");
}
}
如果現(xiàn)在要讓 GreenDuck 有飛的屬性抢埋,應(yīng)該這樣做:
GreenDuck.java
public class GreenDuck extends Duck{
public GreenDuck(){
this.flyable = new FlyNoWay();
}
@Override
public void display() {
System.out.println("my color is green");
}
}
Main.java
public class Main {
public static void main(String[] args) {
GreenDuck gDuck = new GreenDuck();
gDuck.performFly();
gDuck.setFlayable(new FlyWithWing());
gDuck.performFly();
}
}
默認(rèn)GreenDuck是不會飛的,然后可以通過動態(tài)的設(shè)置飛屬性給鴨子督暂,讓他具有各種飛的屬性揪垄。將飛與類解耦,并且也達(dá)到了飛實(shí)現(xiàn)重用的目的损痰。
策略模式大概就是這樣福侈,如果這個(gè)時(shí)候我們要添加 Speak 屬性,怎么做么卢未?
添加新功能
step1: 添加 Speakable接口肪凛,和兩種實(shí)現(xiàn)
Speakable.java
public interface Speakable {
void speak();
}
SpeakDuckLaguage .java
public class SpeakDuckLaguage implements Speakable {
public void speak() {
System.out.println("I can speak duck language");
}
}
SpeakHumanLaguage.java
public class SpeakHumanLaguage implements Speakable {
public void speak() {
System.out.println("I can speak human language");
}
}
step2: Duck類中添加Speakable屬性,并且提供set方法辽社,然后提供一個(gè)執(zhí)行speak的方法
abstract class Duck{
protected Flyable flyable;
protected Speakable speakable;
//...
public void performSpeak(){
speakable.speak();
}
public void setSpeakable(Speakable speakable){
//TODO: 非空判斷
this.speakable = speakable;
}
}
step3: 給紅鴨子添加會說話屬性
RedDuck.java 默認(rèn)說鴨子語言
public class RedDuck extends Duck{
// 默認(rèn)說鴨子語言
public RedDuck(){
this.speakable = new SpeakDuckLaguage();
}
@Override
public void display() {
System.out.println("my color is red");
}
}
Main.java 客戶端調(diào)用
public class Main {
public static void main(String[] args) {
GreenDuck gDuck = new GreenDuck();
gDuck.performFly();
gDuck.setFlayable(new FlyWithWing());
gDuck.performFly();
RedDuck rDuck = new RedDuck();
rDuck.performSpeak();
rDuck.setSpeakable(new SpeakHumanLaguage());
rDuck.performSpeak();
}
}
輸出:
I can speak duck language
I can speak human language
can not fly
fly with wing
最后提供一個(gè)UML圖:
思考
- 感覺用了策略模式伟墙,代碼量和類增加了不少,但是確實(shí)達(dá)到了解耦和復(fù)用的目的滴铅。
- 前面的設(shè)計(jì)并不完美戳葵,因?yàn)?Duck 類中的接口屬性,并沒有提供默認(rèn)值汉匙,而是在子類中去完成默認(rèn)配置拱烁。所以在使用時(shí)很可能出現(xiàn) 空指針錯(cuò)誤,更加完善的設(shè)計(jì)應(yīng)該在 performSpeak(), performFly() 中添加非空判斷噩翠∠纷裕或者將默認(rèn)值在 Duck類中設(shè)置,子類實(shí)現(xiàn)構(gòu)造方法都不用寫了伤锚,但是這樣把實(shí)現(xiàn)和 Duck 耦合在一起擅笔,也不是很好。如果有好的解決方案屯援,請留言猛们。