模擬鴨子游戲的應(yīng)用程序,要求:游戲中會出現(xiàn)各種顏色外形的鴨子记焊,一邊游泳戲水,一邊呱呱叫栓撞。
方法一:運用繼承的特性遍膜,將其中共同的部分提升出來,避免重復(fù)編程腐缤。
即:設(shè)計一個鴨子的超類(Superclass),并讓各種鴨子繼承這個超類捌归。
public class Duck{
public void quack(){ //呱呱叫
System.out.println("呱呱叫");
}
public void swim(){ //游泳
System.out.println(" 游泳");
}
public abstratact void display(); /*因為外觀不一樣,讓子類自己去決定了岭粤。*/
}
對于它的子類只需簡單的繼承就可以了惜索,并實現(xiàn)自己的display()方法。
//野鴨
public class MallardDuck extends Duck{
public void display(){
System.out.println("野鴨的顏色...");
}
}
//紅頭鴨
public class RedheadDuck extends Duck{
public void display(){
System.out.println("紅頭鴨的顏色...");
}
}
不幸的是剃浇,現(xiàn)在客戶又提出了新的需求巾兆,想讓鴨子飛起來。這個對于我們OO程序員虎囚,在簡單不過了角塑,在超類中在加一個方法就可以了。
public class Duck{
public void quack(){ //呱呱叫
System.out.println("呱呱叫");
}
public void swim(){ //游泳
System.out.println(" 游泳");
}
public abstract void display(); /*因為外觀不一樣淘讥,讓子類自己去決定了圃伶。*/
public void fly(){
System.out.println("飛吧!鴨子");
}
}
對于不能飛的鴨子蒲列,在子類中只需簡單的覆蓋窒朋。
//殘廢鴨
public class DisabledDuck extends Duck{
public void display(){
System.out.println("殘廢鴨的顏色...");
}
public void fly(){
//覆蓋,變成什么事都不做蝗岖。
}
}
其它會飛的鴨子不用覆蓋侥猩。
這樣所有的繼承這個超類的鴨子都會fly了。但是問題又出來了抵赢,客戶又提出有的鴨子會飛欺劳,有的不能飛唧取。
點評:
對于上面的設(shè)計,你可能發(fā)現(xiàn)一些弊端划提,如果超類有新的特性枫弟,子類都必須變動,這是我們開發(fā)最不喜歡看到的腔剂,一個類變讓另一個類也跟著變媒区,這有點不符合OO設(shè)計了。這樣很顯然的耦合了一起掸犬。利用繼承-->耦合度太高了袜漩。
方法二:用接口改進
我們把容易引起變化的部分提取出來并封裝之,來應(yīng)付以后的變法湾碎。雖然代碼量加大了宙攻,但可用性提高了,耦合度也降低了介褥。
我們把Duck中的fly方法和quack提取出來座掘。
public interface Flyable{
public void fly();
}
public interface Quackable{
public void quack();
}
最后Duck的設(shè)計成為:
public class Duck{
public void swim(){ //游泳
System.out.println(" 游泳");
}
public abstract void display(); /*因為外觀不一樣,讓子類自 己去決定了柔滔。*/
}
而MallardDuck,RedheadDuck,DisabledDuck 就可以寫成為:
//野鴨
public class MallardDuck extends Duck implements Flyable,Quackable{
public void display(){
System.out.println("野鴨的顏色...");
}
public void fly(){
//實現(xiàn)該方法
}
public void quack(){
//實現(xiàn)該方法
}
}
//紅頭鴨
public class RedheadDuck extends Duck implements Flyable,Quackable{
public void display(){
System.out.println("紅頭鴨的顏色...");
}
public void fly(){
//實現(xiàn)該方法
}
public void quack(){
//實現(xiàn)該方法
}
}
//殘廢鴨 只實現(xiàn)Quackable(能叫不能飛)
public class DisabledDuck extends Duck implements Quackable{
public void display(){
System.out.println("殘廢鴨的顏色...");
}
public void quack(){
//實現(xiàn)該方法
}
}
點評:
好處:這樣已設(shè)計溢陪,我們的程序就降低了它們之間的耦合咱圆。
不足:Flyable和 Quackable接口一開始似乎還挺不錯的糠悼,解決了問題(只有會飛到鴨子才實現(xiàn) Flyable),但是Java接口不具有實現(xiàn)代碼贞奋,所以實現(xiàn)接口無法達到代碼的復(fù)用超全。
方法三:策略模式
對上面各方式的總結(jié):
繼承的好處:讓共同部分,可以復(fù)用.避免重復(fù)編程.
繼承的不好:耦合性高.一旦超類添加一個新方法,子類都繼承,擁有此方法,若子類相當部分不實現(xiàn)此方法,則要進行大批量修改.繼承時,子類就不可繼承其它類了.
接口的好處:解決了繼承耦合性高的問題.且可讓實現(xiàn)類,繼承或?qū)崿F(xiàn)其它類或接口.
接口的不好:不能真正實現(xiàn)代碼的復(fù)用.可用以下的策略模式來解決.
我們有一個設(shè)計原則:
找出應(yīng)用中相同之處咆霜,且不容易發(fā)生變化的東西,把它們抽取到抽象類中嘶朱,讓子類去繼承它們蛾坯;
找出應(yīng)用中可能需要變化之處,把它們獨立出來疏遏,不要和那些不需要變化的代碼混在一起脉课。
現(xiàn)在,為了要分開“變化和不變化的部分”财异,我們準備建立兩組類(完全遠離Duck類)倘零,一個是"fly"相關(guān)的,另一個是“quack”相關(guān)的宝当,每一組類將實現(xiàn)各自的動作。比方說胆萧,我們可能有一個類實現(xiàn)“呱呱叫”庆揩,另一個類實現(xiàn)“吱吱叫”俐东,還有一個類實現(xiàn)“安靜”。
首先寫兩個接口订晌。FlyBehavior(飛行行為)和QuackBehavior(叫的行為).
public interface FlyBehavior{
public void fly();
}
public interface QuackBehavior{
public void quack();
}
我們在定義一些針對FlyBehavior的具體實現(xiàn)虏辫。
public class FlyWithWings implements FlyBehavior{
public void fly(){
//實現(xiàn)了所有有翅膀的鴨子飛行行為。
}
}
public class FlyNoWay implements FlyBehavior{
public void fly(){
//什么都不做锈拨,不會飛
}
}
針對QuackBehavior的幾種具體實現(xiàn)砌庄。
public class Quack implements QuackBehavior{
public void quack(){
//實現(xiàn)呱呱叫的鴨子
}
}
public class Squeak implements QuackBehavior{
public void quack(){
//實現(xiàn)吱吱叫的鴨子
}
}
public class MuteQuack implements QuackBehavior{
public void quack(){
//什么都不做,不會叫
}
}
點評一:
這樣的設(shè)計奕枢,可以讓飛行和呱呱叫的動作被其他的對象復(fù)用娄昆,因為這些行為已經(jīng)與鴨子類無關(guān)了。而我們增加一些新的行為缝彬,不會影響到既有的行為類萌焰,也不會影響“使用”到飛行行為的鴨子類。
最后我們看看Duck 如何設(shè)計谷浅。
public class Duck{ //在抽象類中,聲明各接口,定義各接口對應(yīng)的方法.
FlyBehavior flyBehavior;//接口
QuackBehavior quackBehavior;//接口
public Duck(){}
public abstract void display();
public void swim(){
//實現(xiàn)游泳的行為
}
public void performFly(){
flyBehavior.fly(); //由于是接口,會根據(jù)繼承類實現(xiàn)的方式,而調(diào)用相應(yīng)的方法.
}
public void performQuack(){
quackBehavior.quack();();
}
}
看看MallardDuck如何實現(xiàn)扒俯。
//通過構(gòu)造方法,生成'飛','叫'具體實現(xiàn)類的實例,從而指定'飛','叫'的具體屬性
public class MallardDuck extends Duck{
public MallardDuck {
flyBehavior = new FlyWithWings ();
quackBehavior = new Quack();
//因為MallardDuck 繼承了Duck,所有具有flyBehavior 與quackBehavior 實例變量}
public void display(){
//實現(xiàn)
}
}
這樣就滿足了即可以飛一疯,又可以叫撼玄,同時展現(xiàn)自己的顏色了。
這樣的設(shè)計我們可以看到是把flyBehavior 墩邀,quackBehavior 的實例化寫在子類了掌猛。我們還可以動態(tài)的來決定。 我們只需在Duck中加上兩個方法磕蒲。
在構(gòu)造方法中對屬性進行賦值與用屬性的setter的區(qū)別:
構(gòu)造方法中對屬性進行賦值:固定留潦,不可變;
用屬性的setter辣往,可以在實例化對象后兔院,動態(tài)的變化,比較靈活站削。
public class Duck{
FlyBehavior flyBehavior;//接口
QuackBehavior quackBehavior;//接口
public void setFlyBehavior(FlyBehavior flyBehavior){
this.flyBehavior = flyBehavior;
}
public void setQuackBehavior(QuackBehavior quackBehavior {
this.quackBehavior= quackBehavior;
}
}