2011-03-15 46 views
0

因此,在單一父繼承模型中,最好的解決方案是讓代碼可擴展爲未來的變化同時保持相同的接口(我想強調這些變化不能在原始執行時已知,我的問題的主要焦點是探討支持這些變化的最佳機制/模式,因爲他們來了)?我知道這是一個非常基本的面向對象問題,下面我提供了我如何去解決它的例子,但我想知道是否有更好的解決方案來解決這個常見問題。關於繼承和可擴展性的一般OO問題

這就是我一直在做(示例代碼是Java):

一開始,下面的兩個類和接口創建:

public class Foo 
{ 
    protected int z; 
} 

public interface FooHandler 
{ 
    void handleFoo(Foo foo); 
} 

public class DefaultFooHandler implements FooHandler 
{ 
    @Override 
    public void handleFoo(Foo foo) 
    { 
     //do something here 
    } 
} 

該系統採用變量/只有FooHandler類型的字段,並且該對象(在本例中爲DefaultFooHandler)是在幾個明確定義的位置(可能有一個FooHandlerFactory)創建的,以補償將來可能發生的任何變化。

然後,在未來的某個時間點需要擴展Foo來增加一些功能。因此,創建了兩個新類:

public class ImprovedFoo extends Foo 
{ 
    protected double k; 
} 

public class ImprovedFooHandler extends DefaultFooHandler 
{ 
    @Override 
    public void handleFoo(Foo foo) 
    { 
     if(foo instanceof ImprovedFoo) 
     { 
      handleImprovedFoo((ImprovedFoo)foo); 
      return; 
     } 
     if(foo instanceof Foo) 
     { 
      super.handleFoo(foo); 
      return; 
     } 
    } 

    public void handleImprovedFoo(ImprovedFoo foo) 
    { 
     //do something involving ImprovedFoo 
    } 
} 

,這讓我在上面的例子中畏縮的事情是,出現在ImprovedFooHandler.handleFoo

有沒有辦法避免使用if-statementsinstanceof運營商if-statements

+2

你在尋找訪客模式嗎? http://en.wikipedia.org/wiki/Visitor_pattern – Erik 2011-03-15 20:06:55

+0

@Erik,你應該發佈它作爲答案) – 2011-03-15 20:08:48

+0

@Stas:然後我必須總結模式 - 別人這樣做:) – Erik 2011-03-15 20:20:14

回答

3

首先你寫的代碼不起作用。 每次看到instanceofif...else都要非常小心。這些檢查的訂單是非常重要的。在你的情況下,你永遠不會執行handleImpovedFoo。猜猜爲什麼:)

你有這些instanceof聲明是絕對正常的。有時候,這是爲子類型提供不同行爲的唯一方法。 但是在這裏你可以使用另一個技巧:使用簡單的Map。將foo-hierarchy的類映射到fooHandler層次的實例。

Map<Class<? extends Foo>, FooHandler> map ... 

map.put(Foo.class, new FooHandler()); 
map.put(ImprovedFoo.class, new ImprovedFooHandler()); 

Foo foo ...; // here comes an unknown foo 

map.get(foo.getClass()).handleFoo(foo); 
+0

正如我在其他評論中提到的,我認爲這種方法是最好的。特別是當與戰略模式相結合時。雖然在這種情況下,傳遞給相應FooHandler的「foo」將不得不被降級到該特定FooHandler處理的適當類型。你聽起來怎麼樣? – Andrey 2011-03-16 17:15:14

+0

對於這種沮喪,你無能爲力。所以不要擔心和使用它。 – 2011-03-16 21:50:24

+0

我希望你不怕一般的沮喪^) – 2011-03-16 22:12:05

1

是的,不要違反LSP,這是你在這裏所做的。你有沒有考慮過戰略模式?

+0

對不起。他在哪裏試圖違反LSP? – 2011-03-15 20:41:53

+0

他在哪裏詢問傳遞的對象的類型。您應該始終能夠將Foo視爲Foo。 – JohnOpincar 2011-03-15 21:03:12

+0

在這種情況下'Foo'完全可以替代'ImprovedFoo'。他必須確保所有其他環境都是如此。 – 2011-03-15 21:18:05

1

在這樣的情況下,我通常使用工廠來獲取適當的FooHandler,以獲得我所擁有的Foo類型。在這種情況下,仍然會有一組ifs,但他們將在工廠而不是執行處理程序。

+0

爲什麼你不使用Visitor模式,正如Eric所說的? – 2011-03-15 20:11:47

+0

我不喜歡回調,我沒有得到他需要存儲狀態和/或可能使用多個處理程序處理單個項目的印象。不過,我不是DP專家。 – mattx 2011-03-15 20:40:43

