【问题标题】:What to do if classes with same interface having similar but different method signature?如果具有相同接口的类具有相似但不同的方法签名怎么办?
【发布时间】:2015-10-23 23:53:06
【问题描述】:

如果具有相同接口的类具有相似但不同的方法签名怎么办?

假设我有一个项目来计算不同的成本(最后得到总成本)。

在我的程序中,有几个计算器类,分别是ACostCalculatorBCostCalculator等等。当调用calculate() 方法来计算成本时,成本容器也会传递给这些成本计算器。在一个好的场景中,我可以为每个成本计算器制作一个CostCalculator 接口。

但是,不同成本的计算需要不同的资源。在我目前的程序中,它是这样的:

//getResource() are costly method while several costs need this. So do it outside calculate() method.
ResourceA resourceA = getResourceA(); 
ResourceB resourceB = getResourceB();

CostContainer costContainer = new CostContainer();
CostCalculator aCostCalculator = new ACostCalculator();
...
CostCalculator eCostCalculator = new ECostCalculator();

aCostCalculator.calculate(costContainer);
bCostCalculator.calculate(costContainer)
cCostCalculator.calculate(costContainer, resourceA);
dCostCalculator.calculate(costContainer, resourceA);
eCostCalculator.calculate(costContainer, resourceA, resourceB);

如果签名完全一样,我可以方便地循环一次。但是,由于它们相似但不同,我什至无法做出一个好的界面。

我不确定是否有这样做的好方法。我能想到的是将所有calculate() 方法推广到

calculate(CostContainer costContainer, List<Object> resources);

有什么想法吗?感谢您的回答。

【问题讨论】:

  • ...或可变参数而不是列表。

标签: java design-patterns interface


【解决方案1】:

通用接口的不同签名问题听起来很像Adapter design pattern (object adapter variant) 解决的问题:

根据您的情况,您只能使用不合格计算器的适配器。实际上只有两种类型的适配器,Type1 用于签名(costContainer, resourceA) 和Type2 用于签名(costContainer, resourceA, resourceB)。使用您的示例:

适配器的优点是它是一种已知的设计模式(由 GoF 于 1995 年发布),它允许最终的 calculate(...) 方法具有不同的签名。如果上下文发生变化(例如资源变化),适配器可以动态更新。

缺点显然是额外的类,间接等。它比选择的答案更复杂,但更灵活,特别是如果你不能修改适配者的API。

【讨论】:

  • 感谢您的回答。如果资源和成本计算器的生命周期不匹配,则来自 lesmana 的回答不起作用,例如成本计算器是一个单例。适配器模式可以在所有情况下应用吗?我想是的(假设成本计算器是线程安全的)?
  • @MichaelW.Patterns 始终是它们要解决的问题级别的通用解决方案。将其应用于代码是确定的唯一方法。客户端代码实际上只看到适配器。只要它们被正确地实例化和管理,适配者是否是单例并不重要。如果您需要动态更改适配器的上下文,您可以添加例如setResourceA()。该模式只为您提供标准接口。你必须注意其余的(丑陋的)细节。
【解决方案2】:

这是您实际与泛型一起使用的东西:

    Resource resourceA = new ResourceA();
    Resource resourceB = new ResourceB();
    CostContainer costContainer = new CostContainer();
    CostCalculator<Resource> costCalculatorA = new ACostCalculator();
    costCalculatorA.calculate(costContainer,resourceA,resourceB);
    CostCalculator<Resource> costCalculatorB = new BCostCalculator();
    costCalculatorB.calculate(costContainer,resourceA);


interface Resource {
    //Your code
}

class ResourceA implements Resource {
    //Your code
}

class ResourceB implements Resource {
    //Your code
}

class CostContainer {
    //Your code
}

interface CostCalculator<T extends Resource> {
    void calculate(CostContainer costContainer, T... resources);
}

class ACostCalculator implements CostCalculator<Resource>{

    @Override
    public void calculate(CostContainer costContainer, Resource... resources) {
        System.out.println("Test");
    }
}

class BCostCalculator implements CostCalculator<Resource>{

    @Override
    public void calculate(CostContainer costContainer, Resource... resources) {
        System.out.println("Test2");
    }
}

【讨论】:

  • 感谢您的回答。但是,与 Glorfindel 的回答存在类似的缺陷。
【解决方案3】:

你可以使用variadic arguments:

public interface CostCalculatorInterface {
    public void calculate(CostContainer container, Object... resources);
}

(或将Object 替换为ResourceAResourceB 的另一个超类)。

在实现接口的类中,resources 将是一个Object[],因此您可以将它们称为resources[0]resources[1] 等等。

【讨论】:

  • 如果您传递错误数量的资源,可能会引发异常
  • 感谢您的回答。类型安全是一个问题,也是为了可读性。另一个程序员可能需要花费一些精力来找出“资源[0]”是什么。
  • 这就是为什么我不认为我的 List(或 List)解决方案是一个好的解决方案。
【解决方案4】:

如果资源在计算器的生命周期内保持不变:将资源传递给计算器的构造函数。

ResourceA resourceA = getResourceA(); 
ResourceB resourceB = getResourceB();

CostContainer costContainer = new CostContainer();

CostCalculator aCostCalculator = new ACostCalculator();
CostCalculator bCostCalculator = new BCostCalculator();
CostCalculator cCostCalculator = new CCostCalculator(resourceA);
CostCalculator dCostCalculator = new DCostCalculator(resourceA);
CostCalculator eCostCalculator = new ECostCalculator(resourceA, resourceB);

aCostCalculator.calculate(costContainer);
bCostCalculator.calculate(costContainer);
cCostCalculator.calculate(costContainer);
dCostCalculator.calculate(costContainer);
eCostCalculator.calculate(costContainer);

【讨论】:

  • 这是迄今为止唯一的类型安全解决方案。即使参数在计算器的生命周期内不是静态的,您也可以使用包装类实现类似的机制。
  • 嗯,它看起来非常有效和干净,同时适合我的用例。
  • 已经相当漂亮和简单了。但是让我们看看是否有人可以给出另一个天才的答案
  • @lesmana 使 CostContainer 成为依赖项而不是将其传递给 calculate 方法将是 IMO 更好的选择。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-01-29
  • 1970-01-01
  • 2011-11-23
  • 2011-02-19
相关资源
最近更新 更多