suvera-dev 🥊

Swift) SOLID 원칙 in Swift 볞묞

Language/Swift

Swift) SOLID 원칙 in Swift

suvera 2022. 4. 14. 22:06

SOLID 원칙읎란 ? 

 

SOLID 원칙읎란 객첎지향 섀계에 더 좋은 아킀텍쳐륌 섀계하Ʞ 위핎

지쌜알하는 원칙듀의 5가지륌 앞의 앜얎만 따서 정늬한 닚얎입니닀. 

 

 

SOLID 원칙을 왜 알아알하죠 ? 

 

개발을 할 때 당장 Ʞ능을 구현하는 것도 쀑요하지만,

새롭게 ì–Žë–€ Ʞ능읎 추가되거나 유지볎수가 필요할 때

더욱 생산성 있고 유연하게 대처가 가능핎알 좋겠죠.?

 

읎러한 좋은 섀계륌 위한 최소한의 원칙듀을 정늬한게 SOLID 원칙읎띌고 

할 수 있는데요.  VIPER나 MVVM도 몚두 읎런 원칙에 입각핎서 만듀얎졌닀고 합니닀.

 

 

SOLID 원칙을 적용하여 섀계하멎 !

 

1. ìž¬ì‚¬ìš©ê³Œ 유지ꎀ늬가 쉬욎, 변겜에 유연한 윔드륌 가지게 됩니닀.

읎는 튌튌한 소프튞웚얎륌 만듀 수 있게 하며 높은 확장성을 가지게 합니닀.

2. 높은 응집력곌 낮은 결합도 ( High Cohesion, Low coupling ) 을
전공 책에서 볎는게 아니고 낮 윔드에서 볌 수 있닀 ! 

 

 

 

추가적윌로, Swift 에서도 SOLID 원칙을 알고 적용핎알되는 읎유 ? 

 

Swift는 멀티팚러닀임 얞얎읎지만 Foundation , UIKit 등 

윔윔아터치 프레임워크듀은 Ʞ볞적윌로 OOP에 귌간을 두고 있닀고 합니닀. 

SOLID 원칙을 제대로 알고 있닀멎 평소에 쓰고 있는 큎래슀듀읎

왜 읎런식윌로 만듀얎졌는지 알수있고 앞윌로 우늬가 작성할 윔드에

얎떻게 ë…¹ì—¬ë‚Œìˆ˜ 있을지 파악할 수 있겠죠 ! 

 

처음부터 윔드륌 잘 지수는 없겠지만,

최소한 잘못된 윔드가 얎떀걎지륌 알고 플핎가자 ^^_ 띌는 컚셉 

 

귞러멎 5가지 앜얎에는 ì–Žë–€ 것듀읎 있고,

Swift 얞얎에는 얎떻게 적용읎 될까요 ?

 

 

알아볎러 가뎅시당.

 


1. SRP(Single Responsibility Principle) - 닚음 책임 원칙

 

큎래슀나 핚수륌 섀계할 때, 각 닚위듀은 당 하나의 책임만을 가젞알한닀는 원칙 !

 

하나의 큎래슀가 많은 메서드 및 역할을 가지게 된닀멎 ? 

큎래슀 낎부 핚수끌늬 강한 결합읎 발생할 수 있습니닀.

 

큎래슀 낎에서 수정 및 추가륌 진행할 때,

가독성 저하 및 유지볎수 비용 슝가 등의 ë¬žì œê°€ 발생하겠죠.. 

 

각 큎래슀 별로 책임을 적절하게 나눠서

응집도는 높고 결합도는 낮은 프로귞랚을 섀계하는게 닚음 책임 원칙 !

 

SRP의 가장 대표적읞 위반 사례는 Massive View Controller í˜„상입니닀.

요거 많읎 듀얎뎀죠..? 읎런 MVC의 묞제륌 핎결하Ʞ 위핎

MVVM, MVP, VIPER 등 여러 닀륞 팚턎듀읎 생겚나고 시도되고 있는데,

귞것듀의 공통점은 너묎 많은 역할을 하고 있는 뷰컚튞례러륌 쪌개서

닚음 책임만을 가지는 여러 큎래슀륌 만듀렀고 하는 것입니닀 ! 

 

SRP륌 지킀Ʞ 위핎 당장 시도핎볌 수 있는 것 ? 

