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 {…}
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
Canineitself inherits fromCanoideaorCarnivoraor 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
InputIteratorandForwardIteratorin 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 withRandomAccessIteratorwhen 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 aCaninecompared 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.