Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I avoid breaking the Liskov substitution principle with a class that implements multiple interfaces?

Given the following class:

class Example implements Interface1, Interface2 {     ... } 

When I instantiate the class using Interface1:

Interface1 example = new Example(); 

...then I can call only the Interface1 methods, and not the Interface2 methods, unless I cast:

((Interface2) example).someInterface2Method(); 

Of course, to make this runtime safe, I should also wrap this with an instanceof check:

if (example instanceof Interface2) {     ((Interface2) example).someInterface2Method(); } 

I'm aware that I could have a wrapper interface that extends both interfaces, but then I could end up with multiple interfaces to cater for all the possible permutations of interfaces that can be implemented by the same class. The Interfaces in question do not naturally extend one another so inheritance also seems wrong.

Does the instanceof/cast approach break LSP as I am interrogating the runtime instance to determine its implementations?

Whichever implementation I use seems to have some side-effect either in bad design or usage.

like image 770
jml Avatar asked Jan 14 '19 15:01

jml


People also ask

How do you not violate the Liskov Substitution Principle?

There are several possible ways: Returning an object that's incompatible with the object returned by the superclass method. Throwing a new exception that's not thrown by the superclass method. Changing the semantics or introducing side effects that are not part of the superclass's contract.

What is the most accurate Liskov Substitution Principle?

Simply put, the Liskov Substitution Principle (LSP) states that objects of a superclass should be replaceable with objects of its subclasses without breaking the application. In other words, what we want is to have the objects of our subclasses behaving the same way as the objects of our superclass.

What is the most accurate example of the Liskov Substitution Principle What is the most accurate example of the Liskov Substitution Principle?

Ostrich is a bird, but it can't fly, Ostrich class is a subtype of class Bird, but it shouldn't be able to use the fly method, that means we are breaking the LSP principle.

Which OOP principle is important for liskov substitution?

The Open/Closed Principle To understand the Liskov Substitution Principle, we must first understand the Open/Closed Principle (the “O” from SOLID). The goal of the Open/Closed principle encourages us to design our software so we add new features only by adding new code.


2 Answers

I'm aware that I could have a wrapper interface that extends both interfaces, but then I could end up with multiple interfaces to cater for all the possible permutations of interfaces that can be implemented by the same class

I suspect that if you're finding that lots of your classes implement different combinations of interfaces then either: your concrete classes are doing too much; or (less likely) your interfaces are too small and too specialised, to the point of being useless individually.

If you have good reason for some code to require something that is both a Interface1 and a Interface2 then absolutely go ahead and make a combined version that extends both. If you struggle to think of an appropriate name for this (no, not FooAndBar) then that's an indicator that your design is wrong.

Absolutely do not rely on casting anything. It should only be used as a last resort and usually only for very specific problems (e.g. serialization).

My favourite and most-used design pattern is the decorator pattern. As such most of my classes will only ever implement one interface (except for more generic interfaces such as Comparable). I would say that if your classes are frequently/always implementing more than one interface then that's a code smell.


If you're instantiating the object and using it within the same scope then you should just be writing

Example example = new Example(); 

Just so it's clear (I'm not sure if this is what you were suggesting), under no circumstances should you ever be writing anything like this:

Interface1 example = new Example(); if (example instanceof Interface2) {     ((Interface2) example).someInterface2Method(); } 
like image 183
Michael Avatar answered Sep 19 '22 06:09

Michael


Your class can implement multiple interfaces fine, and it is not breaking any OOP principles. On the contrary, it is following the interface segregation principle.

It is confusing why would you have a situation where something of type Interface1 is expected to provide someInterface2Method(). That is where your design is wrong.

Think about it in a slightly different way: Imagine you have another method, void method1(Interface1 interface1). It can't expect interface1 to also be an instance of Interface2. If it was the case, the type of the argument should have been different. The example you have shown is precisely this, having a variable of type Interface1 but expecting it to also be of type Interface2.

If you want to be able to call both methods, you should have the type of your variable example set to Example. That way you avoid the instanceof and type casting altogether.

If your two interfaces Interface1 and Interface2 are not that loosely coupled, and you will often need to call methods from both, maybe separating the interfaces wasn't such a good idea, or maybe you want to have another interface which extends both.

In general (although not always), instanceof checks and type casts often indicate some OO design flaw. Sometimes the design would fit for the rest of the program, but you would have a small case where it is simpler to type cast rather than refactor everything. But if possible you should always strive to avoid it at first, as part of your design.

like image 28
jbx Avatar answered Sep 21 '22 06:09

jbx