关于继承和可扩展性的一般 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-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 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(6)
首先,您编写的代码将无法运行。
每次当你看到
instanceof
和if...else
在一起时要非常小心。这些检查的顺序非常重要。在你的情况下,你永远不会执行handleImpovedFoo
。猜猜为什么:)有这些
instanceof
语句是绝对正常的。有时,这是为子类型提供不同行为的唯一方法。但在这里你可以使用另一个技巧:使用简单的
Map
。将 foo-hierarchy 的类映射到 fooHandler-hierarchy 的实例。First of all the code you wrote won't work.
Each time you see
instanceof
andif...else
together be very careful. The order of these checks is very important. In your case you'll never executehandleImpovedFoo
. 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.处理这个问题的最佳方法很大程度上取决于具体情况,无法提供通用的解决方案。因此,我将提供一些示例以及如何解决它们。
情况 1:虚拟文件系统
代码的客户端实现虚拟文件系统,这使它们能够操作任何类型的资源,这些资源可以看起来像文件。他们通过实现以下接口来实现这一点。
在软件的下一个版本中,您希望添加删除目录及其所有内容的功能。称之为删除树。您不能简单地将removeTree 添加到IFolder,因为这会破坏IFolder 的所有用户。相反:
每当客户端注册 IFolder(而不是 IFolder2)时,请注册
相反,并在整个应用程序中使用 IFolder2。您的大部分代码不应该关心 IFolder 旧版本支持的差异。
情况 2:更好的字符串
您有一个支持各种功能的字符串类。
您决定在新版本中添加字符串搜索,从而实现:
这太愚蠢了,SearchableString 应该合并到 String 中。
案例 3:形状
您有一个形状模拟,它可以让您获得形状的面积。
现在介绍一种新的 Shape:
我们可以向 Shape 添加一个默认的空 Draw 方法。但让 Shape 具有 Draw 方法似乎是不正确的,因为形状通常不用于绘制。绘图确实需要一个 DrawableShapes 列表,而不是提供的 Shapes 列表。事实上,DrawableShape 可能根本不应该是一个 Shape。
案例 4:零件
假设我们有一辆汽车:
当然,您知道在未来的某个地方,有人会制造出更好的汽车。
这里我们可以利用访问者模式。
汽车将其所有组件传递给 ICarVisitor 接口,从而允许Maintainer 类维护每个组件。
案例 5:游戏对象
我们有一个可以在屏幕上看到的具有各种对象的游戏。
我们的一些游戏对象需要能够定期执行逻辑,因此我们创建:
如何在所有 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.
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:
Whenever a client registers an IFolder (rather then IFolder2), register
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.
You decide to add string searching, in a new version and thus implement:
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.
Now you introduce a new kind of Shape:
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:
Of course, you know somewhere down the road, somebody will make a better car.
Here we can make use of the visitor pattern.
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
Some of our game objects need the ability to perform logic on a regular interval, so we create:
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.
在这种情况下,我通常使用工厂来获取适合我拥有的 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.
是的,不要违反 LSP,这正是您在这里所做的。您考虑过策略模式吗?
Yes, don't violate LSP which is what you appear to be doing here. Have you considered the Strategy pattern?
这看起来像是基本多态性的一个简单的例子。给 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.
使用访问者模式,您可以执行类似的操作,
但这并不能保证只有 Foo 可以传递给 DefaultFooHandler。它允许ImprovedFoo也可以传递给DefaultFooHandler。为了克服,可以做类似的事情
With the visitor pattern you could do something like this,
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