본문 바로가기

Develop/소프트웨어 설계

[SOLID] Liskov Substitution 원칙

반응형

목차

Single Responsibility 원칙

Open/Closed 원칙

Liskov Substitution 원칙

Interface Segregation 원칙

Dependency Inversion 원칙

 

 

SOLID 리스코프 치환 원칙
Liskov Substitution 원칙

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

자식 클래스를 부모 클래스처럼 사용할 수 있도록 설계해야 한다.

Liskov Substitution 법칙는 상속과 관련된 규칙이다. 자식 클래스가 만약 부모 클래스에서 기대하는 기능과 달리 동작한다면, 같은 부모 클래스를 상속하더라도 자식 클래스마다 기대하는 결과가 달라지는 어처구니없는 사태가 발생한다. 따라서 자식 클래스는 사용자가 부모 클래스로부터 기대하는 정도에서 너무 멀어지지 않도록 구현해야 한다.

예제

아래 예제 코드는 Circle-ellipse problem을 발췌했다.

public class Ellipse
{
    //긴 반지름
    public double MajorAxis { get; set; }
    //짧은 반지름
    public double MinorAxis { get; set; }

    public virtual void SetMajorAxis(double majorAxis)
    {
        MajorAxis = majorAxis;
    }

    public virtual void SetMinorAxis(double minorAxis)
    {
        MinorAxis = minorAxis;
    }

    public virtual double Area()
    {
        return MajorAxis * MinorAxis * Math.PI;
    }
}

예제 코드는 Ellipse(타원)의 크기를 계산한다. 그리고 나중에 Circle(원)의 크기를 계산하는 클래스를 Ellipse(타원)상속하여 구현한다.

public class Circle : Ellipse
{
    public override void SetMajorAxis(double majorAxis)
    {
        base.SetMajorAxis(majorAxis);
        this.MinorAxis = majorAxis; //원은 지름이 동일하다.
    }
}

이 때 SetMajorAxis 함수는 수정되었지만, 수정되지 않은 SetMinorAxis 함수는 버그를 발생시킬 수 있다.

Circle circle = new Circle();
circle.SetMajorAxis(5);
circle.SetMinorAxis(4);
var area = circle.Area(); 
// 실제발생한 결과는 5*4 = 20
// 기대했던 결과는 4*4 = 16 또는 5*5 = 25

따라서 동일하게 수정한다.

public class Circle : Ellipse
{
    public override void SetMajorAxis(double majorAxis)
    {
        base.SetMajorAxis(majorAxis);
        this.MinorAxis = majorAxis; //원은 지름이 동일하다.
    }

    public override void SetMinorAxis(double minorAxis)
    {
        base.SetMinorAxis(minorAxis);
        this.MajorAxis = minorAxis;
    }

    public override double Area()
    {
        return base.Area();
    }
}

위와 같이 수정하더라도 Circle 클래스는 불필요한 함수와 중복된 데이터를 가지게 된다.
이 때 Circle 클래스를 재구현할 수도 있다.

public class Circle
{
    public double Radius { get; set; }
    public void SetRadius(double radius)
    {
        this.Radius = radius;
    }

    public double Area()
    {
        return this.Radius * this.Radius * Math.PI;
    }
}

더 나아가 추상 클래스를 활용하여 두 클래스 간의 관계를 유연하게 설계할 수 있다.

abstract class IModel
{
    public abstract double Area();
}

public class Ellipse : IModel
{
    public override double Area()
    {
        return MajorAxis * MinorAxis * Math.PI;
    }
}

public class Circle : IModel
{
    public override double Area()
    {
        return this.Radius * this.Radius * Math.PI;
    }
}

원칙 적용 전에 심사숙고할 사항

처음부터 모든 개념을 완벽하게 모델링하여 클래스 관계를 정의할 수 없다. 개발해나가면서 클래스 간의 역할도 관계도 바뀌게 된다. 대부분 특정 클래스부터 시작해서 더 추상화하거나 더 구체화하면서 설계 전체의 상속 관계를 형성해 간다. 따라서 클래스의 역할이나 관계를 변화시킬 때마다, 지금 당장 문제를 쉽게 해결하기 위해 설계를 어렵게 하는건 아닌지 스스로 생각해볼 필요가 있다.

반응형

'Develop > 소프트웨어 설계' 카테고리의 다른 글

[SOLID] Dependency Inversion 원칙  (0) 2019.08.30
[SOLID] Interface Segregation 원칙  (0) 2019.08.29
[SOLID] Open/Closed 원칙  (0) 2019.08.27
[SOLID] Single Responsibility 원칙  (0) 2019.08.16
[SOLID] SOLID 원칙이란  (2) 2019.08.15