개발잡소리

[ 개발 잡소리 ] SOLID 원칙

vcs 2024. 6. 23. 18:29

SOLID 원칙이란?

객체지향의 핵심원리.

객체지향으로 코딩을 하면서 지켜야 할 규칙? 에티켓 같은 것이다

 

 

유니티에서 공식으로 낸

Level Up Your Code WithGame Programming Patterns

라는 책에도 실려있는 내용이다

 

 

게임개발에 연계해서 잘 설명해 놨으니 한 번쯤 보시길

( 원래 영어로 되어있었는데, 유니티 코리아가 번역 잘해줌 )

 

 

 

 

눈치 챈사람도 있겠지만 이것들은 모두 약자들이다

 

하나하나 이야기해 보자

 


SOLID 규칙의 S는 Single Responsibility, 단일 책임이다.

말 자체에서 느껴지듯이 모든 클래스는 단 하나의 책임만을 진다는 뜻이다.

예를 들어 PlayerController 안에서 Input과 움직임, 체력관리를 동시에 해서는 안된다는 뜻이다.

 

이러한 본인 클래스의 단 하나의 기능을 캡슐화해야 하고

가독성적인 부분은 200줄 내외일 때 가장 좋다.

public class Player : MonoBehaviour 
{
   [SerializeField] private PlayerAudio _playerAudio;
   [SerializeField] private PlayerInput _playerInput;
   [SerializeField] private PlayerMovement _playerMovement;
   
   private void Start(){
      _playerAudio = GetComponent<PlayerAudio>();
      _playerInput = GetComponent<PlayerInput>();
      _playerMovement = GetComponent<PlayerMovement>();
   }
   
}

이런 식으로 Player안에 싹 다 박아놓고 플레이어가 이들을 관리하는 (책임지는) 구조이면 안된다.

따라서 이 코드는 단일 책임 S에 위배된 코드인 것이다.


O는 Open-Closed principle, 개방 폐쇄 원칙 (상속 관련)이다

확장에는 열려있어야 하고 수정에는 닫혀있어야 한다는 의미이다.

 

무슨 말이지?

 

* 확장 : 

확장이란 무엇인가 하면, 모듈의 동작을 확장할 수 있는가를 의미한다

요구사항이 변경될 때 새로운 동작을 추가해 모듈을 확장하여 모듈이 하는 일을 변경하는 것이다.

따라서, 상위 클래스에서 하위 클래스로 확장함에 있어 제약이 없이 확장성이 좋아야 한다는 뜻이다.

 

* 수정 : 

기존 코드를 수정하지 않고 모듈의 기능을 확장하거나 변경이 가능해야 한다.

모듈 라이브러리의 수정이 기존에 작동하던 코드를 수정하게 해서는 안되고 새로운 기능만 건드려야 한다

하위 클래스로 확장할 때 기능추가로 인해 상위클래스를 수정해야 하게 되는 구조를 짜면 안 된다는 뜻이다.

 


 

L은 Liskov substitution principle(리스코프 치환원칙 )이다

파생클래스(하위 클래스)는 기본 클래스(부모 클래스)를 대체할 수 있어야 한다는 뜻이다.

중점은 하위 클래스를 강력하고 유연하게 만드는 것이다.

객체지향의 상속을 사용하면 하위 클래스를 통해 기능을 추가할 수 있다.

부모 클래스로부터 상속받은 메서드를 만약 아무것도 안 하고 비워버린다(버린다) 면 이 원칙에 위배된다.

 

class abstract Parent 
{
   public abstract void DoAct();
   
}
class Child : Parent 
{
   public override void DoAct()
   {
      // 비워둠    
   }
}

따라서 이건 L에 위배된 코드이다.

 

 


I는 Interface segregation principle(인터페이스 분리 원칙)인데

말은 어려울 수 있으나 내용은 비교적 간단한 원칙이다.

클라이언트가 자신이 사용하지 않는 메서드에 의존하지 않아야 한다는 원칙.

큰 덩어리의 인터페이스들을 구체적이고 작은 단위로 분리해야 한다.

( 리스코프 치환을 해결하기 위해 사용할 수 있다)

 