큎래슀륌 작게 만듀자 ! 

사싀 큎래슀가 작닀는 것읎 SRP의 충분조걎은 아니지만, 필요조걎입니닀 !

 

갑자Ʞ 슀쳐지나가는 앱잌 프로젝튞...

슀위프튞 늰튞에 10-200룰을 적용핎놚던... 

10-200룰읎란 핚수는 10쀄 읎낎, 큎래슀는 200쀄 읎낎로 만드는 것입니닀.. 

 

앱잌 정도의 프로젝튞는 큰 묎늬없읎 가능했지만

가끔 Ʞ능읎 많았던 뷰컚은 .. 컀밋읎 안되가지구 끙끙 늬팩토링 했었는데 

읎런 큰 귞늌읎 있었닀니.. 

 

핚수는 10쀄 룰을 지킀렀멎 핚수는 하나의 작업만 핎알된닀 !!

띌는 원칙을 지킬 수 밖에 없고 추상화 레벚에 대핮 고믌을 하게 됩니닀.

작업(핚수)을 얎느 수쀀까지 쪌개서

섀명(추상화)할 것읞지에 대핮 고믌을 하게 만듀얎 쀍니닀.

 

‘큎래슀는 200쀄’ 룰을 지킀렀멎 프로퍌티나 핚수, 메서드륌

귞냥 손읎 가는 ê³³ 아묎데나 만듀얎서 ì“ž 수 없습니닀.

ꌭ 얘가 읎 곳에 있얎알 하는 읎유륌 ì°Ÿì•„ì•Œ 하고,

핎당 큎래슀에 있을 읎유가 없윌멎 SRP 위반읎닀.

 

가장 쉜게 판당할 수 있는 방법 하나는, 핚수나 메서드 낎부에서

self의 프로퍌티나 메서드륌 얌마나 쓰고 있는지 볎는 것읎띌고 합니닀.

 

만앜 하나도 쓰고 있지 않닀멎 ê·ž 큎래슀 안에 있을 읎유가 전혀 없는 것읎띌고 

생각핎도 되겠죠..? 아묎래도..? 

 

또는 self의 혞출 빈도가 적을수록 큎래슀와 연ꎀ성읎 떚얎지는 것읎니

나쀑에 큎래슀륌 늬팩토링 하거나 닀읎얎튞 시쌜알 할 상황읎 였멎 우선적윌로 낎쫓을 후볎로 

점찍얎두는 귞런... 것읎죠 ! 

 

SRP는 몚든 팚턎의 시작읎띌고 합니닀. 

SRP륌 제대로 지킀지 못한 채로 윔딩을 하멎

ê·ž ì–Žë–€ 팚턎을 도입핎볎렀고 핮도 잘 안될 가능성읎 높닀고 합니닀.

얎메메 SRP띌도.. 음닚.. 아좌좌

 

큎래슀는 하나의 역할만 핎알한닀는 것,

객첎지향을 ë°°ìš°ë©Žì„œ 가장 뚌저 배우는 원칙임에도 정말 지킀Ʞ 쉜지 않은 것 같넀요 !

 

 

예시 윔드 

 

나쁜 예시 

class LoginService {
    func login(id: String, pw: String) {
        let userData = requestLogin()
        let user = decodeUserInform(data: userData)
        saveUserOnDatabase(user: user)
    }
    
    private func requestLogin() -> Data {
        // Call API
        return Data()
    }
    
    private func decodeUserInform(data: Data) -> User {
        // Decoding User Inform from Data
        return User(name: "", age: 10)
    }
    
    private func saveUserOnDatabase(user: User) {
        // Save User
    }
}

위의 LoginService띌는 큎래슀 낎부에서 

API 혞출, 디윔딩, 유저 데읎터 저장 등 여러가지 역할을 하고 있넀요 ! 

슉, 큎래슀 낎부에서 하나의 책임읎 아닌 여러책임을 동시에 지고 있닀고 할 수 있습니닀 

 

좋은 예시

protocol APIHandlerProtocol {
    func requestLogin() -> Data
}

protocol DecodingHandlerProtocol {
    func decode<T>(from data: Data) -> T
}

protocol DBhandlerProtocol {
    func saveOnDatabase<T>(inform: T)
}

class LoginService {
    let apiHandler: APIHandlerProtocol
    let decodingHandler: DecodingHandlerProtocol
    let dbHandler: DBhandlerProtocol
    
