토털이
토털이의 ios 개발 여정
토털이
hands-on 개발자 토털이입니다🌈
GitHub
전체 방문자
오늘
어제
  • 분류 전체보기 (7)
    • CS (2)
      • 자료구조&알고리즘 (2)
    • ios (1)
    • swift (2)
    • 🌈TIL (2)

블로그 메뉴

  • 홈
  • 태그
  • 글쓰기

공지사항

인기 글

태그

  • LinkedList
  • queue
  • 링크드리스트
  • 큐
  • AppBased
  • SWIFT
  • AppLaunch
  • UIKit
  • 앱실행과정

최근 댓글

최근 글

hELLO · Designed By 정상우.
토털이

토털이의 ios 개발 여정

swift

[swift] SOLID 원칙 예시를 써보며 이해해보기

2022. 9. 21. 18:29
  • hands-on으로 직접 짜보며 SOLID 원칙을 이해해봅시다!
  • 읽으시는 분들, 좀 더 잘 이해하고 싶으시다면 같이 코드를 작성해보며 이해하는 것을 추천합니다 :)

SOLID란?

  • 디자인 패턴의 일종으로 아래 다섯가지의 원칙을 지키는 것을 말합니다. 천천히 다섯가지 원칙을 쉬운 예시로 짚어보고자 합니다.
  1. Single Responsibility
  2. Open-Closed
  3. Liskov Substitution
  4. Interface Segregation
  5. Dependency Inversion

1. Single Responsibility

각각의 클래스는 하나의 책임만을 가져야 합니다.
클래스가 몇개의 책임을 맡고 있는지를 세어보고, 하나 이상의 책임을 가진 클래스들은 다른 클래스로 나누어 주는것이 좋습니다.

// 이 구조체에서는 두가지의 역할을 모두 맡고 있다.
struct Person {

    let name: String
    let age: Int

    func checkAge() -> String {
        if age < 20 {
            return "미성년자"
        } else {
            return "성인"
        }
    }
}

let person = Person(name: "토털이", age: 27)
person.checkAge() // "성인"
  • 이를 Single Responsibility 원칙을 따르게 바꾸어 보자.
// 하나의 구조체에 하나의 책임이 있고,
struct Person {
  let name: String
  let age: Int
}

// 또 다른 구조체에 하나의 책임이 있다
struct AgeVerifier {
    func checkAge(age: Int) -> String {
        if age < 20 {
          return "미성년자"
        } else {
          return "성인"
        }
    }
}

let person = Person(name: "냥냥이", age: 1)
let verifyAge = AgeVerifier()
verifyAge.checkAge(age: person.age) // "미성년자"
  • 이제 하나의 구조체가 하나의 역할만을 맡게 되었습니다.

2. Open-Closed

  • 클래스는 확장(extension)에는 열려있지만 수정(modification)에는 닫혀있어야 합니다.
  • 짧게 말해서, 새로운 기능을 전체 클래스를 수정하지 않고도 기능을 추가할 수 있어야 합니다. 새로운 기능을 위해 전체 클래스를 수정하는 건 개발과 테스트에 많은 시간을 요하기 때문입니다.
  • 이제 이를 적용한 코드를 짜보면서 이해해볼 수 있습니다. 내가 동물원을 가졌다고 상상해보세요. 그리고 지금 동물원에는 사자 한마리만을 데리고 있습니다. 현실적으로 생각해보면, 더 많은 동물들이 있어야만 동물원에서 돈을 벌 수 있을 겁니다. 그렇기에 동물원을 지을 때 부터, 동물을 쉽게 추가할 수 있게끔 동물원을 지어주어야 합니다.
protocol AnimalProtocol {
    func makeSound() -> String
}

class Tiger: AnimalProtocol {
    func makeSound() -> String {
        return "으르렁"
    }
}

struct Zoo {
    let animals: [AnimalProtocol]
    func animalNoise() -> [String] {
        return animals.map { $0.makeSound() }
    }
}

let tiger = Tiger()
var zooAnimals = Zoo(animals: [tiger])
zooAnimals.animalNoise() // ["으르렁"]
  • 이제 새로운 동물이 생겼고, 이를 동물원에 추가해야한다고 생각해봅시다. 간단하죠? 그냥 새로운 동물을 만들어서 동물원에 넣어주면 됩니다. 추후에 더 많은 동물들이 추가된다고 해도 똑같은 방법으로 추가해주면 됩니다. 아래 코드를 추가해줍시다.
class Horse: AnimalProtocol {
  func makeSound() -> String {
    return "끼로록"
  }
}

let horse = Horse()
zooAnimals = Zoo(animals: [tiger, horse])
zooAnimals.animalNoise() // [roar, neigh]

3. Liskov Substitution

  • 자식 클래스는 부모 클래스 타입의 정의를 깨트려서는 안됩니다.
  • 부모(super class)로 동작하는 곳에서 자식(sub class)를 넣어주어도 대체가 가능해야합니다.
  • 자식 클래스는 부모 클래스의 기능을 바꾸지 않고, 부모클래스의 메서드를 오버라이드 할 수 있어야 합니다. 이 원칙을 통해 더 재사용가능한 코드가 되고, 코드가 교환가능해집니다.
  • 만일, 오버라이드 메서드가 아무것도 하지 않고 exception만 던진다면, Liskov Substitution을 위배하게 됩니다.
  • 모두가 기존의 룰을 위반하지 않고 동작하는 프로그램이 만들어지게 되면, 이를 LSP를 지킨 설계라고 하게 됩니다.
