Notice
Recent Posts
Recent Comments
Link
관리 메뉴

김종권의 iOS 앱 개발 알아가기

[Clean Architecture] 1. 코드로 알아보는 SOLID - SRP(Single Responsibility Principle) 단일 책임 원칙 본문

Clean Architecture/Clean Architecture 코드

[Clean Architecture] 1. 코드로 알아보는 SOLID - SRP(Single Responsibility Principle) 단일 책임 원칙

jake-kim 2021. 9. 17. 01:45

0. 코드로 알아보는 SOLID - 클래스 다이어그램 필수 표현

1. 코드로 알아보는 SOLID - SRP(Single Responsibility Principle) 단일 책임 원칙

2. 코드로 알아보는 SOLID - OCP(Open Close Principle) 개방 폐쇄 원칙

3. 코드로 알아보는 SOLID - LSP(Liskov Substitution Principle) 리스코프 치환 원칙

4. 코드로 알아보는 SOLID - ISP(Interface Segregation Principle) 인터페이스 분리 원칙

5. 코드로 알아보는 SOLID - DIP(Dependency Inversion Principle, testable) 의존성 역전 원칙

6. 코드로 알아보는 SOLID - Coordinator 패턴 화면전환

7. 코드로 알아보는 SOLID - Network, REST (URLSession, URLRequest, URLSessionDataTask)

8. 코드로 알아보는 SOLID - 캐싱 Disk Cache (UserDefeaults, CoreData)

SRP(Single Responsibility Principle) 개념

  • 하나의 모듈은 오직 하나의 Actor에 대해서만 책임져야 한다는 개념
    • Actor: 한 명 이상의 사람들
    • 모듈: 함수와 데이터 구조로 구성된 응집된(cohesive) 집합
  • 주의: `단 하나의 일만 해야 한다는 원칙`은 함수의 개념
    • 큰 함수들을 작은 함수들로 리펙토링 할 때 사용되는 더 저수준에서 사용되는 개념

SRP를 지키지 않으면 발생하는 문제

  • SRP를 지키지 않는 코드: 하나의 모듈에 여러개의 Actor에 대해서 책임지는 경우

class Employee {
    enum WorkType {
        case finance // 재무
        case human // 인사
        case development // 개발
    }
    let workType: WorkType

    init(workType: WorkType) {
        self.workType = workType
    }
}
  • 3개의 Actor에 대해서 책임지는 Calculator 모듈
    • calculatePay()와 reportHours()는 우연히 calculatedHour 프로퍼티에 접근
    • save()를 통해 계산된 hour를 DB에 저장하지만, 두 곳에서 변경되고 있으므로 잘못된 정보 저장할 가능성 존재
class Calculator {

    static var calculatedHour: Int?

    /// 업무 시간을 계산해서, 회계팀장에게 보고
    static func calculatePay() {
        let workTime = regularHours()
        calculatedHour = workTime
        let pay = workTime * 1000

        print("report \(pay) to CFO")
    }

    /// 업무 시간을 계산해서, 인사팀장에게 보고
    static func reportHours() {
        let workTime = regularHours()
        calculatedHour = workTime

        print("report \(workTime) to COO")
    }

    /// 현재 계산된 업무시간을 DB에 저장
    static func save() {
        guard let calculatedHour = calculatedHour else { return }

        print("save \(calculatedHour) to DB")
    }

    /// 초과 근무를 제외한 업무 시간을 계산하는 메소드 (코드의 중복을 피하기 위한 편의 함수)
    static private func regularHours() -> Int {
        return Int.random(in: (0...100))
    }
}

해결 방법

  • 서로 다른 두 Actor가 공통으로 regularHours()와 같은 메소드를 사용할 수 없도록 애초에 코드를 분리하여 사전에 방지하고 데이터도 분리되도록 설계
  • 코드를 분리하면 호출할 때 세 가지를 다 알고 있어야하므로, Facade패턴으로 사용

  • 사용하는 쪽 (actor)
func main() {
    let data = Data()

    let financer = Employee(workType: .finance, data: data)
    financer.employeeFacade.calculatePay()

    let `operator` = Employee(workType: .human, data: data)
    `operator`.employeeFacade.reportHours()

    let developer = Employee(workType: .development, data: data)
    developer.employeeFacade.save()
}

 

* 전체 소스 코드, SRPwithFacade.swift 참고: https://github.com/JK0369/SOLID

// SRPwithFacade.swift

class Employee {
    enum WorkType {
        case finance // 재무
        case human // 인사
        case development // 개발
    }
    let workType: WorkType
    let data: Data
    let employeeFacade: EmployeeFacade

    init(workType: WorkType, data: Data) {
        self.workType = workType
        self.data = data
        self.employeeFacade = EmployeeFacade(payCalculator: PayCalculator(data: data),
                                             hourReporter: HourReporter(data: data),
                                             employeeSaver: EmployeeSaver(data: data))
    }
}

class EmployeeFacade {

    private let payCalculator: PayCalculator
    private let hourReporter: HourReporter
    private let employeeSaver: EmployeeSaver

    init(payCalculator: PayCalculator, hourReporter: HourReporter, employeeSaver: EmployeeSaver) {
        self.payCalculator = payCalculator
        self.hourReporter = hourReporter
        self.employeeSaver = employeeSaver
    }

    func calculatePay() {
        payCalculator.calculatePay()
    }

    func reportHours() {
        hourReporter.reportHours()
    }

    func save() {
        employeeSaver.save()
    }
}

class PayCalculator {

    var data: Data

    init(data: Data) {
        self.data = data
    }

    /// 업무 시간을 계산해서, 회계팀장에게 보고
    func calculatePay() {
        let workTime = Int.random(in: (0...100))
        let pay = workTime * 1000
        data.calculatedHourByPay = pay

        print("report \(pay) to CFO")
    }
}

class HourReporter {

    var data: Data

    init(data: Data) {
        self.data = data
    }

    /// 업무 시간을 계산해서, 인사팀장에게 보고
    func reportHours() {
        let workTime = Int.random(in: (0...100))
        data.calculatedHourByHour = workTime

        print("report \(workTime) to COO")
    }
}

class EmployeeSaver {

    var data: Data

    init(data: Data) {
        self.data = data
    }

    /// 현재 계산된 업무시간을 DB에 저장
    func save() {
        print("save \(data.calculatedHourByPay), \(data.calculatedHourByHour) to DB")
    }
}

class Data {
    var calculatedHourByPay: Int?
    var calculatedHourByHour: Int?
}

func main() {
    let data = Data()

    let financer = Employee(workType: .finance, data: data)
    financer.employeeFacade.calculatePay()

    let `operator` = Employee(workType: .human, data: data)
    `operator`.employeeFacade.reportHours()

    let developer = Employee(workType: .development, data: data)
    developer.employeeFacade.save()
}

 

Comments