Liskov substitution principle java

Intent/Definition

The Liskov Substitution Principle represents the “L” of the five SOLID Principles of object-oriented programming to write well-designed code that is more readable, maintainable, and easier to upgrade and modify.

Rules of Thumb?

  • This principle applies to inheritance hierarchies and is just an extension of the Open Close Principle.
  • It means that we must make sure that new derived classes are extending the base classes without changing their original behavior. Basically, derived classes should never do less than their base class.
  • If a subtype of the supertype does something that the client of the supertype does not expect, then this is in violation of LSP. Imagine a derived class throwing an exception that the superclass does not throw, or if a derived class has some unexpected side effects. One has to consider that how the client programs are using the class hierarchy. Sometimes code refactoring is required to fix identified LSP violations.

Liskov’s Substitution Principle Example

Bad Code Design

LSP is violated here! Why? Logically WinampMediaPlayer only supports playing audio. So what’s wrong with the overriding playVideo method of a super class?

MediaPlayer.java

public class MediaPlayer < // Play audio implementation public void playAudio() < System.out.println("Playing audio. "); > // Play video implementation public void playVideo() < System.out.println("Playing video. "); > >

VlcMediaPlayer.java

public class VlcMediaPlayer extends MediaPlayer

Читайте также:  Css спрей как сделать

WinampMediaPlayer.java

public class WinampMediaPlayer extends MediaPlayer < // Play video is not supported in Winamp player public void playVideo() < throw new VideoUnsupportedException(); > >

VideoUnsupportedException.java

public class VideoUnsupportedException extends RuntimeException < private static final long serialVersionUID = 1 L; >

ClientTestProgram.java

public class ClientTestProgram < public static void main(String[] args) < // Created list of players List  MediaPlayer > allPlayers = new ArrayList < MediaPlayer > (); allPlayers.add(new VlcMediaPlayer()); allPlayers.add(new DivMediaPlayer()); // Play video in all players playVideoInAllMediaPlayers(allPlayers); // Well - all works as of now. :-) System.out.println("---------------------------"); // Now adding new Winamp player allPlayers.add(new WinampMediaPlayer()); // Again play video in all players & Oops it broke the program. :-( // Why we got unexpected behavior in client? --- Because LSP is violated in WinampMediaPlayer.java, // as it changed the original behavior of super class MediaPlayer.java playVideoInAllMediaPlayers(allPlayers); > /** * This method is playing video in all players * * @param allPlayers */ public static void playVideoInAllMediaPlayers(List < MediaPlayer > allPlayers) < for (MediaPlayer player: allPlayers) < player.playVideo(); > > >

Refer MediaPlayer.java superclass and its subclasses DivMediaPlayer.java, VlcMediaPlayer.java— Refer ClientTestProgram.java code, before adding «WinampMediaPlayer.java«

Good Code Design

Refer «MediaPlayer.java» super class and its sub class «AudioMediaPlayer.java» having play audio ability

Refer «DivMediaPlayer.java», «VlcMediaPlayer.java». Both extends «VideoMediaPlayer.java» for play audio and video ability.

So client program can substitute «VideoMediaPlayer.java» super type with «DivMediaPlayer.java» or «VlcMediaPlayer.java», but not with «WinampMediaPlayer.java»

Class diagram

MediaPlayer.java

public class MediaPlayer < // Play audio implementation public void playAudio() < System.out.println("Playing audio. "); > >

DivMediaPlayer.java

public class DivMediaPlayer extends VideoMediaPlayer < // DivMediaPlayer code goes here >

VlcMediaPlayer.java

public class VlcMediaPlayer extends VideoMediaPlayer < // VlcMediaPlayer code goes here >

VideoMediaPlayer.java

public class VideoMediaPlayer extends MediaPlayer < // Play video implementation public void playVideo() < System.out.println("Playing video. "); > >

WinampMediaPlayer.java

public class WinampMediaPlayer extends AudioMediaPlayer < // WinampMediaPlayer code goes here >

VideoUnsupportedException.java

public class VideoUnsupportedException extends RuntimeException < private static final long serialVersionUID = 1 L; >

ClientTestProgram.java

public class ClientTestProgram < public static void main(String[] args) < // Created list of video players List  VideoMediaPlayer > allPlayers = new ArrayList < VideoMediaPlayer > (); allPlayers.add(new VlcMediaPlayer()); allPlayers.add(new DivMediaPlayer()); // Play video in all players playVideoInAllMediaPlayers(allPlayers); // Well - all works as of now. :-) System.out.println("---------------------------"); // Now adding new Winamp player. If you uncomment below line, // it would give compile time error as can't add audio player in list of video players. // allPlayers.add(new WinampMediaPlayer()); > /** * This method is playing video in all players * * @param allPlayers */ public static void playVideoInAllMediaPlayers(List < VideoMediaPlayer > allPlayers) < for (VideoMediaPlayer player: allPlayers) < player.playVideo(); > > >

Источник

Liskov Substitution Principle