    init(apiHandler: APIHandlerProtocol,
         decodingHandler: DecodingHandlerProtocol,
         dbHandler: DBhandlerProtocol) {
        self.apiHandler = apiHandler
        self.decodingHandler = decodingHandler
        self.dbHandler = dbHandler
    }
    
    func login() {
        let loginData = apiHandler.requestLogin()
        let user: User = decodingHandler.decode(from: loginData)
        dbHandler.saveOnDatabase(inform: user)
    }
}

위의 나쁜 에시와 비교핎서 각각 DB, Decoder, APIHandler 역할을 하는

프로토윜을 만듀얎죌고 각자의 역할만하는 메소드만을 구현하게 하였습니닀.

귞늬고 LoginService에서는 닚지 읎 프로토윜듀을 활용핎서 상혞작용만을 하고 있습니닀.

 

읎전곌 비교핎서 확싀히 LoginService는 로귞읞에 ꎀ렚된 로직만을 닀룚는데

각각의 몚듈듀을 활용핎서 로귞읞에 ꎀ한 책임만을 가지고 있닀는 것을 알 수 있습니닀.

 

 


 

 

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

 

확장에는 엎렀있윌나 변겜에는 닫혀있얎알한닀는 원칙 

 

확장에 엎렀있닀 ? 큎래슀의 Ʞ능을 확장하는 것읎 쉬워알된닀.

변겜에는 닫혀있닀 ? Ʞ졎에 구현되얎 있는 것듀을 바꟞지 않고 큎래슀륌 확장할 수 있얎알한닀.

 

슉, ì–Žë–€ Ʞ능을 추가할 때 Ʞ졎의 윔드는 만지지 않고 새로 동작하는 Ʞ능에 대핎서만

윔드가 작성읎 되얎알합니닀.

요구사항의 변겜읎나 추가사항읎 생Ʞ더띌도 êž°ì¡Ž 구성요소는 수정하지 않고,

필요한 Ʞ능만 확장을 통핎서 재사용 할수 있게 !! 

 

읎러한 특성듀은 추상화륌 통핎서 읎룚얎질 수 있습니닀 !

슀위프튞에서 추상화는 죌로 Protocol을 통핎 읎룚얎집니닀 !!

 

 ìœ„의 SRP 예제처럌 프로토윜을 읎용핎서

구현한닀멎 로직에서 변겜사항읎 있을 때 각각의 프로토윜을 구현하고 있는 객첎륌 왞부에서 죌입하멎

되Ʞ 때묞에 새로욎 Ʞ능에도 변화없읎 대응읎 가능하게 됩니닀.!

 

OCP륌 잘하Ʞ 위핎서는 변하지 않을 볞질적읞 특징 (읞터페읎슀) 와 

닀륞 요소로부터 식별될 수 있는 객첎의 볞질적읞 특징 (추상화)륌 잘 구분하는 것읎 쀑요 !

 

OCP 위반의 대표적읞 예는 ì–Žë–€ 타입에 대한 반복적읞 분Ʞ묞읎닀. 

슉, 하나의 enum에 대핮 여러 군데에서 반복적윌로 if/switch 묞을 쓰고 있닀멎

고믌을 핎뎐알한닀. 왜냐하멎, 읎런겜우에 Ʞ능 추가는 case륌 한쀄 추가하멎 되니까 쉜지만

귞렇게 하는 순간 핎당 enum을 슀위칭 하고 있는 몚든 윔드륌 ì°Ÿì•„ì„œ 수정핎쀘알한닀.

 

OCP는 if/switch륌 최대한 안 쓰는 방법을 통핎 연습할 수 있닀.

몚든 분Ʞ묞을 없애는 것은 불가능하고.. enum같읎 타입을 분Ʞ하는 지점에 대핎서 !

 

귌데 읎 얘Ʞ륌 듣고 말도 안된닀고 생각하는게 당연하닀.. 

얎떻게 if/switch 없읎 분Ʞ륌 하냐고 .. 

 

앞서 얞꞉한 10-200룰곌도 상혞볎완적읎띌는데 분Ʞ묞을 없애는 것만윌로도 

핚수 및 큎래슀의 Ꞟ읎륌 많읎 쀄음 수 있닀.

 

if/switch 묞을 대첎할 수 있는 방법 2가지 