+0

這種方法的實現看起來像是其他一些答案的組合 - 使用Map和Strategy模式,Map對象將包含Class對象作爲鍵以及適當類型的FooHandler作爲值。這就是你的意思,對吧? – Andrey 2011-03-16 15:12:55

0

這看起來像一個簡單的基本多態性的例子。給Foo一個名爲像DontWorryI'llHandleThisMyself()這樣的方法(除了沒有撇號和一個更明智的名字)。 FooHandler只是將這個方法調用給它的任何Foo。派生類的Foo會根據他們的需要重寫此方法。這個問題中的例子似乎有東西在裏面。

+0

我不同意這種方法。在我看來,沒有理由爲Foo添加行爲。此外,對於Foo在handleFoo中所做的任何操作都可能需要Foo可能不會「知道」的其他信息 - 這將成爲封裝問題 – Andrey 2011-03-16 15:06:57

2

處理這種情況的最好方法取決於個別情況下過多地提供了一個通用的解決方案。所以我要提供一些例子,以及我將如何解決它們。

案例1:虛擬文件系統代碼的

客戶實現虛擬文件系統,使他們能夠操作任何一種可以做出看起來像一個文件資源。他們通過實施以下界面來實現這一點。

interface IFolder 
{ 
    IFolder subFolder(String Name); 
    void delete(String filename); 
    void removeFolder(); // must be empty 
    IFile openFile(String Name); 
    List<String> getFiles(); 
} 

在下一個版本的軟件中,您希望添加刪除目錄及其所有內容的功能。稱它爲removeTree。您不能簡單地將removeTree添加到IFolder,因爲這會破壞IFolder的所有用戶。相反:

interface IFolder2 implements IFolder 
{ 
    void removeTree();  
} 

每當客戶端註冊的的iFolder(而不是IFolder2),註冊

new IFolder2Adapter(folder) 

相反,並使用IFolder2整個應用程序。你的大部分代碼不應該關心什麼舊版本的IFolder支持的區別。

案例2:更好的字符串

你必須支持各種功能的字符串類。

class String 
{ 
    String substring(int start, end); 
} 

您決定添加字符串搜索,在一個新的版本,從而實現:

class SearchableString extends String 
{ 
    int find(String); 
} 

這只是愚蠢,SearchableString應合併成字符串。

案例3:形狀

你有一個形狀的模擬,它可以讓你獲得形狀的區域。

class Shape 
{ 
    double Area(); 
    static List<Shape> allShapes; // forgive evil staticness 
} 

現在你介紹一種新形狀的:

class DrawableShape extends Shape 
{ 
    void Draw(Painter paint); 
} 

我們可以添加一個默認爲空Draw方法成型。但是,Shape有Draw方法似乎是不正確的,因爲一般情況下形狀並不打算繪製。該繪圖確實需要DrawableShapes的列表,而不是提供的Shapes列表。實際上,可能DrawableShape根本不應該是Shape。

案例4:零件

假設我們有一輛車:

class Car 
{ 
    Motor getMotor(); 
    Wheels getWheels(); 
} 

void maintain(Car car) 
{ 
    car.getMotor().changeOil(); 
    car.getWheels().rotate(); 
} 

當然,你知道在路上某處,有人會做出更好的汽車。

class BetterCar extends Car 
{ 
    Highbeams getHighBeams(); 
} 

這裏我們可以利用訪客模式。

void maintain(Car car) 
{ 
    car.visit(new Maintainer()); 
} 

汽車將其所有零部件傳遞到ICarVisitor接口,允許Maintainer類維護每個組件。

案例5:遊戲中的對象 我們與各種對象的遊戲,可以在屏幕上

class GameObject 
{ 
    void Draw(Painter painter); 
    void Destroy(); 
    void Move(Point point); 
} 

我們的一些遊戲對象需要定期間隔執行邏輯的能力可以看出,所以我們創建:

class LogicGameObject extends GameObject 
{ 
    void Logic(); 
} 

我們如何在所有的LogicGameObjects上調用Logic()?在這種情況下,向GameObject添加一個空的Logic()方法似乎是最好的選擇。它完全在GameObject的工作描述中,期望它能夠知道如何爲Logic更新做些什麼,即使它沒有任何東西。

結論

處理這種情況的最好方法取決於個人情況。這就是爲什麼我提出了爲什麼你不想爲Foo添加功能的問題。擴展Foo的最佳方式取決於你在做什麼。如果出現的是一種症狀,您沒有以最佳方式擴展對象,那麼您如何看待instanceof /。

+0

