因此,在单个父继承模型中,什么是使代码可扩展为将来的更改的最佳解决方案,同时保持相同的界面(我想强调这些更改不能的事实在原始实施时知道,我的问题的主要焦点是探索支持这些变化的最佳机制/模式,因为它们出现)?我知道这是一个非常基本的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-statements
和instanceof
运算符?
答案 0 :(得分:3)
首先,您编写的代码无效。
每次看到instanceof
和if...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);
}
}