재우니의 블로그

 

 

Liskov Substitution Principle (LSP)

 

 

리스코프 대체  원칙(LSP)은 파생 클래스의 객체는 프로그램의 정확성에 영향을 주지 않으면서 기본 클래스의 객체로 대체할 수 있어야 한다는 점을 강조합니다. 이 원칙은 상속 계층 구조가 일관성을 유지하고 예기치 않은 동작을 유발하지 않도록 보장합니다.

 

다시 말해 객체지향 프로그래밍에서 하위 클래스는 상위 클래스를 대체할 수 있어야 한다는 원칙입니다. 이를 좀 더 쉽게 이해하기 위해 다음과 같은 설명을 드릴게요.

 

1. 대체 가능성 (Substitutability): 상속 관계에 있는 클래스들은 서로 대체 가능해야 합니다. 즉, 어떤 부모 클래스의 인스턴스가 있을 때 이를 해당 부모 클래스의 하위 클래스의 인스턴스로 대체해도 프로그램의 의미나 동작이 변하지 않아야 합니다.

 

2. 하위 클래스의 확장성 (Subclass Extensibility): 하위 클래스는 상위 클래스의 기능을 확장할 수 있어야 합니다. 이는 하위 클래스에서 새로운 기능이나 동작을 추가하거나, 기존의 동작을 변경할 수 있어야 한다는 것을 의미합니다.

 

 

이 코드는 IFlyable 인터페이스를 구현하는 Bird  Ostrich 클래스를 보여주며, MakeBirdFly 메서드를 통해 두 클래스를 사용하는 방법을 보여줍니다. 이 예제는 Liskov Substitution Principle을 준수하고 있습니다. 프로그램을 실행하면 새가 날 수 있지만 타조는 날 수 없다는 결과가 출력됩니다.

 

using System;

// IFlyable 인터페이스 선언
public interface IFlyable
{
    void Fly();
}

// Bird 클래스: IFlyable 인터페이스 구현
public class Bird : IFlyable
{
    public void Fly()
    {
        Console.WriteLine("A bird can fly.");
    }
}

// Ostrich 클래스: IFlyable 인터페이스 구현
public class Ostrich : IFlyable
{
    public void Fly()
    {
        Console.WriteLine("An ostrich can't fly.");
    }
}

class Program
{
    static void MakeBirdFly(IFlyable bird)
    {
        bird.Fly();
    }

    static void Main()
    {
        // Bird 인스턴스 생성 및 MakeBirdFly 메서드 호출
        var bird = new Bird();
        Console.WriteLine("Calling MakeBirdFly with Bird instance:");
        MakeBirdFly(bird);
        Console.WriteLine();

        // Ostrich 인스턴스 생성 및 MakeBirdFly 메서드 호출
        var ostrich = new Ostrich();
        Console.WriteLine("Calling MakeBirdFly with Ostrich instance:");
        MakeBirdFly(ostrich);
    }
}

 

 

LSP 원칙 위반 코드

 

LSP는 상위 클래스의 메서드를 하위 클래스에서 오버라이드할 때, 그 동작이 예상대로 이뤄져야 한다는 원칙을 강조합니다. 여기서 Bird 클래스의 Fly 메서드는 날 수 있는 동작을 나타내고, Ostrich 클래스에서는 이 메서드를 오버라이드하여 타조는 날 수 없다는 예외를 던지도록 구현하고 있습니다.

 

이 경우, Ostrich 클래스는 Bird 클래스의 하위 클래스이기 때문에 Fly 메서드를 오버라이드하는 것이 LSP를 위반하지 않는 것처럼 보일 수 있습니다. 그러나 LSP에서는 오버라이드된 메서드는 기반 클래스에서 정의한 메서드와 동일한 기능적인 동작을 해야 한다고 명시하고 있습니다.

 

따라서 Ostrich 클래스에서 Fly 메서드가 예외를 던지도록 오버라이드되면, 기대했던 동작과 다르게 되어 LSP를 위반하게 됩니다. LSP를 지키려면 하위 클래스에서는 부모 클래스의 메서드를 오버라이드할 때 동일한 기대 동작을 유지해야 합니다.

 

using System;

public class Bird
{
    public virtual void Fly()
    {
        Console.WriteLine("A bird can fly.");
    }
}

public class Ostrich : Bird
{
    public override void Fly()
    {
        throw new InvalidOperationException("Ostriches can't fly.");
    }
}

class Program
{
    static void Main()
    {
        // Bird 클래스의 인스턴스 생성
        Bird myBird = new Bird();

        // Bird 클래스의 인스턴스를 Ostrich 클래스의 인스턴스로 대체
        Bird myOstrich = new Ostrich();  // LSP 위반

        // 각각의 인스턴스에서 Fly 메서드 호출
        Console.WriteLine("Calling Fly on Bird instance:");
        myBird.Fly();

        Console.WriteLine("\nCalling Fly on Ostrich instance:");
        myOstrich.Fly();  // 예외가 발생함 (LSP 위반)
    }
}

 

 

 Ostrich 클래스의 인스턴스를 Bird 클래스의 인스턴스로 대체했을 때, Fly 메서드를 호출하면 Ostrich 클래스에서 오버라이드된 메서드가 호출되어 예외가 발생합니다. 이는 LSP를 위반한 예시입니다. 일반적으로 LSP를 지키는 경우에는 부모 클래스의 인스턴스를 하위 클래스의 인스턴스로 대체해도 동일한 동작을 기대할 수 있어야 합니다.