본문 바로가기
C#

C#] SOLID 원칙 3. Liskov Substitution Principle(리스코프 치환 원칙)

by Fastlane 2024. 1. 26.
728x90
반응형

Liskov Substitution Principle (LSP)은 child class는 parent class를 대체할 수 있어야 한다. 이 말은, 기능 변경 없이 상속받은 class는 base class를 대체할 수 있도록 만들어야 한다. 그렇지 않으면, application은 결국 오류가 날 수 있다. 

 

명확한 이해를 위해, 합계 계산기 예를 사용해보자. 

 

Project 설정

수들을 갖는 array가 있고, array의 모든 수를 합하는 기본기능이 있다. 이제, 홀수 또는 짝수만 더하는 기능이 필요하다고 하자.

 

어떻게 구현할 수 있는가?

public class SumCalculator
{
    protected readonly int[] _numbers;

    public SumCalculator(int[] numbers)
    {
        _numbers = numbers;
    }

    public int Calculate() => _numbers.Sum();
}

 

public class EvenNumbersSumCalculator: SumCalculator
{
    public EvenNumbersSumCalculator(int[] numbers)
        :base(numbers)
    {
    }

    public new int Calculate() => _numbers.Where(x => x % 2 == 0).Sum();
}

 

class Program
{
    static void Main(string[] args)
    {
        var numbers = new int[] { 5, 7, 9, 8, 1, 6, 4 };

        SumCalculator sum = new SumCalculator(numbers);
        Console.WriteLine($"The sum of all the numbers: {sum.Calculate()}"); //40

        Console.WriteLine();

        EvenNumbersSumCalculator evenSum = new EvenNumbersSumCalculator(numbers);
        Console.WriteLine($"The sum of all the even numbers: {evenSum.Calculate()}"); //18
    }
}

 

코드 개선

코드는 잘 동작한다. 하지만 무슨 문제가 있으며, 왜 수정을 해야 하는가?

 

알고있듯이, child class는 parent class로부터 상속받으면, child class는 parent class가 된다. 이 생각으로, 별도의 수정없 EvenNumbersSumCalculator를 SumCalculator처럼 참조할 수 있어야 한다. 

 

확인해보자. 

SumCalculator evenSum = new EvenNumbersSumCalculator(numbers);
Console.WriteLine($"The sum of all the even numbers: {evenSum.Calculate()}"); // 40

 

변수 evenSum의 type인 SumCalculator이 다른 class보다 높기 때문이다. base class이기 때문에 Count 함수는 SumCalculator의 것으로 실행된다. child class가 parent class를 대체하여 동작하지 않았기 때문에 개선이 필요하다. 

 

다행히도, 솔루션은 간단하다. 두 class에 약간의 수정을 하면 된다. 

public class SumCalculator
{
    protected readonly int[] _numbers;

    public SumCalculator(int[] numbers)
    {
        _numbers = numbers;
    }

    public virtual int Calculate() => _numbers.Sum();
}

 

public class EvenNumbersSumCalculator: SumCalculator
{
    public EvenNumbersSumCalculator(int[] numbers)
        :base(numbers)
    {
    }

    public override int Calculate() => _numbers.Where(x => x % 2 == 0).Sum();
}

 

이제 합계 18을 얻을 수 있다. 

 

이 동작을 설명해보자. parent object 변수에 저장된 child object 참조를 갖고 Calculate 함수를 호출하면 컴퍼일러는 parent class의 함수를 호출한다. 이제 Calculate 함수가 virtual 이고, child class에서 overriden되었기 때문에 child class의 함수가 대신 사용된다. 

 

Liskov Substitution Principle 구현

여전히 상속받은 class의 동작이 수정되었고, base class를 대체할 수 없다. Calculator abstract class 도입으로 개선이 필요하다. 

public abstract class Calculator
{
    protected readonly int[] _numbers;

    public Calculator(int[] numbers)
    {
        _numbers = numbers;
    }

    public abstract int Calculate();
}

 

public class SumCalculator : Calculator
{
    public SumCalculator(int[] numbers)
        :base(numbers)
    {
    }

    public override int Calculate() => _numbers.Sum();
}

 

public class EvenNumbersSumCalculator: Calculator
{
    public EvenNumbersSumCalculator(int[] numbers)
       :base(numbers)
    {
    }

    public override int Calculate() => _numbers.Where(x => x % 2 == 0).Sum();
}

 

 class Program
{
    static void Main(string[] args)
    {
        var numbers = new int[] { 5, 7, 9, 8, 1, 6, 4 };

        Calculator sum = new SumCalculator(numbers);
        Console.WriteLine($"The sum of all the numbers: {sum.Calculate()}");

        Console.WriteLine();

        Calculator evenSum = new EvenNumbersSumCalculator(numbers);
        Console.WriteLine($"The sum of all the even numbers: {evenSum.Calculate()}");
    }
}

 

모든 숫자에 대해 40, 짝수에 대해 18이라는 동일한 결과를 다시 얻었다. 하지만, 어떠한 subclass 참조도 base class 변수에 저장할 수 있고, LSP의 목표처럼 동작도 변경되지 않았다. 

 

 

728x90
반응형

댓글