정사각형은 직사각형인가요? 리스코프 치환 원리의 실제 적용 | Steven Giesel

본 글은 Steven Giesel님의 Is a square a rectangle? Liskov substitution principle in action 글을 번역하였습니다.

이 간단한 질문을 던져보겠습니다: “정사각형은 직사각형인가요?” 그러면 여러분은 감사할 것입니다: “그럼요, 물론이죠!”

하지만 잠시만 기다려서 함께 확인해 봅시다. SOLID: 리스코프 치환 원칙의 L을 사용하여 이 관계가 맞는지 확인해 보겠습니다!

SOLID로 플래시백

저는 SOLID 원칙에 대한 전체 기사를 작성했습니다: “C#의 SOLID 원칙” 그래서 여기서는 그 이유에 대해서만 설명하겠습니다: 리스코프 대체 원칙(LSP)은 다음과 같이 요약할 수 있습니다. 어떤 클래스가 다른 클래스의 서브클래스인 경우, 코드에서 문제를 일으키지 않고 슈퍼클래스(또는 인터페이스)를 대체할 수 있어야 한다는 것입니다. 기본적으로 우리는 서브클래스의 객체가 슈퍼클래스(또는 인터페이스)의 객체와 동일한 방식으로 동작하기를 원합니다.

이제 물어볼 수 있습니다:

정사각형은 직사각형인가요?

그렇다면 정사각형은 직사각형일까요? 정답은 '예’와 '아니오’입니다. 예, 정사각형은 직사각형의 한 유형이며 직사각형의 모든 속성을 상속 받기 때문입니다. 그러나 정사각형에는 직사각형에 적용되지 않는 추가적인 제약 조건이 있기 때문에 정사각형은 직사각형이 아닙니다. 정사각형은 네 변이 같지만 직사각형은 모든 변의 조합을 가질 수 있습니다. 다음 코드를 살펴보세요:

public class Rectangle
{
  public int Width { get; private set; }
  public int Height { get; private set; }
  
  public int Area => Width * Height;
  
  public virtual void SetWidth(int width) => Width = width;
  public virtual void SetHeight(int height) => Height = height;
}

public class Square : Rectangle
{
  public override void SetWidth(int width) => (Width, Height) = (width, width);
  public override void SetHeight(int heigth) => (Width, Height) = (heigth, heigth);
}

언뜻 보기에는 간단해 보입니다. 이제 제공된 코드를 살펴보겠습니다. 이 코드는 Square 클래스가 Rectangle 클래스의 SetWidthSetHeight 메서드의 동작을 수정하기 때문에 리스코프 치환 원칙을 위반합니다. Rectangle 클래스에서 SetWidthSetHeight 메서드는 직사각형의 너비와 높이를 독립적으로 설정할 수 있습니다. 그러나 Square 클래스에서는 두 메서드 모두 너비와 높이를 동일한 값으로 설정합니다.

즉, Square 클래스의 인스턴스를 생성하여 Rectangle 인스턴스 대신 사용하면 예기치 않은 결과가 나올 수 있습니다. 예를 들어, 주어진 테스트 사례에서는 너비를 10으로 설정하고 높이를 5로 설정했습니다. 그러나 Square 인스턴스를 사용하기 때문에 너비와 높이가 모두 10으로 설정되고 그 이후에는 5로 설정되어 “일반” 직사각형의 경우 면적이 50이 아닌 100이 되고 25가 됩니다.

public class Tests
{
  [Fact]
  public void GivenSquareRectangle_CalculateArea()
  {
    Rectangle rectangle = new Square();
    
    rectangle.SetWidth(10);
    rectangle.SetHeigth(5);
    
    // This will fail - but if we would adhere to
    // The Liskov substitution principle that shouldn't happen
    rectangle.Area.Should.Be(50);
  }
}

결론

리스코프 대체 원칙은 객체 지향 프로그래밍에서 매우 중요한 원칙이며, 이를 위반하면 프로그램에서 예기치 않은 동작이 발생할 수 있습니다. 우리는 이 원칙을 숙지하고 코드가 이 원칙을 따르도록 하여 프로그램을 더욱 유지보수 가능하고 예측 가능하게 만들어야 합니다.


2개의 좋아요