两个具有相同签名的接口


13

我正在尝试为纸牌游戏建模,其中纸牌具有两项重要功能:

首先是效果。这些是您玩纸牌时对游戏状态所做的更改。效果界面如下:

boolean isPlayable(Player p, GameState gs);
void play(Player p, GameState gs);

而且,当且仅当您能够支付费用且其所有效果均是可玩的时,您才可以考虑使用该卡。像这样:

// in Card class
boolean isPlayable(Player p, GameState gs) {
    if(p.resource < this.cost) return false;
    for(Effect e : this.effects) {
        if(!e.isPlayable(p,gs)) return false;
    }
    return true;
}

好的,到目前为止,非常简单。

卡上的另一组功能是异能。这些功能是对游戏状态的更改,您可以随意激活它们。当提出这些接口时,我意识到他们需要一种确定是否可以将其激活的方法,以及一种用于实现激活的方法。最终被

boolean isActivatable(Player p, GameState gs);
void activate(Player p, GameState gs);

我认识到,与调用它的异常“激活”,而不是“玩”,Ability并且Effect有相同的签名。


具有具有相同签名的多个接口是否不好?我应该简单地使用一个,并拥有两组相同的接口吗?因此:

Set<Effect> effects;
Set<Effect> abilities;

如果是这样,如果它们变得不相同(随着更多功能的发布),我应该采取哪些重构步骤,特别是如果它们之间存在分歧(即它们都获得了彼此不应该获得的东西,而不是仅仅获得一个)另一个是完整的子集)?我特别担心的是,一旦发生变化,将它们结合起来将是不可持续的。

精美印刷品:

我认识到这个问题是由游戏开发引起的,但是我认为这是在非游戏开发中同样容易出现的问题,尤其是当试图在一个应用程序中容纳多个客户的业务模型时,我所做的每个项目都具有不止一个业务影响力...而且,所使用的代码段是Java代码段,但这可以很容易地应用于多种面向对象的语言。


只要跟随KISSYAGNI,您就可以了。
伯纳德

2
甚至出现此问题的唯一原因是,由于Player和GameState参数的范围非常广且不受限制,因此您的函数无法访问太多状态。
拉尔斯·维克伦德

Answers:


18

仅仅因为两个接口具有相同的协定,并不意味着它们是相同的接口。

Liskov替代原则指出:

令q(x)是关于类型T的对象x的一个可证明的属性。那么q(y)应该是对于类型S的对象y的可证明的,其中S是T的子类型。

或者,换句话说:接口或超类型的所有正确子对于其所有子类型都正确。

如果我正确地理解了您的描述,那么能力不是效果,效果不是能力。如果其中一个改变了合同,那么另一个就不太可能随之改变。我认为没有充分的理由尝试将它们彼此绑定。


2

摘自Wikipedia:“ 接口通常用于定义不包含数据但公开定义为方法的行为的抽象类型 ”。在我看来,接口用于描述行为,因此,如果您具有不同的行为,则具有不同的接口是有意义的。阅读您的问题时,给我的印象是您在谈论不同的行为,因此不同的界面可能是最好的方法。

您自己说的另一点是,如果其中一种行为发生了变化。那么当您只有一个界面时会发生什么?


1

如果您的纸牌游戏规则在“效果”和“能力”之间进行区分,则需要确保它们是不同的界面。这样可以避免您在需要使用另一个的情况下意外使用其中一个。

也就是说,如果它们极其相似,则可以从一个共同的祖先那里获得它们。仔细考虑:你有理由相信,“效果”和“能力”永远必然有相同的接口?如果将元素添加到effect接口,是否期望将相同的元素添加到ability接口?

如果是这样,则可以将这样的元素放在feature它们均从中派生的公共接口中。如果不是这样,那么您不应该尝试统一它们-您将浪费时间在基本接口和派生接口之间移动内容。但是,由于除了“不要重复自己”之外,您实际上不打算将公用基本接口用于任何其他用途,因此可能没有太大区别。而且,如果您坚持这一意图,我的猜测是,如果您一开始做出的选择错误,那么进行重构以稍后进行修复可能相对简单。


0

您所看到的基本上是类型系统有限的表达能力的产物。

从理论上讲,如果您的类型系统允许您准确指定这两个接口的行为,则它们的签名将有所不同,因为它们的行为是不同的。实际上,类型系统的表达性受到诸如“停顿问题”和赖斯定理之类的基本限制的限制,因此,并非行为的所有方面都能得到表达。

现在,不同类型的系统具有不同的表达度,但是总会有一些无法表达的东西

例如:具有不同错误行为的两个接口在C#中可能具有相同的签名,但在Java中却没有(因为Java异常是签名的一部分)。行为仅在副作用方面有所不同的两个接口在Java中可能具有相同的签名,但在Haskell中将具有不同的签名。

因此,对于不同的行为,您最终都有可能获得相同的签名。如果您认为能够在一个名义上的范围之外区分这两种行为很重要,那么您需要切换到一个更多(或不同)的表现型系统。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.