1. protocol을 만듀고 상속받아 쓰는 방법 - 직접적윌로 OCP륌 지킀는 구조

2. 간닚하게 딕셔너늬륌 활용하는 방법 - OCP륌 지킀는 구조는 아니지만, 분Ʞ묞을 없애고 싶을떄 

제한적윌로 사용하멎 좋닀. 

 

예시윔드 

1번 프로토윜 활용 !

 

첫번짞 예시 - 동묌

Ʞ졎윔드 

class Dog {
    func makeSound() {
        print("멍멍")
    }
}

class Cat {
    func makeSound() {
        print("알옹")
    }
}

class Zoo {
    var dogs: [Dog] = [Dog(), Dog(), Dog()]
    var cats: [Cat] = [Cat(), Cat(), Cat()]
    
    func makeAllSounds() {
        dogs.forEach {
            $0.makeSound()
        }
        
        cats.forEach {
            $0.makeSound()
        }
    }
}

읎렇게 된닀멎 새로욎 동묌읎 생게을 때 Class로 새롭게 정의하고 

Zoo에 또 êž°ì¡Ž 윔드륌 수정하게 됩니닀. 새로욎 동묌에 맞는 메서드륌 추가핎쀘알겠죠.. ! 

읎렇게 불필요한 메서드듀읎 늘얎나게 됩니닀.

 

또, 여Ʞ서 dog 큎래슀의 makeSound() 띌는 메서드륌 변겜하게 되멎,

읎제는 dog 큎래슀에 없는 메서드읎Ʞ 때묞에 Zoo class에서

dogs.makeSound() 부분은 에러가 나게 됩니닀.

 

묞제 : 큎래슀륌 수정하멎 메서드륌 계속 수정핎알되고, 비슷한 형태의 메서드가 계속 추가되Ʞ도 합니닀.

귞렇닀멎 큎래슀의 볞질적읞 특징 + 확장되는 부분을 얎떻게 구분하는게 좋을까요?

 

protocol Animal {
    func makeSound()
}

class Dog: Animal {
    func makeSound() {
        print("멍멍")
    }
}

class Cat: Animal {
    func makeSound() {
        print("알옹")
    }
}

class Zoo {
    var animals: [Animal] = []
    
    func makeAllSounds() {
        animals.forEach {
            $0.makeSound()
        }
    }
}

슀위프튞에서는 변하지 않는 부분을 프로토윜을 사용하여 

읞터페읎슀화 합니닀. 변하는 부분은 각 큎래슀별로 프로토윜을 채택핎 구현핎죌멎 되겠죠.

 

읎렇게 프로토윜을 활용핎서 섀계륌 진행하게 되멎 새롭게 동묌읎 추가되얎도

Ʞ졎의 윔드는 만지지 않고 ê·žì € class 새로욎동묌: Animal윌로 선얞하여

구현만하멎 될 것 같습니닀. 귞렇게 되멎 Zoo 큎래슀에서는 Ʞ졎의 메서드륌 수정할 필요 없읎

새로욎 동묌듀을 추가하여 핚수륌 동작할 수 있게 될 것입니닀.

 

결론적윌로 프로토윜은 변겜에 닫혀있Ʞ 때묞에 makeSound() 띌는 메서드륌 수정할 필요가 없고

새로욎 동묌읎 추가되는 상황에서는 확장에 엎렀있닀고 할 수 있습니닀 ! 

 

 

2번 딕셔너늬 활용 ! 

앞서 섀명에서 분Ʞ묞을 없애고 싶을때가 있닀멎 , 요렇게 딕셔너늬륌 사용핎볎멎 좋을 것 같넀요 ! 

switch reason {
  case .initializing:
    self.instructionMessage = "Move your phone".localized
  case .excessiveMotion:
    self.instructionMessage = "Slow down".localized
  case .insufficientFeatures:
    self.instructionMessage = "Keep moving your phone".localized
  case .relocalizing:
    self.instructionMessage = "Move your phone".localized
}

//적절한 곳에 딕셔너늬 생성
let trackingStateMessages: [TrackingState.Reason : String] 
                         = [.initializing.        : "Move your phone".localized,
                            .excessiveMotion      : "Slow down".localized,
                            .insufficientFeatures : "Keep moving your phone".localized,
                            .relocalizing         : "Move your phone".localized]

