Swift를 통해 SOLID를 알아보자

SOLID란?

etc-image-0

  • 디자인 설계에서 고려되야 하는 기본 원칙
  • SRP(Single Responsibility Principle)
  • OCP(Open Closed Principle)
  • LSP(Liskov Substiution Principle)
  • ISP(Interface Segregation Principle)
  • DIP(Dependency Inversion Principle)

SRP

  • 각 클래스가 오직 하나의 책임을 져야함
  • 하나의 클래스가 여러 책임을 지게 되면 코드를 이해하기 난해해짐
  • 클래스의 책임이 명확해져 유지보수 비용이 줄어듦

OCP

  • 확장성을 염두해둔 코드 설계
  • 만약 외부의 요구 사항이 변경되어도 기존 코드의 변경 없이 대응이 가능하도록 설계
  • 기존의 코드는 Closed가 되어서 안정성 증가
func eat(_ apple: Apple) { 
    // Closed된 코드
}
  • 여기서 Apple은 Open된 외부의 요구사항이고, eat함수 안의 코드는 Closed되어 있음

LSP

  • 인터페이스(Swift에서는 프로토콜)를 통해서 설계해야함
  • 하위 모듈이 변경되어도 동작이 가능함

ISP

  • public으로 노출되는 부분을 프로토콜로 추상화 해야함

DIP

  • 구현된 모듈이 프로토콜에 의존
  • 그 반대는 성립X
  • 프로토콜에만 의존하여 결합도를 낮출 수 있음

LSP, ISP, DIP는 모두 프로토콜과 연관되어 있어서 한꺼번에 예시로 설명

class Apple: Fruit {
    func cost() {}
}

class Banana: Fruit {
    func cost() {}
}

class Melon: Fruit {
    func cost() {}
}
  • 만약 과일이라는 공통적인 특성을 가지는 Apple, Banana, Melon이 있음
  • 이 과일들은 모두 cost라는 공통적인 메서드를 가짐
// ISP
protocol Fruit { 
    func cost()
}
  • 공통적인 프로토콜(인터페이스)로 추상화한 코드
  • 이것은 앞서 언급했던 ISP에 입각된 것
  • Fruit의 하위 모듈인 Apple, Banana, Melon이 직접 구현하게 됨
func pay(fruit: Fruit)

let apple = Apple()
pay(fruit: apple) // DIP
  • 위 pay라는 함수는 추상화된 프로토콜을 인자로 받고 있음
  • 추상화된 프로토콜을 바라보면서 구체적인 하위 모듈을 알 필요가 없음
  • 이 과정이 DIP에 입각된 것, 이것이 유명한 의존성 주입(DI)
let banana = Banana()
pay(fruit: banana) // LSP
  • 만약 하위 모듈이 바뀌더라도 pay라는 함수는 여전히 바뀌지 않음
  • 어떤 과일이 오든간에 추가 구현이 필요 없는 확장성을 설계 및 구현할 수 있음
  • 만약 아예 새로운 과일이 추가 되었다 하더라도 상관 없음
  • 이 과정이 LSP에 입각된 것

Solid의 목표

  • 코드들의 응집도를 높이고
  • 결합도는 낮춰야함
  • 결국 확장성을 넓히고 유지 보수 비용을 최소화 하기 위함