본문 바로가기
Object-Oriented Programming/SOLID

SOLID 원칙

by yongmin.Lee 2021. 12. 23.

SOLID 원칙

  • 로버트.C마틴이 "객체 설계"를 할 때 중요하게 생각하는 것으로 제시한 다섯 가지 원칙
  • 장점
    • 1. 모듈화를 통해 재상용성이 높은 코드를 만들고 생산성을 높일 수 있다
    • 2. 테스트 가능한 코드를 만들고 관리할수있어, 테스트 코드의 장점을 취할수 있다.
    • 3. 변경에 유연하며 확장성이 높은 코드를 만들수 있다.
    • 4. 유지보수의 리소스가 줄어든다.
  • 종류 
    • 1. SRP (Single-Responsibility Principle), 단일 책임 원칙
    • 2. OCP (Open-Close Principle), 개방 폐쇄 원칙
    • 3. LSP (Liskov Substitution Principle), 리스코프 치환 원칙
    • 4. DIP (Dependency-Inversion Principle), 의존성 역전 원칙
    • 5. ISP (Interface-Segregation Principle), 인터페이스 분리법칙

 

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

  • 1. 정의
    • 소프트웨어 요소(클래스, 함수 등)는 응집도 있는 하나의 책임을 갖는다. 
    • 응집도 : 관련성 있는 코드들이 얼마나 모여있는지
    • 결합도 : 불필요한 의존성이 생겨 있는지
    • 높은 응집도(낮은 결합도)의 경우는 하나의 책임을 하나의 객체가 가지고 있다
  • 2. 문제상황
    • 처음에 모듈을 만든 이후 작은 기능이 하나둘씩 추가 되면서 문제가 시작된다. 계속해서 기능이 추가되면서 점점 해당 모듈의 책임은 많아지게 된다. 
    • 해당 모듈의 책임이 많아 질수록 기능들이 병합될 확률이 높아지는데, 이때 하나의 기능(A)을 리팩토링하는 경우 해당 기능뿐만 아니라 다른 기능(B) 또한 수정해야 할 가능성이 높아지며, 해당 기능(A)이 제대로 동작하는지 또한 다시 확인해야 한다. 
    • 이를 해결하기 위해서는 꼬인 끈을 모두 풀어내는 수 밖에 없으며, 기능이 많아질수록 이에 대한 비용은 기하 급수적으로 늘어나게 된다. 
    • ex)  UI, Network, Navigation 등등 모든 역할을 책임지고 있는 UIViewController
  • 3. 단일책임원칙 적용방법
    • 작은 피쳐를 추가 하는 것을 멈추고 해당 피쳐를 모듈, 컴포넌트, API 형식으로 분리하는것으로 고려해야한다. 기능을 붙일 생각을 하지말고 라이브러리로 만들 생각을 해야한다.
    • 가능한 하나의 기능을 할 수 있는 클래스를 만들어야한다. 이미 병합된 기능을 가진 클래스라면 이를 분리하고 분리한 클래스를 사용하는 클래스를 하나 만들어야한다.

 

2. 개방-폐쇄 원칙 , OCP (Open-Close Principle)

  • 1. 정의
    • 확장에는 가능하도록 열려있고, 변경에는 닫혀있어야 한다.
  • 2. 문제상황
    • ex) 특정 타입만 고려해서 기능을 만드는 경우, 새로운 타입이 추가되는 경우 기존의 기능을 전부 수정하거나 새로운 기능을 전부 만들어야 한다.
  • 3. 개방폐쇄원칙 적용방법
    • 기능의 추가가 기존 코드에 영향을 끼치지 않도록 해야한다. 제네릭, 상속, 인터페이스, 프로토콜을 사용하여 확장성이 높은 코드를 사용한다.
    • 결국 다른 타입이 추가 되더라도, 기존의 코드는 최대한 건드리지 않으면서 새로운 타입에 대해서도 기존에 사용했던 기능들은 그대로 사용할 수 있어야 한다.

 

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

  • 1. 정의
    • 자식클래스는 부모 클래스로써의 역할을 완벽히 할 수 있어야한다. 즉, A(자식객체)가 B(부모객체)를 상속받았으면, B로도 역할을 완벽히 할 수 있어야 한다.
    • 올바른 상속으로 자식객체의 확장이 부모객체의 방향을 온전히 따름으로써, 자식 클래스는 부모 클래스 동작을 완전히 대체할수 있어야 한다
  • 2. 문제상황
    • 객체지향에서는 객체의 상속이 일어나는데 , 이과정에서 객체의 의의와 어긋나는 확장으로 인해 잘못된 방향으로 상속되는 경우가 생긴다.
    • 자식객체의 확장이 부모객체의 방향을 온전히 따르지 않게되면 자식객체에서 오버라이드한 메소드는 동일한 흐름으로 동작하지 않을수 있다.
  • 3. 리스코프 치환 원칙 적용방법
    • 상위타입이 하위타입으로 대체가 되더라도 동일한 동작을 해야한다. 이를 위해서 상속의 범위를 잘 생각해야 한다. 사용하는 메서드가 전체를 아우르는 메서드가 맞다면 동일한 동작을 해야하며, 그렇지 않다면 상위타입을 따로 작성해야 함을 의미한다.

 

4. 인터페이스 분리 법칙, ISP (Interface-Segregation Principle)

  • 1. 정의
    • 클라이언트 객체는 사용하지 않는 메소드에 의존하면 안된다.
    • 인터페이스는 기능별로 잘게 쪼개어 필요한 부분만 클라이언트가 취사선택하여 사용할 수 있게 해야한다.
  • 2. 문제상황
    • 큰 인터페이스 하나에 의존하게 되면 필요하지 않는 기능을 가지고 있게된다.
    • 최악의 경우 필요없는 인터페이스를 구현해야하는 경우가 있을수있다.
  • 3. 인터페이스 분리법칙 적용방법
    • 인터페이스는 기능별로 잘게 쪼개어 필요한 부분만 클라이언트가 취사선택하여 사용할 수 있게 해야한다.

 

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

  • 1. 정의
    • 상위 모듈은 하위 모듈에 의존해서는 안된다. 상위 모듈과 하위 모듈 모두 추상화에 의존해야 한다.
    • 추상화는 세부 구현사항에 의존해서는 안된다. 세부사항이 추상화에 의존해야 한다.
  • 2. 문제상황
    • A 객체에서 B 객체를 참조함으로써 A가 B에 의존하고 있는경우 B의 구현체가 변경되었을 때 발생합니다.
    • A는 B를 직접 참조하고 있으므로 B가 변경될 때 마다 컴파일러는 A를 다시 컴파일해야하고, A에 수정사항이 생길수도 있다.
  • 3. 의존성역전원칙 적용방법
    • A모듈은 B1, B2, B3, ... 모듈에 의존하는 것이 아니라 B1,B2,B3를 추상화한   B protocol에 의존함으로써 A와 B의 의존관계 디커플링 시켜준다.
    • A → B protocol ← B 형태의 코드를 작성하게 된다면 loose coupling 이 되며 서로의 변경에 영향이 적어지게 된다.
    • B는 참조를 받는 입장에서 B protocol을 참조하는 방향으로 바뀌게 되는데 기존의 제어 흐름과 소스코드의 의존성이 반대가 되는 것이다.

 

 

 

 

참고

https://onemoonstudio.tistory.com/9

https://wikidocs.net/158677

https://velog.io/@leeyoungwoozz/SwiftiOS-%EA%B0%9D%EC%B2%B4-%EC%84%A4%EA%B3%84-%EC%9B%90%EC%B9%99-SOLID