유니티 디자인 패턴

SOLID 원칙 3. LSP(리스코프 치환 원칙)

Imperor 2025. 3. 7. 11:52

SOLID 원칙은 객체지향 설계에서 지켜줘야 할 5개의 소프트웨어 개발 원칙이다.

 

SOLID 설계 원칙은 OOP의 4가지 특징(추상화, 상속, 다형성, 캡슐화)와 더불어, 객체 지향 프로그래밍의 중요한 요소이며, 여러 디자인 패턴들이 SOLID 설계 원칙에 입각하여 만들어졌기 때문에, 표준화 작업부터 아키텍처 설계에 이르기까지 다양하게 적용되는 근간이 된다.

 

객체 지향 설계의 5가지 원칙은 다음과 같다.

1. SRP(Single Responsibility Principle): 단일 책임 원칙

2. OCP(Open Closed Principle): 개방 폐쇄 원칙

3. LSP(Liskov Substitution Principle): 리스코프 치환 원칙

4. ISP(Interface Segregation Principle): 인터페이스 분리 원칙

5. DIP(Dependency Inversion Principle): 의존성 역전 원칙

 

하나씩 자세히 살펴보자.


3. LSP(Liskov Substitution Principle): 리스코프 치환 원칙

 

LSP는 파생된 클래스가 기본 클래스로 대체될 수 있어야 한다고 명시한다.

객체 지향 프로그래밍에서 상속을 사용하면 서브 클래스를 통해 기능을 추가할 수 있다.

하지만 주의하지 않으면 불필요한 복잡성을 더할 수 있다.

 

LSP는 상속을 사용하여 서브 클래스를 더 강력하고 유연하게 만드는 방법을 알려준다.

 

게임에 Vehicle이라는 클래스가 필요하다고 생각하자.

이 클래스는 애플리케이션에서 사용할 차량이라는 파생 클래스의 기본 클래스가 된다.

자동차나 트럭을 사용할 경우를 예로 들 수 있다.

 

기본 클래스(Vehicle)을 사용할 수 있는 모든 위치에서 애플리케이션에 오류를 발생시키지 않고 Car, Truck과 같은 파생 클래스를 사용할 수 있다.

 

Vehicle 클래스의 예시는 다음과 같다

public class Vehicle
{
    public float speed = 100;
    public Vector3 direction;
    public void GoForward()
    {
        //...
    }
    public void Reverse()
    {
        //...
    }
    public void TurnRight()
    {
        //...
    }
    public void TurnLeft()
    {
        //...
    }
}

직진, 후진, 우회전, 자회전 기능을 메서드로 가지고 있다

 

보드에서 차량을 옮기는 턴제 게임을 개발한다고 가정하자

 

사전에 정해진 경로에 따라 차량을 운전하는 Navigator 클래스를 추가하자.

public class Navigator
{
    public void Move(Vehicle vehicle)
    {
        vehicle.GoForward();
        vehicle.TurnLeft();
        vehicle.GoForward();
        vehicle.TurnRight();
        vehicle.GoForward();
    }
}

 

이 클래스를 사용하면 "어떤 차량이든" Navigator 클래스의 Move메서드로 전달할 수 있으며, Car와 Truck에는 이 방법이 잘 통할것이다.

하지만 Train이라는 클래스를 구현하려는 경우에는 어떻게 될까?

기차는 철도를 이탈할 수 없으므로 TurnLeft와 TurnRight메서드는 Train클래스에서 작동하지 않는다.

Navigator의 Move메서드로 기차를 전달하는 경우, 해당 라인에 도달하면 구현되지 않은 예외가 발생하거나 아무일도 일어나지 않게 된다.

특정 유형을 하위 유형과 교체할 수 없다면 LSP를 위반하게 된다. ★

 

Train은 Vehicle의 하위 유형이므로 Vehicle 클래스가 허용되는 모든 위치에 사용할 수 있어야 한다.

그렇지 않으면 코드가 예측할 수 없는 방식으로 작동할 수 있다.

 

 

