Youngminah / TIL

🌱 iOS 필수 숙지 개념 레포
282 stars 20 forks source link

SOLID 객체지향의 5가지 원칙 #93

Closed Youngminah closed 2 years ago

Youngminah commented 2 years ago

image

SOLID

객체 설계에 필요한 5가지 원칙으로써 유지보수가 쉽고, 유연하고, 확장이 쉬운 소프트웨어를 만들기 위한 수단으로 본다.

객체의 책임이란

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

SRP 적용전

class Handler {
  func handle() {
    let data = requestDataToAPI()
    let array = parse(data: data)
    saveToDB(array: array)
  }

  private func requestDataToAPI() -> Data {
  // send API request and wait the response
  }

  private func parse(data: Data) -> [String] {
  // parse the data and create the array
  }

  private func saveToDB(array: [String]) {
  // save the array in a database (CoreData/Realm/...)
  }
}

SRP 적용후

class Handler {

    let apiHandler: APIHandler
    let parseHandler: ParseHandler
    let dbHandler: DBHandler

    init(apiHandler: APIHandler, parseHandler: ParseHandler, dbHandler: DBHandler) {
        self.apiHandler = apiHandler
        self.parseHandler = parseHandler
        self.dbHandler = dbHandler
    }

    func handle() {
        let data = apiHandler.requestDataToAPI()
        let array = parseHandler.parse(data: data)
        dbHandler.saveToDB(array: array)
    }
}

class APIHandler {

    func requestDataToAPI() -> Data {
        // send API request and wait the response
    }
}

class ParseHandler {

    func parse(data: Data) -> [String] {
        // parse the data and create the array
    }
}

class DBHandler {

    func saveToDB(array: [String]) {
        // save the array in a database (CoreData/Realm/...)
    }
}





O: OCP ( Open-Closed Principle ): 개방, 폐쇄 원칙

OCP 적용전

enum Country {
  case korea
  case japan
  case china
}

class Flag {
  let country: Country

  init(country: Country) {
    self.country = country
  }
}

func printNameOfCountry(flag: Flag) {
  switch flag.country {
    case .china:
      print("중국")
    case .korea:
      print("한국")
    case .japan:
      print("일본")
  }
}

OCP 적용후

enum Country {
  case korea
  case japan
  case china

  var name: String {
    switch self {
      case .china:
        return "중국"
      case .korea:
        return "한국"
      case .japan:
        return "일본"
    }
  }
}

class Flag {
  let country: Country

  init(country: Country) {
    self.country = country
  }
}

func printNameOfCountry(flag: Flag) {
  print(flag.country.name)
}
protocol Country {
  var name: String { get }
}

struct Korea: Country {
  let name: String = "한국"
}

struct Japan: Country {
  let name: String = "일본"
}

struct China: Country {
  let name: String = "중국"
}

class Flag {
  let country: Country

  init(country: Country) {
    self.country = country
  }
}

func printNameOfCountry(flag: Flag) {
  print(flag.country.name)
}

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

LSP 적용전

class 직사각형 {
  var 너비: Float = 0
  var 높이: Float = 0
  var 넓이: Float {
    return 너비 * 높이
  }
}

class 정사각형: 직사각형 {
  override var 너비: Float {
    didSet {
      높이 = 너비
    }
  }
}

func printArea(of 직사각형: 직사각형) {
  직사각형.높이 = 5
  직사각형.너비 = 2
  print(직사각형.넓이)
}

let rectangle = 직사각형()
printArea(of: rectangle) //10
let square = 정사각형()
printArea(of: square) //4

LSP 적용후

protocol 사각형 {
  var 넓이: Float { get }
}

class 직사각형: 사각형 {
  private let 너비: Float
  private let 높이: Float

  init(너비: Float, 높이: Float) {
    self.너비 = 너비
    self.높이 = 높이
  }

  var 넓이: Float {
    return 너비 * 높이
  }
}

class 정사각형: 사각형 {
  private let 변의길이: Float

  init(변의길이: Float) {
    self.변의길이 = 변의길이
  }

  var 넓이: Float {
    return 변의길이 * 변의길이
  }
}





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

ISP 적용전

protocol Shape {
    var area: Float { get }
    var length: Float { get }
}

class Square: Shape {
    var width: Float
    var height: Float

    var area: Float {
        return width * height
    }

    var length: Float {
        return 0
    }

    init(width: Float,
         height: Float) {
        self.width = width
        self.height = height
    }
}