말 자체는 뭔가 꼬여있어서 좀 복잡하게 느껴질 수 있는데 풀어서 예시로 설명해 보겠다.

 

public interface IUnitStats
{
    public float Health { get; set; }
    public int Defense { get; set; }
    public void Die();
    public void TakeDamage();
    public void RestoreHealth();
    public float MoveSpeed { get; set; }
    public float Acceleration { get; set; }
    public void GoForward();
    public void Reverse();
    public void TurnLeft();
    public void TurnRight();
    public int Strength { get; set; }
    public int Dexterity { get; set; }
    public int Endurance { get; set; }
}

하나의 인터페이스에 너무 많은 것들을 가지고 있다.

 

이를 사용처와 의미에 따라 명확하게 분리할 수 있는데 분리한다면 다음과 같다

 

public interface IMovable
{
    public float MoveSpeed { get; set; }
    public float Acceleration { get; set; }
    public void GoForward();
    public void Reverse();
    public void TurnLeft();
    public void TurnRight();
}
public interface IDamageable
{
    public float Health { get; set; }
    public int Defense { get; set; }
    public void Die();
    public void TakeDamage();
    public void RestoreHealth();
}
public interface IUnitStats
{
    public int Strength { get; set; }
    public int Dexterity { get; set; }
    public int Endurance { get; set; }
}

크게 이 정도로 나눌 수 있다.

 

"어.. 움직인다는 게 기차처럼 옆으로 못 움직이고 앞뒤로만 움직일 수도 있는 거 아닌가?

ITurnable 같은 거로 나눠야 하는 거 아닌가?"

라고 생각할 수도 있는 것이다.

객체지향에서는 이러한 문제가 발생할 수도 있는데.

이건 프로젝트의 방향성이나 개발에 필요한 대로 유연하게 대처하여 구조를 짜면 된다.

 

결론적으로는 객체가 반드시 필요한 기능만을 가지도록 제한하는 원칙이다.

불필요한 기능의 상속과 구현을 방지하여 객체의 불필요한 책임을 덜어준다.

또한 작게 나누어진 인터페이스로 확장성을 향상해 준다.

이런 식으로 각각 다른 SOLID들이라도 결국에는 비슷한 말을 하고 있다.

 


마지막 원칙 D는  Dependency Inversion Principle(의존 역전의 원칙) 이다

이는 소프트웨어 모듈들을 분리하는 특정 형식을 말한다.

상위모듈은 하위모듈의 것들 직접 가져와서는 안된다. 둘다 추상화에 의존해서 가져와야한다.

추상화는 세부사항에 의존해야 한다. 세부가 추상을 의존해야한다.

클래스가 다른 클래스의 구현 및 작동과 관계가 있으면 안된다. 종속성은 잠재적인 위협으로 다가온다.

 

가장 설계에 관련된 원칙이다. 클래스간의 의존성을 최소화 해야한다는 원칙인데

이것이 SOLID중에서 가장 지키기 어려운 원칙이다.

상위와 하위라는 것이 클래스간의 상속을 말하는 것이 아니다.

무슨 말이지?

이처럼 다른 두 객체간의 커플링(참조, 연결력)이 강할수록 유지보수가 힘들고

나중에 규모가 커지면서 망가지기 쉬운 코드가 된다.

 

이를 가장 많이 해치는 것이 디자인 패턴중 싱글톤 패턴인데

그리하여 싱글톤 사용을 아예 금지하는 대형 프로젝트들이 많아지고있는 추세이다.

 

따리서 이를 해결하기 위해서는 어떻게 해야할까?

물론 참조자체를 안할 수는 없다.

우리는 앞서 인터페이스를 통해 구현할 기능들을 분할했었다

이 인터페이스를 참조하면 클래스(객체)를 통째로 참조하는 것 보다

간접적으로 추상적인 인터페이스의 메서드들을 사용함으로써 

객체간의 결합력을 쉽게 줄일 수 있다. 


 

이렇게 객체지향의 SOLID 원칙에 대하여 알아보았다.

앞으로 원칙들을 지키면서 코딩해보자

그러면 나중에 피를 보지않을 것이다.