1)假設存在代碼庫並且添加了ImprovedFoo對象以支持新功能,則合併不是真正的選擇。 2)我不知道爲什麼你認爲改編Foo而不是延伸它更好,你能詳細說明一下嗎? 3)看到我對DarenW的回答的評論。 – Andrey 2011-03-16 15:09:19

+0

@Andrey,在第二種情況下,ImprovedFoo仍然存在。這裏的假設是Foo的舊版本由外部來源提供。適配器將Foo對象變成ImprovedFoo對象。只有在別人正在實現你的接口的情況下才有意義。 – 2011-03-16 15:32:42

+0

你爲什麼會把Foo變成ImprovedFoo?這個想法是讓Foo保持原樣,同時增加對ImprovedFoo的支持。 – Andrey 2011-03-16 17:07:03

0

有了你可以做這樣的事情訪問者模式,

abstract class absFoo {} 
class Foo extends absFoo 
{ 
    protected int z; 

} 
class ImprovedFoo extends absFoo 
{ 
    protected double k; 

} 
interface FooHandler { 
    void accept(IFooVisitor visitor, absFoo foo); 
} 
class DefaultFooHandler implements FooHandler 
{ 
    public void accept(IFooVisitor visitor, absFoo foo) 
    { 
     visitor.visit(this, foo); 
    } 
    public void handleFoo(absFoo foo) { 
     System.out.println("DefaultFooHandler"); 
    } 
} 
class ImprovedFooHandler implements FooHandler 
{ 
    public void handleFoo(absFoo foo) 
    { 
     System.out.println("ImprovedFooHandler"); 
    } 

    public void accept(IFooVisitor visitor, absFoo foo) { 
     visitor.visit(this, foo); 
    } 

} 

interface IFooVisitor { 
    public void visit(DefaultFooHandler fooHandler, absFoo foo); 
    public void visit(ImprovedFooHandler fooHandler, absFoo foo); 
} 

class FooVisitor implements IFooVisitor{ 
    public void visit(DefaultFooHandler fHandler, absFoo foo) { 
     fHandler.handleFoo(foo); 
    } 

    public void visit(ImprovedFooHandler iFhandler, absFoo foo) { 
     iFhandler.handleFoo(foo); 
    } 


} 

public class Visitor { 
    public static void main(String args[]) { 
     absFoo df = new Foo(); 
     absFoo idf = new ImprovedFoo(); 

     FooHandler handler = new ImprovedFooHandler(); 

     IFooVisitor visitor = new FooVisitor(); 
     handler.accept(visitor, idf); 

    } 
} 

但是,這並不能保證只有富可以傳遞給DefaultFooHandler。它允許ImprovedFoo也可以傳遞給DefaultFooHandler。爲了克服,可以做類似的事情

class Foo 
{ 
    protected int z; 

} 
class ImprovedFoo 
{ 
    protected double k; 

} 

interface FooHandler { 
    void accept(IFooVisitor visitor); 
} 

class DefaultFooHandler implements FooHandler 
{ 
    private Foo iFoo; 

    public DefaultFooHandler(Foo foo) { 
     this.iFoo = foo; 
    } 

    public void accept(IFooVisitor visitor) 
    { 
     visitor.visit(this); 
    } 
    public void handleFoo() { 
     System.out.println("DefaultFooHandler"); 
    } 
} 

class ImprovedFooHandler implements FooHandler 
{ 
    private ImprovedFoo iFoo; 

    public ImprovedFooHandler(ImprovedFoo iFoo) { 
     this.iFoo = iFoo; 
    } 

    public void handleFoo() 
    { 
     System.out.println("ImprovedFooHandler"); 
    } 

    public void accept(IFooVisitor visitor) { 
     visitor.visit(this); 
    } 

} 

interface IFooVisitor { 
    public void visit(DefaultFooHandler fooHandler); 
    public void visit(ImprovedFooHandler fooHandler); 
} 

class FooVisitor implements IFooVisitor{ 
    public void visit(DefaultFooHandler fHandler) { 
     fHandler.handleFoo(); 
    } 

    public void visit(ImprovedFooHandler iFhandler) { 
     iFhandler.handleFoo(); 
    } 


} 
public class Visitor { 
    public static void main(String args[]) { 
     FooHandler handler = new DefaultFooHandler(new Foo()); 
     FooHandler handler2 = new ImprovedFooHandler(new ImprovedFoo()); 

     IFooVisitor visitor = new FooVisitor(); 
     handler.accept(visitor); 

     handler2.accept(visitor); 

    } 
} 
+0

但我想你不允許編輯Foo和DefaultFooHandler。如果這是這種解決方案沒有用的情況。 – kalyan 2011-03-17 19:40:30