空接口(抽象类)用于对象的逻辑分组以避免 LSP 违规

发布于 2024-11-29 22:44:01 字数 307 浏览 1 评论 0原文

在现实世界中,正方形是长方形,但对于程序来说,情况并非如此(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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

黑白记忆 2024-12-06 22:44:01

取决于它的用途。

空接口不施加任何语法要求,因为没有要实现的方法。

但接口可能会被记录为强加语义要求,特别是如果Canine本身继承自CanoideaCarnivora或其他。这使得空接口可能有用,因为它提供了自己的保证。所有食肉目动物都有耳朵[*],但 Canoidea 的部分特征在于其耳朵的某些属性,这些属性不能像添加方法那样简单地表达出来,因此实现该接口成为一个承诺:“除了作为食肉目动物之外,我的耳朵就是这样的”。

这意味着违反 LSP 不再是不可能的。您可能会意外地编写一个带有返回 12 的重写函数的子类,这对于 Carnivora 是允许的,但 Canine 的语义表明它不会返回超过 10。

仅添加语义的接口不一定适合使用,因为有些错误很容易犯。一个例子是 C++ 中的 InputIteratorForwardIterator 之间的区别(当然,这些是模板概念,而不是继承的接口)。如果你用错误的标签标记你的类,编译器永远不会注意到。但是,如果您使用 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 from Canoidea or Carnivora 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 and ForwardIterator 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 with RandomAccessIterator when really it's only a ForwardIterator, then the compiler will notice, when someone tries to use operator+ on it, since operator+ 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 a Canine compared with accepting an Object. 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.

风启觞 2024-12-06 22:44:01

不,它不会破坏 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)

随心而道 2024-12-06 22:44:01

在您的情况下,这对我们有什么帮助。我们有一个抽象类 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?

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文