【问题标题】:When "if else"/"instance of" are inevitable, how do we improve the design apart from using visitor pattern?当“if else”/“instance of”不可避免时,除了使用访问者模式,我们如何改进设计?
【发布时间】:2013-05-21 08:56:53
【问题描述】:

当我们有一个纯粹继承语义而不是行为的对象层次结构时,我们不可避免地需要在各处编写“instanceof”或“if/else”来进行运行时类型检查。

例如

如果我有一个具有

的对象层次结构
Class Function

Class Average extends Function

Class Sum extends Function

Class Max extends Function

如果这些类中有一个叫做calculate()的方法,那么我们就没有问题,我们可以利用多态性,这样的设计就满足了LSP。

但是,如果我们出于某种原因不想将此 calculate() 方法添加到此层次结构中怎么办,这些对象是纯粹的普通对象,无状态对象仅代表语义。

那我们就不得不到处写下面的代码了:

if (function instanceof Average)
//perform average
else if(function instanceof Sum)
//perform sum
else if(function instanceof Max)
//perform max

上面的代码表明一个糟糕的设计,因为你到处都写了这个代码,这个设计很脆弱,以后很难改变。我猜如果数量函数是有限的并且函数的计算在一个地方,这可能是可以的,这取决于复杂性。

目前我所知道的是,要解决上述方法,唯一可能的方法是实现访问者模式,除了使用访问者模式之外,还有其他方法可以解决上述设计吗?

从visitor模式中我看到一个问题是visitor模式的accept方法没有返回值,如果accept()方法不能完全满足要求,有时这很不方便。

【问题讨论】:

  • 策略模式怎么样:en.wikipedia.org/wiki/Strategy_pattern
  • 觉得策略行不通,请检查我的 cmets 下面
  • 嗯好吧。但是,如果这些 if-elseif-else 块在您的项目中无处不在,则可能会将这些块重构为单独的实用程序方法
  • 看来没有简单的方法来实现更好的设计? if/else 分支是不可避免的?
  • 看起来像只要你坚持这个,除非你可以改变你Function类的代码

标签: java design-patterns instanceof visitor-pattern liskov-substitution-principle


【解决方案1】:

如果您在编译时仍然知道类型,则可以使用帮助类:

class Function {
}

class Average extends Function {
}

class Sum extends Function {
}

class Max extends Function {
}

class FunctionHelper {
  public Number calculate(Average a) {
    return null;
  }

  public Number calculate(Sum s) {
    return null;
  }

  public Number calculate(Max a) {
    return null;
  }

  public Number calculate(Function a) {
    return null;
  }

}

通常您会将帮助方法设为静态,但您不限于此 - 您可以使用多种风格的帮助类来做一些相当有趣的事情。

【讨论】:

  • 这有点危险,因为它依赖于编译时调度。除了我们想要避免的同一个 instanceof 之外,最后一个方法(使用 Function)是什么。
  • @Thilo - 我不会称之为危险 - 我确实注意到您必须在编译时知道类型。最后一个Function 实现相当于一个默认值,也许它会return 0;
  • 所以这将返回 0:Function x = new Sum(); calculate(x); 而这不会:Sum x = new Sum(); calculate(x);
  • @Thilo - 正确。因此我的评论如果您在编译时仍然知道类型...
  • 如果你有那种编译时耦合,你根本不需要任何标记对象。你可以只拥有一个方法calculate_sum() 和一个方法calculate_max()。我能看到这些 Function 对象(没有状态或行为)的唯一原因是能够在运行时打开它们。
【解决方案2】:

这些对象是纯粹的对象,无状态对象只是代表语义。

听起来你想使用枚举而不是常规对象。

然后您可以使用switch 语句并让编译器检查您是否处理了所有情况。

enum Function { Average, Sum, Max }

【讨论】:

  • 在我看来这并不能解决问题,因为您仍然需要执行切换并且它是高度不可扩展的。
  • @MichalBorek:没有任何函数上的方法,你将如何避免切换?
  • 您也可以将calculate 方法添加到枚举中。
  • 是的。但是 OP 不想要方法(出于某种未知原因)。如果方法避免了切换(他们应该这样做),则不再需要枚举。
【解决方案3】:

我刚刚偶然发现了Chain of Responsibilty pattern,它可能会解决您的问题:

为此,我们为每个从公共基类开始的函数创建一个处理程序类

public abstract class FunctionHandler {
    private FunctionHandler nexthandler = null;

    protected abstract boolean isConditionMet(Function function);

    protected abstract void calculate(Function function);

    public void handleFunction(Function function) {
        if(function == null) {
            return;
        }

        if (isConditionMet(function)) {
            calculate(function);
        } else {
            if (nexthandler != null) {
                nexthandler.handleFunction(function);
            }
        }
    }

    public FunctionHandler setNexthandler(FunctionHandler nexthandler) {
        this.nexthandler = nexthandler;
        return nexthandler;
    }
}

接下来我们创建具体的处理程序:

public class Averagehandler extends FunctionHandler {
    @Override
    protected boolean isConditionMet(Function function) {
        return function instanceof Average;
    }

    @Override
    protected void calculate(Function function) {
        // do average stuff
    }
}

public class SumHandler extends FunctionHandler {
    @Override
    protected boolean isConditionMet(Function function) {
        return function instanceof Sum;
    }

    @Override
    protected void calculate(Function function) {
        // do sum stuff
    }
}

public class MaxHandler extends FunctionHandler {
    @Override
    protected boolean isConditionMet(Function function) {
        return function instanceof Max;
    }

    @Override
    protected void calculate(Function function) {
        // do max stuff
    }
}

现在寻找一个很好的中心位置来将你的链放在一起(例如一个实用程序类)

public final class FunctionUtil {
    public static final FunctionHandler HANDLER;

    static {
        HANDLER = new Averagehandler();
        HANDLER.setNexthandler(new SumHandler()).setNexthandler(new MaxHandler());
    }
}

现在您可以开始用调用 FunctionUtil.HANDLER.handleFunction(function); 替换您的 if-else-blocks

【讨论】:

  • 谢谢,宏,不过,我只是看了你的建议,我不太明白为什么我们需要为这个问题引入责任链?仅仅使用每个处理程序对象来处理每个函数还不够吗?例如。我在想如果使用这种方法会是这样的
  • 一个处理程序就足够了。责任链使您无需决定哪个处理程序对手头的Function 对象是正确的。它只是获取对象并将其传递给处理程序,除非有权处理实际类型的函数
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2018-04-11
  • 1970-01-01
  • 1970-01-01
  • 2021-08-20
  • 2022-10-04
  • 1970-01-01
  • 2013-03-02
相关资源
最近更新 更多