공부/SOLID

Liskov Substitution Principle(리스코프 치환 원칙 )

ironk.im 2021. 6. 1. 23:21
반응형

Liskov Substitution Principle은 서브타입이 부모타입으로 대체될 수 있어야 한다는 것이다.

이렇게 보면 이해하기가 조금 난해할 수 있다.

간단히 설명하면 상속 또는 인터페이스를 구현하는 어떤 클래스가 상위 클래스에 대체될 수 있어야하고, 그 때 일관된 동작을 보장해야한다는 것이다.

즉, 일관성과 안정성을 보장해야한다는 원칙이다.

Liskov Substitution Principle을 준수하지 않는 코드

public class Bird {
	
	public void fly() {
		System.out.println("fly high!");
	}
}

public class Penguin extends Bird {
	
	@Override
	public void fly() {
		throw new IllegalStateException("I can not fly!");
	}
}

public class Zoo {
	public static void main(String[] args) {
		
		System.out.println("새들이 나는 공연");
		
		List<Bird> birds = new ArrayList<>();
		birds.add(new Bird());
		birds.add(new Penguin());
		
		birds.forEach(Bird::fly);
	}
}

Liskov Substitution 원칙을 준수 하지 않은 코드를 보면 Bird를 보면 해당 클래스는 fly라는 메소드가 있고 하늘을 나는 기능을 수행하는 메소드임을 알 수 있다.

그러나 Bird를 상속하는 자식 클래스인 Penguin은 Bird를 상속하고 있어 fly 메소드가 있어 하늘을 나는 기능을 수행할 것이라고 예상되나 Penguin은 하늘을 날 수 없다.

이러한 경우 Penguin은 부모 클래스인 Bird를 상속 하였지만 Bird클래스를 대체하여 일관적인 기능(fly)를 제공할 수 없다.

Bird를 사용하는 부분이 Penguin으로 대체 되어도 정상 동작 되어야하나 Penguin은 날지 못하기 때문에 이러한 기능을 제공할 수 없다

Liskov Substitution Principle을 준수하는 코드

public interface Flyable {
	void fly();
}

public class Bird {
	public void sing() {
		System.out.println("Singing");
	}
}

public class Magpie extends Bird implements Flyable {
	@Override
	public void fly() {
		System.out.println("I can fly!");
	}
}

public class Penguin extends Bird {
	
}

public class Zoo {
	public static void main(String[] args) {
		System.out.println("새들이 나는 공연");
		
		List<Bird> allBirds = List.of(new Magpie(), new Penguin());

    System.out.println("새들 노래");
    allBirds.forEach(Bird::sing);

    System.out.println("날 수 있는 새들 공연");
    allBirds.stream()
        .filter(b -> b instanceof Flyable)
        .map(b -> (Flyable) b)
        .forEach(Flyable::fly);
  }
}

Flyable 인터페이스로 fly기능을 분리하고 fly가 필요한 경우 해당 기능을 구현하도록 함으로서 Liskov Substitution Principle을 준수하였다고 볼 수 있다.

 

리스코프 치환 원칙에서 중요한 부분은 일관된 동작을 보장해야 한다는 부분이라고 생각한다.

반응형