//switch묞 대첎
self.instructionMessage = trackingStateMessages[reason]

 

3번 추가 예시 

 

1번 예시에 OCP륌 적용핎볎멎, 읎렇게 변하지 않는 부분은 프로토윜로 선얞핎죌고,

각 종류별 핞듀러에서 프로토윜을 채택하여 구현핎쀄 수 있습니닀. 귞늬고

LoginService에서 필요한 타입의 apihandler륌 사용할 수 있습니닀 ! 

protocol APIHandlerProtocol {
    func requestAPI()
}

class getHandler: APIHandlerProtocol {
    func requestAPI() {
        print("getHandler")
    }
}

class postHandler: APIHandlerProtocol {
    func requestAPI() {
        print("postHandler")
    }
}

class putHandler: APIHandlerProtocol {
    func requestAPI() {
        print("putHandler")
    }
}

class APIService {
    var apiHandler: APIHandlerProtocol

    init(handlerType: APIHandlerProtocol) {
        apiHandler = handlerType
    }
    
    func requestAPIByHandler() {
        apiHandler.requestAPI()
    }
}

import UIKit

class ViewController: UIViewController {


    override func viewDidLoad() {
        super.viewDidLoad()
        let someService = APIService(handlerType: getHandler())
        someService.requestAPIByHandler()
        
        someService.apiHandler = putHandler()
        someService.requestAPIByHandler()
    }
}

 

 


 

 

3. LSP(Liskov Substitution Principle) - 늬슀윔프 치환 원칙

 

부몚큎래슀로 동작하는 곳에서 자식큎래슀 읞슀턎슀륌 넣얎죌얎도 대첎가 가능핎알한닀는 원칙

자식 큎래슀륌 구현할떄, Ʞ볞적윌로 부몚 큎래슀의 Ʞ능읎나 능력듀을 묌렀받습니닀.

여Ʞ서 자식 큎래슀는 동작을 할때 부몚 큎래슀의 Ʞ능듀을 제한하멎 안된닀는 뜻 ! 

 

슉, 부몚 큎래슀의 타입에 자식 큎래슀의 읞슀턎슀륌 넣얎도 똑같읎 동작하여알 합니닀.

 

자식큎래슀가 부몚큎래슀의 Ʞ능을 였버띌읎딩핎서 Ʞ능을 변겜하거나 제한하는 겜우에 

결곌값읎 닀륎게 나였므로, LSP 원칙을 위반한 것입니닀. 

 

 

전형적읞 위반 사례로는 직사각형을 상속받아서 만든 정사각형 큎래슀륌 생각하멎 됩니닀! 

정사각형은 너비와 높읎가 같아알 하Ʞ 때묞에 너비와 높읎륌 자유롭게 바꿀 수 있는

직사각형 부몚 큎래슀의 동작을 제한핎알만 원하는 동작을 만듀 수 있습니닀. 읎런 겜우가 LSP의 위반읎닀.

LSP륌 지킀Ʞ 위핎서는 직사각형읎 정사각형을 상속받거나, 아니멎 너비와 높읎륌 아예 let윌로 만듀얎버늜시닀 !! 

 

나쁜 예시

class Rectangle {
    var width: Float = 0
    var height: Float = 0
    
    var area: Float {
        return width * height
    }
}

class Square: Rectangle {
    override var width: Float {
        didSet {
            height = width
        }
    }
}

func printArea(of rectangle: Rectangle) {
	rectangle.height = 3
	rectangle.width = 6
	print(rectangle.area)
}

let rectangle = Rectangle()
printArea(of: rectangle)
// 18

let square = Square()
printArea(of: square)
// 36

싀제로 정사각형은 직사각형읎띌고 할 수 있습니닀.

읎 원늬에 따띌 프로귞랚을 닀음곌 같읎 섀계하게 되멎 묞제가 생Ʞ게 됩니닀.

didset 부분에서 부몚의 프로퍌티륌 였버띌읎딩을 통핎 

변겜하고 있Ʞ 때묞에, 자식 읞슀턎슀륌 생성핎서 

똑같은 메서드에 넣얎도 닀륞 결곌값읎 나였게 됩니닀 !

 

읎 겜우 부몚의 역할을 자식 읞슀턎슀에서 동음하게 수행하고 있지

못하Ʞ 때묞에 LSP 원칙을 위반한 사례띌고 할 수 있슎닀 !

 

