空接口(抽象类)用于对象的逻辑分组以避免 LSP 违规
在现实世界中,正方形是长方形,但对于程序来说,情况并非如此(LSP原理)。创建一个空白接口来将对象逻辑分组在一起是否被认为是反模式/糟糕的编程?我的想法是,使用空白界面就不可能违反其正下方的 LSP。下面是人为的例子。
Abstract class Canine{ }
Dog extends Canine {…}
Wolf extends Canine {…}
In the real world a square is a rectangle, but to a program this is not the case(LSP principle). is it considered an anti-pattern / poor programming to create a blank interface to logically group objects together? My thought is with a blank interface it would be impossible to violate the LSP
at the level directly beneath it. Contrived example below.
Abstract class Canine{ }
Dog extends Canine {…}
Wolf extends Canine {…}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
取决于它的用途。
空接口不施加任何语法要求,因为没有要实现的方法。
但接口可能会被记录为强加语义要求,特别是如果
Canine
本身继承自Canoidea
或Carnivora
或其他。这使得空接口可能有用,因为它提供了自己的保证。所有食肉目动物都有耳朵[*],但 Canoidea 的部分特征在于其耳朵的某些属性,这些属性不能像添加方法那样简单地表达出来,因此实现该接口成为一个承诺:“除了作为食肉目动物之外,我的耳朵就是这样的”。这意味着违反 LSP 不再是不可能的。您可能会意外地编写一个带有返回 12 的重写函数的子类,这对于 Carnivora 是允许的,但 Canine 的语义表明它不会返回超过 10。
仅添加语义的接口不一定适合使用,因为有些错误很容易犯。一个例子是 C++ 中的
InputIterator
和ForwardIterator
之间的区别(当然,这些是模板概念,而不是继承的接口)。如果你用错误的标签标记你的类,编译器永远不会注意到。但是,如果您使用RandomAccessIterator
标记您的类,而实际上它只是一个ForwardIterator
,那么当有人尝试使用时,编译器将会注意到其上的operator+
,因为operator+是RandomAccessIterator的一部分,但不是ForwardIterator的一部分。没有语义的空接口是没有意义的,因此可能毫无用处。我能想到的唯一用途是作为一种变体 - 一些狡猾的代码可以测试该对象是否是“狗”或“狼”(或其他东西)的实例,并相应地执行不同的操作。这可能不是一个好的用途,因为它首先可能不是好的代码。如果可用的话,您也可以使用像
Object
这样的终极超类,因为无论哪种方式,代码都必须处理它无法识别和无法处理的类型。 “狐狸到底是什么,我这里不能使用狐狸”并不比“Vector 到底是什么,我这里不能使用 Vector”好还是坏,所以如果你理解的是狗和狼,与接受Object
相比,接受Canine
并没有真正的优势。它们都是允许狗、狼以及其他任何人选择放入层次结构的变体。[*] 是的,甚至是海豹。他们只是没有耳廓。
Depends what it's for.
An empty interface imposes no syntactic requirements, since there are no methods to implement.
But the interface might be documented to impose semantic requirements, especially if
Canine
itself inherits fromCanoidea
orCarnivora
or whatever. This makes the empty interface potentially useful since it provides guarantees of its own. All Carnivora have ears[*], but Canoidea are characterized in part by some property of their ears that isn't easily expressed as simply as adding a method, so implementing the interface becomes a promise: "in addition to being a Carnivora, my ears behave like that".That means it's no longer impossible to violate the LSP. You could accidentally write a subclass with an overridden function that returns 12, which is permitted for Carnivora, but the semantics of Canine say that it won't return more than 10.
Interfaces that add only semantics aren't necessarily great to work with, since there are certain mistakes that it's easy to make. An example is the difference between
InputIterator
andForwardIterator
in C++ (although of course those are template concepts, not inherited interfaces). If you tag your class with the wrong one, the compiler will never notice. But if you tag your class withRandomAccessIterator
when really it's only aForwardIterator
, then the compiler will notice, when someone tries to useoperator+
on it, sinceoperator+
is part of RandomAccessIterator but not ForwardIterator.An empty interface with no semantics is meaningless, and hence probably useless. The only use for it I can think of is as a kind of variant - some dodgy code could test whether the object is an instance of "Dog" or "Wolf" (or something else), and do different things accordingly. That's probably not a good use since it's probably not good code in the first place. You might as well use an ultimate superclass like
Object
, if available, since either way the code is going to have to cope with types that it doesn't recognise and can't handle. "What on earth is a Fox, I can't use a Fox here" is no better or worse than "What on earth is a Vector, I can't use a Vector here", so if what you understand is dogs and wolves, there's no real advantage to accepting aCanine
compared with accepting anObject
. They're both variants which allow Dogs, Wolves, plus anything else someone chooses to slot into the hierarchy.[*] Yes, even seals. They just don't have pinnae.
不,它不会破坏 LSP,因为基类无法做任何事情,因此任何类都不可能违反 LSP。
我有时确实使用空接口将类分组在一起。我这样做是为了表明一些意图。例如,我有一个名为
IMessage
的空接口,它用于表明类的目的只是在我的 pub/sub 框架中作为消息进行处理。 (并且只有实现该接口的类才允许在其中传输)。对我来说,这有助于确保课程遵循单一职责原则。 (与不实现任何接口的消息类相比)
No it will not break LSP since there's nothing that can be done with the base, and therefore it's not possible for any of the classes to violate LSP.
I do sometime use empty interfaces to group classes together. I do so to show some intent. For instance, I got an empty interface called
IMessage
which is used to show that the purpose of a class is only to be handled as a message in my pub/sub framework. (And only classes which implements the interface are allowed to be transported in it).For me, it helps to make sure that classes follow single responsibility principle. (compared to a message class which do not implement any interfaces)
在您的情况下,这对我们有什么帮助。我们有一个抽象类 Rectangle?是什么阻止我们声称正方形扩展了矩形?
In your scenario how does this help us. We have an abstract class Rectangle? What is it that prevent us from claiming that Square extends Rectangle?