Aren't we breaking the Single Responsibility Principle with this super-mega-god-interface?
Not exactly, because the interface isn't doing anything. It has no responsibilities.
You are breaking Liskov Substitution Principle and Interface Segregation Principle though.
LSP because you're writing methods that do nothing, just to make it fit the interface (ie. What is true of IDrawable -- that you can move it -- is not true of Wall).
ISP because you'll have situations like a trade method, which should have access to Buy and Sell methods, but will also have access to Draw.
Are both approaches valid against the Liskov's Substitution Principle? I think it's broken in the first case because the post-conditions are broken due to some methods don't do anything. Anyway, in the second approach I'm not sure as well because of the interfaces inheritance tree
It's just as broken in the second case as the first.
Edit: On second thoughts, it isn't. But it is still broken. It doesn't seem automatically true that everything that could have a Move method will also have a Draw method. It might be true in your case but, if you later find yourself wanting to pass an object with a Move method into a method that receives an IMovable, you're going to have to implement Draw on that object, even if you don't need it.
And it gains you nothing over a third solution ...
Maybe, another alternative could be the definition of basic Drawable objects (with 'click' and 'draw' methods). Thus, we may take advantage of some kind of mechanism like the Decorator pattern in order to add behaviors dynamically. But how?
The best alternative is to segregate your interfaces fully. You can still have objects with lots of methods (but Dependency-Inversion Principle and Single Responsibility Principle dictate you must push the logic down into services), but they only have to implement the interfaces they need. Wall shouldn't implement IMovable, if it can't be moved.
public class Wall : IDrawable
public class Furniture : IDrawable, IMovable, ISellable
public class Window : IDrawable, IOpenable
Nothing here dictates that any of those interfaces must derive from another.
public interface IDrawable
{
void Draw(Canvas canvas);
}
public interface IMovable
{
void MoveTo(int x, int y);
}
etc
You should then manage your list of IDrawables, which you can only be sure implement Draw(), and cast to see if other methods are available.
For example,
ITradable tradable = drawable as ITradable;
if (tradable != null)
{
tradable.Trade(buyer, seller);
}
You're going to have to do the above in your option 2 anyway, so why tie everything to IDrawable?
If we use a language without interfaces such as JavaScript or Python, how can we deal with this problem?
OO languages tend to support at least one of three things:
Prototypical languages, such as Javascript, support ... well, prototypes, which allow for a slightly modified version of duck-typing (in prototypes, you generally ask if a method exists before calling it).
Other paradigms generally handle these issues in their own way.
Both are correct
The way I read it, the purpose of ISP (Interface Segregation Principle) is to keep interfaces small and focused: all interface members should have very high cohesion. Both definitions are intended to avoid "jack-of-all-trades-master-of-none" interfaces.
Interface segregation and SRP (Single Responsibility Principle) have the same goal: ensuring small, highly cohesive software components. They complement each other. Interface segregation ensures that interfaces are small, focused and highly cohesive. Following the single responsibility principle ensures that classes are small, focused and highly cohesive.
The first definition you mention focuses on implementers, the second on clients. Which, contrary to @user61852, I take to be the users/callers of the interface, not the implementers.
I think that your confusion stems from the a hidden assumption in the first definition: that the implementing classes are already following the single responsibility principle.
To me the second definition, with the clients as the callers of the interface, is a better way of getting to the intended goal.
Segregating
In your question you state:
since this way my MyClass is able to implement only the methods it
needs ( D() and C() ), without being forced to also provide dummy
implementations for A(), B() and C():
But that is turning the world upside down.
- A class implementing an interface does not dictate what it needs in the interface it is implementing.
- The interfaces dictate what methods an implementing class should provide.
- The callers of an interface really are the ones that dictate what functionality they need the interface to provide for them and thus what an implementer should provide.
So when you are going to split IFat
into smaller interface, which methods end up in which ISmall
interface should be decided based on how cohesive the members are.
Consider this interface:
interface IEverythingButTheKitchenSink
{
void DoDishes();
void CleanSink();
void CutGreens();
void GrillMeat();
}
Which methods would you put in ICook
and why? Would you put CleanSink
together with GrillMeat
just because you happen to have a class that does just that and a couple of other things but nothing like any of the other methods? Or would you split it into two more cohesive interfaces, such as:
interface IClean
{
void DoDishes();
void CleanSink();
}
interface ICook
{
void CutGreens();
void GrillMeat();
}
Interface declaration note
An interface definition should preferably be on its own in a separate unit, but if it absolutely needs to live with either caller or implementer, it should really be with the caller. Otherwise the caller gets an immediate dependency on the implementer which is defeating the purpose of interfaces altogether. See also: Declaring interface in the same file as the base class, is it a good practice? on Programmers and Why should we place interfaces with classes that use them rather than those that implement them? on StackOverflow.
Best Answer
Yes and no. Some things are mixed up here. And some are omitted.
mixed up stuff
LSP according to wikipedia
LSP is not concerned about two sibling types
Rectangle
andSquare
being interchangeable with each other. It's concerned about interchangeability of asupertype
and one of itssubtype
.LSP in code is basically this:
In a sense, you could say that
Rectangle
andSquare
are interchangeable here, both being possible substitutions forShape
, but this is merely a result of LSP relationships ofRectangle
andSquare
to their superclassShape
respectively.Every type has an individual LSP relationship to each of its supertypes. So given
Square : Shape, IDraw
andRectangle : Shape, IMove
the above is still valid:What you are likely referring to as a sign of non-interchangeability of
Rectangle
andSquare
is that you cannot do this:But there's no
supertype
-subtype
relationship betweenIDraw
andRectangle
/IMove
andSquare
respectively, which means LSP isn't nullified here, it simply doesn't apply. Expecting interchangeability here is "begging the question". LSP still applies to eachsupertype
-subtype
relationship individually:Just because
Rectangle
andSquare
have one common supertypeShape
, which according to LSP they are each individually interchangeable with, does not (necessarily) mean they are interchangeable with each other.This sort of LSP interchangeability explained above is fulfilled by the type-system already, because every subtype is also all its supertypes. There's more to this than just types.
comment
The LSP relationship has a "direction". You can use a
subtype
where asupertype
is expected, but not the other way round.If you have a
Rectangle
object in place somewhere in your code and you useDraw
ofIDraw
, then you are correct that you could not substitute that withShape
object, "since shape doesn't useIDraw
". This expectation however is unreasonable or simply wrong in terms of LSP. LSP is not suggesting that you can do this.Again, you are begging the question by asking "how do I abide by LSP if I do something that doesn't".
As a rule of thumb: You cannot break LSP with just the type system, because the hierarchical type system is equivalent to LSP.
omitted stuff
The actually important thing about LSP is not types, but behaviour. Your example is entirely free from any functionality and concentrates on compatibility of types. All your methods are empty.
There's always an "implicit" part to a type definition. Sometimes this is referred to as an "implicit contract". This includes things like:
Here's a modified example of your code:
This new version of
IDraw
demands that you update the drawing buffer to be retrieved later.disclaimer: Whether this sort of interface design is a good idea or not is questionable. It might be perfectly fine or it might be better to have only one method:
DrawingBuffer Draw();
For the sake of this explanation, let's assume it is the way to go.Now - strictly speaking - the code as is breaks LSP, because it is not updating the buffer:
And it's the same with the other one:
Of course, if actually updating the buffer is optional, this is might be ok to opt-out for implementation of special cases, like if the shape hasn't changed.
But when it comes to Exceptions, you might accidentally opt-in, where you shouldn't:
Depending on your programming language, types of
_x
,screenSize
and_somePrivateVariableThatMightBeZero
and the value of the latter, the above code might throw an exception due to a division by 0;This breaks the contract of
IMove
and thus LSP. A user ofIMove
would expect to be able to callMove()
without having to deal with (likely implementation specific) exceptions being thrown.