class Line: Shape {
    var pointA: Float
    var pointB: Float

    var area: Float {
        return 0
    }

    var length: Float {
        return pointA - pointB
    }

    init(pointA: Float,
         pointB: Float) {
        self.pointA = pointA
        self.pointB = pointB
    }
}

ISP 적용후

protocol AreaCalculatableShape {
    var area: Float { get }
}

protocol LenghtCalculatableShape {
    var length: Float { get }
}

class Square: AreaCalculatableShape {
    var width: Float
    var height: Float

    var area: Float {
        return width * height
    }

    init(width: Float,
         height: Float) {
        self.width = width
        self.height = height
    }
}

class Line: LenghtCalculatableShape {
    var pointA: Float
    var pointB: Float

    var length: Float {
        return pointA - pointB
    }

    init(pointA: Float,
         pointB: Float) {
        self.pointA = pointA
        self.pointB = pointB
    }
}

D: DIP (Dependency Inversion Principle) : 의존관계 역전 원칙

DIP 적용전

class 맥북13인치 {
  func 전원켜기() {}
}

class 개발자 {
  let 노트북: 맥북13인치 = 맥북13인치()

  func 개발시작() {
    노트북.전원켜기()
  }
}

class 개발자 { let 노트북: 맥북13인치

init(노트북: 맥북13인치) { self.노트북 = 노트북 }

func 개발시작() { 노트북.전원켜기() } }

- 객체생성을 외부에서 한경우 이를 DI라고 부름
- 그럼 DIP는 머냐? 

#### DIP 적용후
```swift
protocol 노트북 {
  func 전원켜기()
}

class 개발자 {
  let 노트북: 노트북

  init(맥북: 노트북) {
    self.노트북 = 맥북
  }

  func 개발시작() {
    노트북.전원켜기()
  }
}

class 맥북13인치: 노트북 {
  func 전원켜기() {}
}

class 맥북15인치: 노트북 {
  func 전원켜기() {}
}

class 레노버: 노트북 {
  func 전원켜기() {}
}
Youngminah commented 2 years ago

Dependency, 의존성이란?

import UIKit

struct Eat {
    func coffee() {
        print("아메리카노")
    }

    func meal() {
        print("피자")
    }
}

struct Person {
    var todayEat: Eat

    func coffee() {
        todayEat.coffee()
    }

    func meal() {
        todayEat.meal()
    }
}

Injection이란?

class Eat:Menu {
    var coffee: String
    var meal: String

    init(coffee: String, meal: String) {
        self.coffee = coffee
        self.meal = meal
    }

    func printCoffee() {
        print("아메리카노")
    }

    func printMeal() {
        print("피자")
    }
}
let menu = Eat(coffee: "아메리카노", meal: "피자")

Dependency Injection 의존성 주입의 장점

Dependency Inversion Principle : 의존관계 역전 법칙

protocol Menu {
    func printCoffee()
    func printMeal()
}

이후 Eat클래스는 Menu Protocol을 채택한 후, Protocol에 정의한 함수를 실체화 시켜준다.

class Eat: Menu {
    var coffee: String
    var meal: String

    init(coffee: String, meal: String) {
        self.coffee = coffee
        self.meal = meal
    }

    func printCoffee() {
        print("아메리카노")
    }

    func printMeal() {
        print("피자")
    }
}

이제부터 중요한 부분이 나온다. 기존의 방식과 다르게 todayEat변수는 추상적인 객체인 Menu타입에 의존하게 된다. 여기서 changeMenu함수를 활용해서 의존성 주입을 시킬 수 있다.

struct Person {
    var todayEat: Menu

    func printCoffee() {
        todayEat.printCoffee()
    }

    func printMeal() {
        todayEat.printMeal()
    }

    mutating func changeMenu(menu: Menu) {
        self.todayEat = menu
    }
}

이렇게 구현한다면 Eat객체와 Person객체는 거의 독립적인 객체가 된다. Eat 객체를 수정하거나 Person을 수정한다고 해서 상대 객체를 함께 수정해야 하는 문제를 방지할 수 있다.

let menu = Eat(coffee: "아메리카노", meal: "피자")
let anotherMenu = Eat(coffee: "라떼", meal: "햄버거")

var suhshin = Person(todayEat: menu)

suhshin.printCoffee() // print 아메리카노
suhshin.changeMenu(menu: anotherMenu)
suhshin.printCoffee() // print 라떼