좋은 예시 

protocol Shape {
    var area: Float { get }
}

class Rectangle: Shape {
    let width: Float
    let height: Float
    
    var area: Float {
        return width * height
    }
    
    init(width: Float,
         height: Float) {
        self.width = width
        self.height = height
    }
}

class Square: Shape {
    let length: Float
    
    var area: Float {
        return length * length
    }
    
    init(length: Float) {
        self.length = length
    }
}

위와 같읎 Rectangle, Square 둘닀 Shape띌는 프로토윜을 채택한 후, 

싀제 구현부(area)륌 큎래슀에게 넘Ʞ는 형태로 섀계하멎 LSP에 얎Ꞌ나지 않고 프로귞랚을 섀계할 수 있겠죠 !

Shape의 자늬륌 Square, Rectangle 둘 ë‹€ 몚두 대첎가 가능하Ʞ 때묞에 

LSP륌 위반하지 않습니닀. !

 

하지만, LSP륌 절대 얎Ʞ지 않고 프로귞래밍을 하는걎 얎렀욎 음읎띌고 합니닀..

하지만 너묎 많은 곳에서 LSP륌 얎ꞎ닀멎 묞제가 생ꞎ닀.

 

닀륞 티슀토늬 ì°žê³ ... 

 

상위 큎래슀륌 Ʞ쀀윌로 윔딩할 수 없얎지고, 귞렇게되멎 ìƒì† 자첎가 의믞가 없얎지고 OCPì¡°ì°š 지킬수 없게 된닀.

예륌 듀얎 UITableView는 UITableViewCell을 Ʞ쀀윌로 만듀얎젞 있Ʞ 때묞에

우늬가 만든 컀슀텀 셀읎 LSP륌 지킀지 않는닀멎 테읎랔뷰도 제대로 동작하지 않을 것읎닀.

또한 protocol륌 만드는 읎유도 추상화된 읞터페읎슀륌 Ʞ쀀윌로 윔드륌 작성하Ʞ 위핎서읞데

상속받은 타입읎 protocol의 메서드륌 퇎화시쌜버늬멎

프로토윜을 Ʞ쀀윌로 작성된 윔드듀읎 쀄쀄읎 망가질 수 밖에 없닀.

 

상속은 객첎 지향 프로귞래밍의 쀑요한 부분읎고 잘 ì“°ë©Ž 유용하지만 잘못된 상속을 만듀멎 묞제가 발생할 수 있닀.

반멎 때에 따띌서는 LSP륌 위반핚윌로썚 펞늬핚곌 닚순핚을 얻게 될 수도 있닀.

 

결론적윌로, LSP륌 몚든 곳에서 지킀렀고만 하멎 비횚윚적읎고

너묎 자죌 위반하멎 예상하지 못한 결곌듀 때묞에 안정성을 핎치게 된닀.

귞러므로 상속을 만듀때는 LSP 위반읞지 아닌지륌 숙지하고 사용하는 것읎 좋겠닀.

 

ê²°ë¡  : 지나친 LSP 는 비횚윚적읎고, 지나친 LSP 위반은 안정성을 핎치Ʞ 때묞에

둘 사읎에서 얎느정도 추구할 것읞지에 대한 판닚읎 쀑요할 것 같습니닀 ! 

 

 


 

 

 

 

4. 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
    }
}

Line, Square 몚두 Shape을 상속받는 객첎읎지만

싀제로 Square는 length띌는 변수가 필요가 없고

Line은 area띌는 변수가 필요없게 됩니닀.

귞럌에도 닚지 Shape읎띌는 프로토윜을 채택한닀는 읎유만윌로 필요없는 Ʞ능을 구현하고 있습니닀.

 

읎런 겜우에 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
    }
}

Ʞ졎에 필요없는 Ʞ능듀을 구현하고 있던 읞터페읎슀듀을 더욱 섞분화하여 나누얎죌었습니닀.

 

AreaCalculatableShape, LenghtCalculatableShape윌로 각각 읞터페읎슀륌 섞분화시쌜

넓읎륌 구핎알하는 Shape에만 AreaCalculatableShape ì±„택하여 구현하고

Ꞟ읎륌 구핎알하는 Shape에만 LenghtCalculatableShape ì±„택하였습니닀.

각각 큎래슀에서 구현하지 않는 메서드가 없게 되죠 !

 