다음은 LSP를 더 철저하게 준수하기 위해 필요한 몇 가지 준수사항

  • 서브 클래스를 설정할 때 기능을 제거하면 LSP를 위반하게 될 가능성이 크다.

NotImplementException은 이 원칙을 위반했다는 의미이며, 메서드를 비워두는 경우도 마찬가지이다.

파생클래스가 기본클래스처럼 동작하지 않는다면 오류나 예외가 명시적으로 보이지 않더라도 LSP를 준수하지 않는 것이다.

 

 

  • 추상화를 단순하게 유지한다.

기본클래스에 들어가는 로직이 많을수록 LSP를 위반할 확률도 커진다.

기본클래스는 파생클래스의 일반적인 기능만 표현해야 한다.

 

 

  • 서브 클래스는 기본 클래스와 동일한 공용 멤버를 가져야 한다. ★★★

그러한 공용 멤버는 호출 시 동일한 서명을 갖고 동일한 동작을 취해야 한다.

 

 

  • 클래스의 계층 구조를 수립할 때 클래스 API를 고려한다. ★

대상을 모두 차량으로 간주하더라도 Car와 Train은 각각 서로 다른 부모 클래스로부터 상속하는 편이 더 나을 수 있다.

실질적으로 분류가 항상 클래스 계층 구조와 일치하지는 않는다.

 

 

  • 상속보다는 합성을 우선시한다. ★★

상속을 통해 기능의 전달을 시도하는 대신, 특정한 동작을 캡슐화할 수 있도록 인터페이스 또는 별도의 클래스를 만들어라.

그런 다음 Mix And Match를 통해 다양한 기능의 합성물을 생성한다.

상속은 is의 개념 ★

인터페이스는 has의 개념 ★★

 

이 디자인을 사용하기 위해 원본 Vehicle 클래스를 삭제하고 대부분의 기능을 인터페이스로 옮긴다. ★★

public interface ITurnable
{
    public void TurnRight();
    public void TurnLeft();
}
public interface IMovable
{
    public void GoForward();
    public void Reverse();
}

 

 

RoadVehicle 유형과 RailVehicle유형을 만들면 LSP원칙을 더 철저하게 따를 수 있다.

Car와 Train은 해당하는 기본클래스로부터 상속한다.

 

public class RoadVehicle : IMovable, ITurnable
{
    public float speed = 100f;
    public float turnSpeed = 5f;
    public virtual void GoForward()
    {
        //...
    }
    public virtual void Reverse()
    {
        //... 
    }
    public virtual void TurnLeft()
    {
        //... 
    }
    public virtual void TurnRight()
    {
        //... 
    }
}
public class RailVehicle : IMovable
{
    public float speed = 100;
    public virtual void GoForward()
    {
        //...
    }
    public virtual void Reverse()
    {
        //...
    }
}
public class Car : RoadVehicle
{
    //...
}
public class Train : RailVehicle
{
    //...
}

 

이러한 방법에서는 기능이 상속 대신 인터페이스를 통해 실행된다. ★

Car와 Train 클래스가 더 이상 같은 기본 클래스를 공유하지 않음으로써 LSP를 준수한다.

같은 기본 클래스(Vehicle) 에서 RoadVehicle과 RailVehicle을 파생시킬 수도 있으나 이 경우에는 필요하지 않다. (모든 기능을 인터페이스로부터 받고 있기 때문)

LSP치환 원칙에 따라 상속 사용 방법에 제한을 두어 코드 베이스를 확장 가능하고 유연하게 유지해야 한다.

 

 

 

 

출처

https://unity.com/kr/resources/level-up-your-code-with-game-programming-patterns

 

게임 프로그래밍 패턴으로 코딩 스킬 업그레이드

새로운 전자책에서는 잘 알려진 디자인 패턴과 함께 실제 Unity 프로젝트에서 활용할 수 있는 실용적인 예제를 소개합니다.

unity.com