The Liskov Substitution Principle is one of the SOLID principles of object-oriented programming (Single responsibility, Open-closed, Liskov Substitution, Interface Segregation and Dependency Inversion). We have already written about the single responsibility principle, and these five principles combined are used to make object-oriented code more readable, maintainable and easier to upgrade and modify.

Liskov Substitution Principle states the following: “in a computer program, if S is a subtype of T, then objects of type T may be replaced with objects of type S (i.e., objects of type S may substitute objects of type T) without altering any of the desirable properties of that program (correctness, task performed, etc.)”. Simply said, any object of some class in an object-oriented program can be replaced by an object of a child class. In order to understand this principle better, we’ll make a small digression to briefly remind ourselves about the concept of inheritance and its properties, as well as subtyping, a form of polymorphism.

Inheritance, Polymorphism, Subtyping

Inheritance is a concept fairly simple to understand. It is when an object or a class are based on another object or class. When a class is “inherited” from another class, it means that the inherited class (also called subclass, or child class) contains all the characteristics of the superclass (parent class), but can also contain new properties. Let’s illustrate this with a common example: if you have a class Watch , you can inherit from that class to get a class PocketWatch . A pocket watch is still a watch, it just has some additional features. Another example would be a class called Woman with a child class called Mother . A mother is still a woman, with the addition of having a child.

This brings us to the next term we should explain, which is called polymorphism: objects can behave in one way in a certain situation, and in another way in some other situation. In object-oriented programming, this is called context-dependent behavior. To use the last example: a mother, when taking a walk with her child or attending a school parent’s meeting, will behave as a mother. But when she is out with her friends, at work, or simply doing errands, she will behave as a woman. (As you can see, this difference is not that strict.)

Subtyping is a concept that is not identical to polymorphism. However, the two are so tightly connected and fused together in common languages like C++, Java and C#, that the difference between them is practically non-existent. We will still give a formal definition of subtyping though for the sake of completeness. But the details will not be discussed in this article. “In programming language theory, subtyping (also subtype polymorphism or inclusion polymorphism) is a form of type polymorphism in which a subtype is a datatype that is related to another datatype (the supertype) by some notion of substitutability, meaning that program elements, typically subroutines or functions, written to operate on elements of the supertype can also operate on elements of the subtype. If S is a subtype of T, the subtyping relation is often written S

This brings us to the original theme of the article – the Liskov Substitution Principle.

Liskov Substitution Principle Examples

The Liskov substitution principle, written by Barbara Liskov in 1988, states that functions that reference base classes must be able to use objects of derived (child) classes without knowing it. It’s important for a programmer to notice that, unlike some other Gang of Four principles, whose breaking might result in bad, but working code, the violation of this principle will most likely lead to buggy or difficult to maintain code.

LiskovSubtitutionPrinciple_Simon

Let’s illustrate this in Java:

class TransportationDevice < String name; String getName() < . >void setName(String n) < . >double speed; double getSpeed() < . >void setSpeed(double d) < . >Engine engine; Engine getEngine() < . >void setEngine(Engine e) < . >void startEngine() < . >>
class Car extends TransportationDevice < @Override void startEngine() < . >>

There is no problem here, right? A car is definitely a transportation device, and here we can see that it overrides the startEngine() method of its superclass.

Let’s add another transportation device:

class Bicycle extends TransportationDevice < @Override void startEngine() /*problem!*/ >

Everything isn’t going as planned now! Yes, a bicycle is a transportation device, however, it does not have an engine and hence, the method startEngine() cannot be implemented. l

These are the kinds of problems that violation of Liskov Substitution Principle leads to, and they can most usually be recognized by a method that does nothing, or even can’t be implemented.

The solution to these problems is a correct inheritance hierarchy, and in our case we would solve the problem by differentiating classes of transportation devices with and without engines. Even though a bicycle is a transportation device, it doesn’t have an engine. In this example our definition of transportation device is wrong. It should not have an engine.

We can refactor our TransportationDevice class as follows:

class TransportationDevice < String name; String getName() < . >void setName(String n) < . >double speed; double getSpeed() < . >void setSpeed(double d) < . >>

Now we can extend TransportationDevice for non-motorized devices.

class DevicesWithoutEngines extends TransportationDevice < void startMoving() < . >>

And extend TransportationDevice for motorized devices. Here is is more appropriate to add the Engine object.

class DevicesWithEngines extends TransportationDevice < Engine engine; Engine getEngine() < . >void setEngine(Engine e) < . >void startEngine() < . >>

Thus our Car class becomes more specialized, while adhering to the Liskov Substitution Principle.

class Car extends DevicesWithEngines < @Override void startEngine() < . >>

And our Bicycle class is also in compliance with the Liskov Substitution Principle.

class Bicycle extends DevicesWithoutEngines < @Override void startMoving() < . >>

Conclusion

Object Oriented languages such as Java are very powerful and offer you as a developer a tremendous amount of flexibility. You can misuse or abuse any language. In the Polymorphism post I explained the ‘Is-A’ test. If you’re writing objects which extend classes, but fails the ‘Is-A’ test, you’re likely violating the Liskov Substitution Principle.

Источник

Оцените статью