각각을 ISP의 원칙을 지킀는 프로귞랚의 섀계가 되었습니닀.

읎렇게 ISP의 핵심은 프로토윜의 분늬띌는 것을 확읞할 수 있습니닀 !

 

 

 


 

 

 

5. DIP(Dependency Inversion Principle) - 의졎ꎀ계 역전 원칙

 

상위 몚듈읎 하위 몚듈에 의졎하멎 안되고 두 몚듈 몚두 추상화에 의졎하게 만듀얎알 한닀는 원칙

 

큎래슀 사읎에는 의졎ꎀ계가 졎재할 수 밖에 없습니닀! 

의졎 ꎀ계가 졎재하되, 직접적윌로 구첎적읞 큎래슀끌늬 의졎하지 말고

ê·ž 사읎에 최대한 추상화된 읞터페읎슀륌 활용핎 의졎하띌는 것읎 DIP 원칙입니닀 !

 

상위 몚듈읎 하위몚듈에 의졎하멎 안된닀는 말읎 직접적윌로 의졎하지 말띌는 말 ! 

 

추상화륌 진행하여 각각의 몚듈에 더 추상화된 것에 의졎하게 만듀얎알한닀는 뜻입니닀. 

읎렇게 윔드륌 섀계핎알 재사용에도 유용하고 하나륌 수정하더띌도 닀륞 수정사항읎 

많읎 없는 좋은 프로귞랚을 섀계할 수 있게 됩니닀.

 

DIP 원칙은 나쀑에 Unit Test륌 진행할 때 더욱 쀑요하게 될 원칙읞데 

의졎성 죌입읎띌는 개념읎 나옵니닀 !.

 

상위 몚듈에 ì–Žë–€ 하위 몚듈을 사용할 때, 상위몚듈에서 직접적윌로 

하위몚듈을 쎈Ʞ화하지 않고 왞부에서 하위몚듈을 쎈Ʞ화 할 수 있게 하띌는 뜻입니닀 !

귞늬고 읎 상위 몚듈, 하위몚듈을 몚두 추상화된 객첎에 의졎할 수 있게 핎알합니닀.

 

나쁜예시 

class APIHandler {
    func request() -> Data {
        return Data(base64Encoded: "This Data")!
    }
}

class LoginService {
    let apiHandler: APIHandler = APIHandler()
    
    func login() {
        let loginData = apiHandler.request()
        print(loginData)
    }
}

현재 상위몚듈읞 LoginService가 하위 몚듈읞 APIHandler에 의졎하고 있는 ꎀ계로

만앜  APIHandler의 구현 방법읎 변화하게 되멎 프로귞랚에 영향을 믞치게 되고

새롭게 LoginService띌는 상위몚듈을 수정핎알하는 상황읎 음얎날 수 있습니닀.

직접 APIHandler 큎래슀륌 가젞와서 의졎을 하고 있Ʞ 때묞에 닀륞 Handler가 듀얎였는 겜우? 

직접 윔드륌 수정하거나 추가할 음읎 많Ʞ 때묞 ! 

변화에 맀우 췚앜한 구조띌고 할 수 있습니닀.. 

 

읎러한 상황읎 DIP의 원칙을 얎ꞎ 프로귞랚의 섀계띌고 할 수 있습니닀.

 

읎륌 수정하Ʞ 위핎 의졎성 죌입읎띌는 것을 사용핎서 수정핎볎겠습니닀 

(DIP륌 유지하Ʞ 위핎 의졎성을 역전시킀Ʞ.)

 

좋은 예시 

 

protocol APIHandlerProtocol {
    func requestAPI() -> Data
}

class LoginService {
    let apiHandler: APIHandlerProtocol
    
    init(apiHandler: APIHandlerProtocol) {
        self.apiHandler = apiHandler
    }
    
    func login() {
        let loginData = apiHandler.requestAPI()
        print(loginData)
    }
}

class LoginAPI: APIHandlerProtocol {
    func requestAPI() -> Data {
        return Data(base64Encoded: "User")!
    }
}

let loginAPI = LoginAPI()
let loginService = LoginService(apiHandler: loginAPI)
loginService.login()

읎렇게 작성하게 되멎 LoginService는 Ʞ졎에 APIHandler에 의졎하지 않고

추상화 시킚 객첎읞 APIHandlerProtocol에 의졎하게 됩니닀.

 

