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

发布于 2024-10-23 00:12:34 字数 1465 浏览 2 评论 0原文

因此,在单父继承模型中,在保持相同接口的同时,使代码可扩展以适应未来的更改的最佳解决方案是什么(我想强调的是,这些更改无法在在最初实施时,我的问题的主要焦点是探索支持这些变化的最佳机制/模式当它们出现时)?我知道这是一个非常基本的面向对象问题,下面我提供了我如何解决这个问题的示例,但我想知道是否有更好的解决方案来解决这个常见问题。

这是我一直在做的事情(示例代码是用 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-statements

有没有办法避免使用if 语句instanceof 运算符?

So, in a single parent inheritance model what's the best solution for making code extensible for future changes while keeping the same interface (I'd like to emphasize the fact that these changes cannot be known at the time of the original implementation, the main focus of my question is to explore the best mechanism/pattern for supporting these changes as they come up)? I know that this is a very basic OO question and below I provide example of how I've been going about it, but I was wondering if there a better solution to this common problem.

Here's what I've been doing (the example code is in Java):

In the beginning, the following two classes and interface are created:

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
    }
}

The system uses variables/fields of type FooHandler only and that object (in this case DefaultFooHandler) is created in a few, well-defined places (perhaps there's a FooHandlerFactory) so as to compensate for any changes that might happen in the future.

Then, at some point in the future a need to extend Foo arises to add some functionality. So, two new classes are created:

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
    }
}

The thing that makes me cringe in the example above is the if-statements that appear in ImprovedFooHandler.handleFoo

Is there a way to avoid using the if-statements and the instanceof operator?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

初见 2024-10-30 00:12:34

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

有这些 instanceof 语句是绝对正常的。有时,这是为子类型提供不同行为的唯一方法。
但在这里你可以使用另一个技巧:使用简单的Map。将 foo-hierarchy 的类映射到 fooHandler-hierarchy 的实例。

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 );

First of all the code you wrote won't work.
Each time you see instanceof and if...else together be very careful. The order of these checks is very important. In your case you'll never execute handleImpovedFoo. Guess why :)

It's absolutely normal you have these instanceof statements. Sometimes it's the only way to provide different behavior for a subtype.
But here you can use another trick: use simple Map. Map classes of foo-hierarchy to instances of fooHandler-hierarchy.

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 );
那一片橙海, 2024-10-30 00:12:34

处理这个问题的最佳方法很大程度上取决于具体情况,无法提供通用的解决方案。因此,我将提供一些示例以及如何解决它们。

情况 1:虚拟文件系统

代码的客户端实现虚拟文件系统,这使它们能够操作任何类型的资源,这些资源可以看起来像文件。他们通过实现以下接口来实现这一点。

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

在软件的下一个版本中,您希望添加删除目录及其所有内容的功能。称之为删除树。您不能简单地将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
}

现在介绍一种新的 Shape:

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() 方法似乎是最好的选择。它完全符合游戏对象的工作描述,希望它能够知道如何进行逻辑更新,即使它什么都不做。

结论

处理这种情况的最佳方法取决于个人情况。这就是为什么我提出了为什么你不想向 Foo 添加功能的问题。扩展 Foo 的最佳方法取决于您到底在做什么。您在 instanceof/if 中看到了什么,这表明您没有以最佳方式扩展对象。

The best way of handling this depends too much on the individual case to provide a general solution. So I'm going to provide a number of examples and how I would solve them.

Case 1: Virtual File System

Clients of your code implement virtual file systems which enable them to operate any sort of resource which can be made to look like a file. They do so by implementing the following interface.

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

In the next version of your software you want to add the ability to remove a directory and all it contents. Call it removeTree. You cannot simply add removeTree to IFolder because that will break all users of IFolder. Instead:

interface IFolder2 implements IFolder
{
     void removeTree();    
}

Whenever a client registers an IFolder (rather then IFolder2), register

new IFolder2Adapter(folder)

Instead, and use IFolder2 throughout your application. Most of your code should not be concerned with the difference about what old versions of IFolder supported.

Case 2: Better Strings

You have a string class which supports various functionality.

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

You decide to add string searching, in a new version and thus implement:

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

That's just silly, SearchableString should be merged into String.

Case 3: Shapes

You have a shape simulation, which lets you get the areas of shapes.

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

Now you introduce a new kind of Shape:

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

We could add a default empty Draw method to Shape. But it seems incorrect to have Shape have a Draw method because shapes in general aren't intended to be drawn. The drawing really needs a list of DrawableShapes not the list of Shapes that is provided. In fact, it may be that DrawableShape shouldn't be a Shape at all.

Case 4: Parts

Suppose that we have a Car:

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

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

Of course, you know somewhere down the road, somebody will make a better car.

class BetterCar extends Car
{
    Highbeams getHighBeams();
}

Here we can make use of the visitor pattern.

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

The car passes all of its component parts to calls into ICarVisitor interface allowing the Maintainer class to maintain each component.

Case 5: Game Objects
We have a game with a variety of objects which can be seen on screen

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

Some of our game objects need the ability to perform logic on a regular interval, so we create:

class LogicGameObject extends GameObject
{
    void Logic();
}

How do we call Logic() on all of the LogicGameObjects? In this case, adding an empty Logic() method to GameObject seems like the best option. Its perfectly within the job description of a GameObject to expect it to be able to know what to do for a Logic update even if its nothing.

Conclusion

The best way of handling this situations depends on the individual situation. That's why I posed the question of why you didn't want to add the functionality to Foo. The best way of extending Foo depends on what exactly you are doing. What are you seeing with the instanceof/if showing up is a symptom that you haven't extended the object in the best way.

追星践月 2024-10-30 00:12:34

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

In situations like this I usually use a factory to get the appropriate FooHandler for the type of Foo that I have. In this case there would still be a set of ifs but they would be in the factory not the implementation of the handler.

吲‖鸣 2024-10-30 00:12:34

是的,不要违反 LSP,这正是您在这里所做的。您考虑过策略模式吗?

Yes, don't violate LSP which is what you appear to be doing here. Have you considered the Strategy pattern?

ぺ禁宫浮华殁 2024-10-30 00:12:34

这看起来像是基本多态性的一个简单的例子。给 Foo 一个名为 DontWorryI'llHandleThisMyself() 之类的方法(嗯,除了没有撇号,以及一个更明智的名称)。 FooHandler 只是调用它所提供的任何 Foo 的此方法。 Foo 的派生类可以随意重写此方法。问题中的例子似乎是由内而外的。

This looks like a plain simple case for basic polymorphism.Give Foo a method named something like DontWorryI'llHandleThisMyself() (um, except without the apostrophe, and a more sensible name). The FooHandler just calls this method of whatever Foo it's given. Derived classes of Foo override this method as they please. The example in the question seems to have things inside-out.

暖树树初阳… 2024-10-30 00:12:34

使用访问者模式,您可以执行类似的操作,

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);

    }
}

With the visitor pattern you could do something like this,

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);

    }
}

But this does not guarantee only Foo can be passed to DefaultFooHandler. It allows ImprovedFoo also can be passed to DefaultFooHandler. To overcome, something similar can be done

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);

    }
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文