关于继承和可扩展性的一般OO问题

时间:2011-03-15 20:03:00

标签: java oop design-patterns inheritance extensibility

因此,在单个父继承模型中,什么是使代码可扩展为将来的更改的最佳解决方案,同时保持相同的界面(我想强调这些更改不能的事实在原始实施时知道,我的问题的主要焦点是探索支持这些变化的最佳机制/模式,因为它们出现)?我知道这是一个非常基本的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
    }
}

让我在上面的示例中畏缩的是if-statements中出现的ImprovedFooHandler.handleFoo

有没有办法避免使用if-statementsinstanceof运算符?

6 个答案:

答案 0 :(得分:3)

首先,您编写的代码无效。 每次看到instanceofif...else时都要非常小心。这些检查的顺序非常重要。在您的情况下,您永远不会执行handleImpovedFoo。猜猜为什么:)

你有这些instanceof陈述是绝对正常的。有时它是为子类型提供不同行为的唯一方法。 但是在这里你可以使用另一个技巧:使用简单的Map。将foo层次结构的类映射到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 );

答案 1 :(得分: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应该合并为String。

案例3:形状

你有一个形状模拟,可以让你获得形状区域。

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

现在你介绍一种新的形状:

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

我们可以为Shape添加一个默认的空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的工作描述中,期望它能够知道如何处理逻辑更新,即使它什么也没有。

<强>结论

处理这种情况的最佳方法取决于具体情况。这就是为什么我提出了为什么你不想将功能添加到Foo的问题。扩展Foo的最佳方式取决于您究竟在做什么。你看到什么实例/如果出现是一个症状,你没有以最好的方式扩展对象。

答案 2 :(得分:1)

是的,不要违反您在此处所做的LSP。你有没有考虑过战略模式?

答案 3 :(得分:1)

在这种情况下,我通常使用工厂为我拥有的Foo类型获取适当的FooHandler。在这种情况下,仍然会有一组ifs,但它们将在工厂而不是处理程序的实现。

答案 4 :(得分:0)

这看起来像一个基本多态的简单案例。给Foo一个名为DontWorryI'llHandleThisMyself()的方法(嗯,除了没有撇号,一个更明智的名字)。 FooHandler只是调用这个方法,无论它给出什么Foo。派生的Foo类会随心所欲地覆盖此方法。问题中的例子似乎内心深处。

答案 5 :(得分: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);

    }
}

但这并不能保证只有Foo可以传递给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);

    }
}