귞렇Ʞ 때묞에 APIHandlerProtocol의 구현부는 왞부에서 변화에 따띌 지정핎서 지정핎죌멎 되Ʞ 때묞에 

LoginService는 구현부에 상ꎀ없읎 좀 더 변화에 믌감하지 않은 

DIP의 원칙을 지킚 프로귞랚을 섀계할 수 있게 됩니닀.

 

( 프로토윜 구현부는 왞부에서 처늬핎쀀 닀음, 로귞읞 서비슀띌는 읞슀턎슀륌 만듀 때

의졎성 죌입을 처늬핎 죌Ʞ 때묞에 볎닀 유연한 구조로 윔드 작성을 할 수 있닀. )

 

읎렇게 왞부에서 낎부의 변수륌 쎈Ʞ화핎서 의졎ꎀ계륌 가지는 겜우륌 의졎성 죌입읎띌고 하게 됩니닀.

읎 때, 의졎성 죌입을 추상화시쌜 진행하게 되멎 더욱 변화에는 안전한 프로귞랚을 섀계할 수 있게 됩니닀.

 

protocol APIHandlerProtocol {
    func requestAPI()
}

class getHandler: APIHandlerProtocol {
    func requestAPI() {
        print("getHandler")
    }
}

class postHandler: APIHandlerProtocol {
    func requestAPI() {
        print("postHandler")
    }
}

class putHandler: APIHandlerProtocol {
    func requestAPI() {
        print("putHandler")
    }
}

class APIService {
    var apiHandler: APIHandlerProtocol

    init(handlerType: APIHandlerProtocol) {
        apiHandler = handlerType
    }
    
    func requestAPIByHandler() {
        apiHandler.requestAPI()
    }
}

import UIKit

class ViewController: UIViewController {


    override func viewDidLoad() {
        super.viewDidLoad()
        let someService = APIService(handlerType: getHandler())
        someService.requestAPIByHandler()
        
        someService.apiHandler = putHandler()
        someService.requestAPIByHandler()
    }
}

앞서 볎여드늰 윔드에서도 의졎성 죌입을 핎죌는 부분읎 있었습니닀 !

읎처럌 5가지의 원칙읎 서로 ꎀ계륌 갖고 있는 것도 있닀.

ISP륌 지킀Ʞ 위핎 ê²°êµ­ SRP륌 지킀게 된닀던가.. 

 

귞러나 읎 원칙을 몚두 지킀멎서 프로귞랚을 섀계하Ʞ는 힘듀닀고 생각한닀

닀듀 귞렇닀고 말하Ʞ도 하고,, 하지만 읎걞 공부하멎서, 얎떀게 나쁜 윔드읎고 얎떀게 좋은 윔드읞지

파악할 수 있었던 것 같닀 넘 좋았닀 !! 

 

 

 

 

 

 

 

예시 윔드 ì°žê³  

 

[SWIFT] Swift SOLID 원칙

안녕하섞요 였늘은 SOLID 원칙에 대핮 정늬륌 핎볎렀고 합니닀. 귌데, 읎것을 Swift 얞얎에 접목시쌜서 읎핎핎볎고 작성핎볎겠습니닀 🙌 SOLID란? SOLID 원칙읎란 객첎지향 섀계에 더 좋은 아킀텍쳐

dongminyoon.tistory.com

 

슀위프튞로 닀시볎는 객첎지향 프로귞래밍: 플핎알할 윔딩 습ꎀ · Soojin Ro

배겜 컎퓚터와 회화(painting)륌 전공했고 ꜀ 성공한 누군가가 프로귞래뚞는 곌학자볎닀 화가와 공통점읎 더 많닀고 했닀. 정말로, 프로귞래밍을 잘하렀는 녞력을 하닀볎멎 곌학볎닀는 겜험적윌

soojin.ro

 

[Design-Pattern-In-Swift] 1. SOLID 원칙을 Swift 윔드에 적용핎볎Ʞ

GoF의 디자읞팚턎읎띌는 책을 Ʞ반윌로 디자읞 팚턎에 대핮 공부륌 하렀고 합니닀! 프로젝튞에서 반복적윌로 발생하는 닀양한 묞제륌 핎결하Ʞ 위핎 사용된 방법을 구조화 시킚것읎 디자읞 팹

i-colours-u.tistory.com

 

Comments