2011-03-15 70 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