// Bird, Eagle은 Liskov Substitution를 준수합니다.
class Bird {
  func makeNoise() {
    print("짹짹")
  }
}

class Eagle: Bird {
  override func makeNoise() {
    print("으르렁-짹")
  }
}

// 아래의 경우 부모 클래스를 깨뜨리기 때문에, Liskov Substitution를 위반하게 됩니다.
class Crow: Bird {
  override func makeNoise() {
    fatalError("내 소리가 뭐였더라?")
  }
}

4. Interface Segregation

  • 필요한 것만을 적용하라!
  • 인터페이스를 분리함으로서, fat interface 문제를 해결할 수 있습니다. fat interface는 우리가 사용할 것보다 더 많은 메서드를 가지고 있는 경우를 말합니다.
  • 필요한 것만을 사용하자!가 Interface Segregation의 핵심입니다.
  • 아래의 코드는 Interface Segregation를 지키지 못한 예시입니다. 아기는 일을 하지 못하죠? 하지만 프로토콜을 따르려면 정의를 해줘야만 합니다. 이런경우 우리는 사용하지 않는 메서드를 구현해주게 됩니다.
protocol Action {
    func eat()
    func work()
}
class Adult: Action {
    func eat() {
        print("쩝쩝")
    }
    func work() {
        print("아이고 허리야")
    }
}
class Baby: Action {
    func eat() {
        print("냠냠")
    }
    func work() {
        // baby can’t work
    }
}
  • 원칙을 지켜서 쓰려면 어떻게 해야할까?
  • 아래와 같이 고쳐 써줄 수 있다.
protocol EatAction {
    func eat()
}

protocol WorkAction {
    func work()
}

class Adult: EatAction, WorkAction {
    func eat() {
        print("쩝쩝")
    }
    func work() {
        print("아이고 허리야")
    }
}

class Baby: EatAction {
    func eat() {
        print("냠냠")
    }
}

5. Dependency Inversion

  • 추상화에 의존하고, 구체적인 것에 의존하지 말자는 원칙입니다.
  • 상위 모듈이 하위 모듈에 의존하면 안되고 두 모듈 모두 추상화에 의존하게 만들어야 한다는 원칙입니다.
  • 높은 레벨의 모듈은 낮은 레벨의 모듈에 의존해서는 안됩니다. 예를 들어, 높은 레벨 모듈인 view controller은 networking 요소와 같은 하위 요소에 의존해서는 안됩니다. 대신, 추상화에 의존해야합니다. 스위프트 언어로는 프로토콜이라고 볼 수 있습니다. 커플링(coupling)을 줄이는게 이 규칙의 포인트입니다.
  • 만일 firebase를 사용하다가 다음날 서버가 종료된다는 사실을 알았을 때, 바로 Realm과 같은 다른 서버리스에 연결을 해줘야합니다. 이런때, 상위모듈의 하위모듈의 관계를 떨어뜨려 놓았다면, 쉽게 의존을 대신해줄 수 있습니다.
  • 아래의 코드는 하위 모듈인 NetworkRequest 모듈과 상위 모듈인DatabaseController가 관계가 깊다는 문제가 있습니다.
class DatabaseController {

    private let networkRequest: NetworkRequest

    init(network: NetworkRequest) {
        self.networkRequest = network
    }
    func connectDatabase() {
        networkRequest.connect()
    }
}

class NetworkRequest {
    func connect() {
        // connect to the database
    }
}
  • 위의 코드를 프로토콜을 이용해 고쳐주면, 아래와 같게 됩니다.
protocol Database {
    func connect()
}

class DatabaseController {

    private let database: Database

    init(db: Database) {
        self.database = db
    }

    func connectDatabase() {
        database.connect()
    }
}

class NetworkRequest: Database {
    func connect() {
        // Connect to the database
    }
}
  • 이로서 구체적인 것이 아닌, 추상화 된 것에 의존할 수 있게 됩니다.

5줄 요약

Quick Summary
In your own journey to become a better engineer, keep in mind the following SOLID principles.

  • 하나의 클래스는 하나의 책임만을 갖습니다.
  • 클래스는 확장에는 열려있어야 하지만, 수정에는 닫혀있어야 합니다.
  • 자식 클래스는 부모 클래스의 정의를 깨뜨려서는 안됩니다.
  • 필요한 기능만을 적용해야합니다.
  • 추상화에 의존하고, 구체적인 것에 의존해서는 안됩니다.

[참고링크]
https://betterprogramming.pub/an-ios-engineers-perspective-on-solid-principles-bf46ddc25d47
https://www.nextree.co.kr/p6960/
https://dongminyoon.tistory.com/49

'swift' 카테고리의 다른 글

[swift] Delegate와 UML  (0) 2022.09.20
    'swift' 카테고리의 다른 글
    • [swift] Delegate와 UML
    토털이
    토털이
    ios를 공부하는 학생의 블로그입니다.

    티스토리툴바