試想一下,如果寫出來的代碼如藝術(shù)品,那將是多么美妙的一件事。因此,小編該好好學(xué)習(xí)一下怎么去設(shè)計代碼了。設(shè)計模式這種東西來到世界上已經(jīng)有相當(dāng)歷史了,可是工作了之后發(fā)現(xiàn)真正能用上的還是少數(shù)人。在寫代碼之前認真思考并設(shè)計一下,能省去相當(dāng)一部分維護代碼的成本。朋友推薦了《Head First 設(shè)計模式 (中文版)》這本書。小編就直接以這本書來打開設(shè)計的大門了。在設(shè)計之前我們體會一下根據(jù)需求的變化,沒有設(shè)計的代碼會讓我們手忙腳亂
1:需求來了-使用繼承
- 需求:SimUDuck這款游戲會出現(xiàn)各種鴨子,游泳,呱呱叫
- 考慮:拓展的是多個鴨子,而叫和游泳每個鴨子都有,會有長的不一樣的鴨子
- 做法:定義鴨子父類Duck,定義三個方法,quack、swim、display。外觀不一樣,那么抽象display。其他實現(xiàn)了。增加鴨子只需要繼承Duck并實現(xiàn)不同的外觀就行了。
public abstract class Duck {
public void quack() {
System.out.println("呱呱叫");
}
public void swim() {
System.out.println("游泳");
}
public abstract void display();
}
public class MallardDuck extends Duck {
@Override
public void display() {
System.out.println("外觀:綠頭");
}
}
public class RedheadDuck extends Duck {
@Override
public void display() {
System.out.println("外觀:紅頭");
}
}

總結(jié):感覺還是很可以的,感覺以后增加鴨子只要多寫個鴨子類就好了。
2:增加需求-拓展功能
- 需求:讓鴨子會飛
- 考慮:難不倒我嘛,只要在Duck實現(xiàn)一個fly的方法下馬所有的鴨子就成功的會飛了
- 做法:Duck增加fly的方法
代碼僅僅修改了Duck類
public abstract class Duck {
public void quack() {
System.out.println("呱呱叫");
}
public void swim() {
System.out.println("游泳");
}
public abstract void display();
public void fly() {
System.out.println("飛");
}
}

總結(jié):內(nèi)心覺得當(dāng)初設(shè)計還是有點用的,完美適應(yīng)增加的需求
3:再增加需求-定制功能的實體
- 需求:加橡皮鴨和誘餌鴨,橡皮鴨的會吱吱叫,不會飛。誘餌鴨不會叫也不會飛
- 考慮:父類定義的某些方法的實現(xiàn),這時候字類不需要了,覆蓋掉就好了
- 做法:繼承Duck增加兩個鴨子RubberDuck和DecoyDuck。覆蓋需要重新實現(xiàn)的方法。
public class RubberDuck extends Duck {
/**
* 覆蓋父類方法,實現(xiàn)新的功能
*/
@Override
public void quack() {
System.out.println("吱吱叫");
}
@Override
public void display() {
System.out.println("外觀:橡皮鴨");
}
@Override
public void fly() {
//不會飛
}
}
public class DecoyDuck extends Duck {
@Override
public void display() {
System.out.println("外觀:誘餌鴨");
}
@Override
public void quack() {
// 不會叫
}
@Override
public void fly() {
// 不會飛
}
}

總結(jié):這時候隨著需求不斷增加,感覺當(dāng)初采用繼承的方式去設(shè)計代碼有點不夠用了
- 問題:以后所有的鴨子都默認有父類的方法,如果不需要這些方法,就得每個都提供空實現(xiàn),如果忘記了提供,那么就會多出不需要的方法了。代碼不優(yōu)雅,看著都難受。
4、優(yōu)化設(shè)計-使用接口
- 需求:改變一下設(shè)計,引入接口
- 考慮:變化的方法有fly和quack。
- 做法:抽取fly到Flyable接口,抽取quack到Quackable接口,需要的子類自行實現(xiàn)
public interface Flyable {
void fly();
}
public interface Quackable {
void quack();
}
public abstract class Duck {
public void swim() {
System.out.println("游泳");
}
public abstract void display();
}
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 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 RubberDuck extends Duck implements Quackable {
@Override
public void quack() {
System.out.println("吱吱叫");
}
@Override
public void display() {
System.out.println("外觀:橡皮鴨");
}
}
public class DecoyDuck extends Duck {
@Override
public void display() {
System.out.println("外觀:誘餌鴨");
}
}

