SOLID — Liskov Substitution Principle

OmerM
3 min readSep 24, 2022

--

Photo by seth schwiet on Unsplash

This principle corresponds to ‘L’ in the SOLID abbreviation.

This principle mainly says that every concrete class that implements an interface should be able to substitute with another class that implements the same interface.

This should also be valid for derived and parent classes (derived types should be substitutable for their base types).

All offered methods at base class/interface should be used at derived classes (if not then the wrong abstraction might be done) (most common given example is rubber duck extending real duck — rubber duck can quack and swim but can not fly so inherited fly method is not fully implemented).

And no new exceptions should be thrown by the subtype (for instance; UnsupportedOperationException at derived class for the not used methods from base class).

Clients should not know which specific subtype they are calling. If this is not possible somehow, this principle will be violated and possibly wrong abstraction was performed.

So, in summary, the inheritance/implementing interface should be used when this principle is applicable (this is an extension of the Open Closed Principle).

The sample codes can be seen below.

Sample

// DBObject Class

package sample;

public class DBObject {

private int id;
private String firstName;
private String lastName;

public DBObject() {

}

public DBObject(int id, String firstName, String lastName) {
super();
this.id = id;
this.firstName = firstName;
this.lastName = lastName;
}

public int getId() {
return id;
}

public void setId(int id) {
this.id = id;
}

public String getFirstName() {
return firstName;
}

public void setFirstName(String firstName) {
this.firstName = firstName;
}

public String getLastName() {
return lastName;
}

public void setLastName(String lastName) {
this.lastName = lastName;
}

}
// DB Interface

package sample;

public interface DB {

public void add(DBObject dbObj);
public DBObject read();
public void update(DBObject dbObj);
public void delete(DBObject dbObj);

}
// SQLEngine1 Class

package sample;

public class SQLEngine1 implements DB {

@Override
public void add(DBObject dbObject) {
...
}

@Override
public DBObject read() {
...
return new DBObject();
}

@Override
public void update(DBObject dbObject) {
...
}

@Override
public void delete(DBObject dbObject) {
...
}

}
// SQLEngine2 Class

package sample;

public class SQLEngine2 implements DB {

@Override
public void add(DBObject dbObject) {
...
}

@Override
public DBObject read() {
...
return new DBObject();
}

@Override
public void update(DBObject dbObject) {
...
}

@Override
public void delete(DBObject dbObject) {
...
}

}
// Application Class

package sample;

public class Application {

public static void main(String[] args) {
DBObject dbObj1 = new DBObject(1, "xyz", "def");
DBObject dbObj2 = new DBObject(2, "abc", "qwe");

// SQLEngine1 and SQLEngine2 classes
// can be replaceable with each other
// and the base interface.

DB db = new SQLEngine1();
db.add(dbObj1);
db.read();

db = new SQLEngine2();
db.update(dbObj2);
db.read();
}

}

This is the end of the article. Please check the next article which is about the Interface Segregation Principle.

Thanks for reading.

--

--

OmerM

Senior Software Engineer, sharing my knowledge and what i learn.