總結(jié):使用接口之后發(fā)現(xiàn)確實可以不寫這么多空實現(xiàn)了,但是也暴露出了另一個問題,
- 問題:會出現(xiàn)重復(fù)的代碼,試想一下,如果很多個鴨子都會同樣的飛,那么每個鴨子都要去實現(xiàn)同樣的fly方法。因此簡單的使用接口也不太好。
5:進一步優(yōu)化設(shè)計-使用組合
- 需求:拓展的時候要解決重復(fù)代碼和動態(tài)實現(xiàn)方法的問題
- 考慮:繼承會使方法不靈活,接口會導(dǎo)致重復(fù)代碼。嘗試組合一下,并面向接口編程
- 做法:封裝鴨子的行為(動態(tài)的方法),定義行為接口。定義行為類去實現(xiàn)行為接口(面向接口)。在Duck父類中組合這些行為。而字類提供具體的行為實現(xiàn)。
行為:FlyBehavior以及他的不同實現(xiàn)方式:飛,不會飛...
public interface FlyBehavior {
void fly();
}
public class FlyNoWay implements FlyBehavior {
@Override
public void fly() {
//不會飛
}
}
public class FlyWithWings implements FlyBehavior {
@Override
public void fly() {
System.out.println("飛");
}
}
行為:QuackBehavior 以及他的不同實現(xiàn)方式:呱呱叫,吱吱叫,不會叫...
public interface QuackBehavior {
void quack();
}
public class MuteQuack implements QuackBehavior {
@Override
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 abstract class Duck {
FlyBehavior flyBehavior;
QuackBehavior quackBehavior;
public void swim() {
System.out.println("游泳");
}
public abstract void display();
public void performQuack() {
quackBehavior.quack();
}
public void performFly() {
flyBehavior.fly();
}
}
public class MallardDuck extends Duck {
public MallardDuck() {
quackBehavior = new Quack();
flyBehavior = new FlyWithWings();
}
@Override
public void display() {
System.out.println("外觀:綠頭");
}
}
public class RedheadDuck extends Duck {
public RedheadDuck() {
quackBehavior = new Quack();
flyBehavior = new FlyWithWings();
}
@Override
public void display() {
System.out.println("外觀:紅頭");
}
}
public class RubberDuck extends Duck {
public RubberDuck() {
quackBehavior = new Squeak();
flyBehavior = new FlyNoWay();
}
@Override
public void display() {
System.out.println("外觀:橡皮鴨");
}
}
public class DecoyDuck extends Duck {
public DecoyDuck() {
quackBehavior = new MuteQuack();
flyBehavior = new FlyNoWay();
}
@Override
public void display() {
System.out.println("外觀:誘餌鴨");
}
}

總結(jié):光從uml上看就能體會到組合的拓展性是多么的強了。重構(gòu)成這樣之后,不用更擔(dān)心拓展任何的功能的鴨子了,如果沒有功能,你就提供接口,并提供實現(xiàn)類,組合到新的鴨子里即可。那么你可能你會說了,這樣還是不夠動態(tài)。如果我要連行為都是動態(tài)的呢?
6:增加需求-輕松拓展
需求:增加一個模型鴨,不會飛,會呱呱叫。但是模型鴨可以變成吱吱叫,并且會用火箭飛
考慮:由于我們將行為當(dāng)成屬性組合到父類里面了,要改變行為,那么提供改變屬性的set方法即可。
做法:修改Duck類,增加改變屬性flyBehavior、quackBehavior的方法。拓展模型鴨提供默認行為實現(xiàn)。增加fly的實現(xiàn)方式用火箭飛
public class FlyRocketPowered implements FlyBehavior {
@Override
public void fly() {
System.out.println("用火箭飛");
}
}
public abstract class Duck {
FlyBehavior flyBehavior;
QuackBehavior quackBehavior;
public void swim() {
System.out.println("游泳");
}
public abstract void display();
public void performQuack() {
quackBehavior.quack();
}
public void performFly() {
flyBehavior.fly();
}
/**
* 修改行為:FlyBehavior
*
* @param flyBehavior
* @return
*/
public Duck setFlyBehavior(FlyBehavior flyBehavior) {
this.flyBehavior = flyBehavior;
return this;
}
/**
* 修改行為:QuackBehavior
*
* @param quackBehavior
* @return
*/
public Duck setQuackBehavior(QuackBehavior quackBehavior) {
this.quackBehavior = quackBehavior;
return this;
}
}
public class ModelDuck extends Duck {
public ModelDuck() {
quackBehavior = new Quack();
flyBehavior = new FlyNoWay();
}
@Override
public void display() {
System.out.println("外觀:模型鴨");
}
}
public class Test {
public static void main(String[] args) {
Duck modelDuck = new ModelDuck();
modelDuck.performQuack();
modelDuck.performFly();
modelDuck.setQuackBehavior(new Squeak());
modelDuck.setFlyBehavior(new FlyRocketPowered());
modelDuck.performFly();
}
}

總結(jié):從最初的繼承,到接口,到封裝改變,面向接口變成。我們已經(jīng)體會到了設(shè)計的美妙。我們發(fā)現(xiàn)了這樣寫出來的代碼具有較高的拓展性了。無論是拓展行為,還是拓展實體。都可以駕馭的了。但是如果一開始沒有思考和設(shè)計,隨意設(shè)計,一旦項目的代碼和業(yè)務(wù)多起來,那么重構(gòu)的成本將會提高很多,因此寫代碼之前花點時間思考一下拓展性是很有必要的。
總結(jié)一下這個過程當(dāng)中的一些設(shè)計原則。
將變化的部分和固定部分的區(qū)別開來,封裝變化面向接口編程,而不是面向?qū)崿F(xiàn)編程組合比繼承好用,多用組合,少用繼承
參考文獻:[1]
-
《Head First 設(shè)計模式 (中文版)》 ?