[회고] 신입 iOS 개발자가 되기까지 feat. 카카오 자세히보기

💻 CS/객체지향

SOLID in Swift (1): SRP(Single Responsibility Principle)

inu 2021. 10. 27. 11:00
반응형

해당 게시글은 아래 Article을 참고하여 작성되었습니다.
https://medium.com/movile-tech/single-responsibility-principle-in-swift-61ee11ee81b5

 

Single Responsibility Principle in Swift

First article of the series of five articles about SOLID and its use with Swift

medium.com

SOLID란?

SOLID는 5개의 프로그래밍 디자인 원칙의 앞글자를 딴 합성어이다. 각 디자인 원칙들은 소프트웨어에 대한 이해와 발전을 돕고 유연성과 유지보수성을 높여준다. 이러한 원칙들은 교수이자 소프트웨어 엔지니어인 Robert C. Martin(a.k.a Uncle Bob)으로부터 소개되었다.

  • Single Responsibility Principle
  • Open-Closed Principle
  • Liskov Substitution Principle
  • Interface Segregation Principle
  • Dependency Inversion Principle.

오늘은 이중 SRP(Single Responsibility Principle)에 대해 알아보겠다.

SRP (Single Responsibility Principle)

SRP는 해석 그대로 "하나의 class가 하나의 책임(single responsibility)만을 가져야 한다"는 원칙이다. 여기까지는 잘 알겠는데, 이를 어떻게 확인하면 좋을까? 특정 Class를 변경할 이유가 하나 이상이 되면 이는 하나 이상의 책임을 가진 것이라고 판단할 수 있다.

 

예시를 보자.

protocol Persistence {
    func save(_ square: Square)
}

class Square {
    let edge: Int
    let persistence: Persistence

    init(edge: Int, persistence: Persistence) {
        self.edge = edge
        self.persistence = persistence
    }

    // First Responsibility
    func area() -> Int {
        return edge * edge
    }

    // Second Responsibility
    func save() {
        persistence.save(self)
    }

    // Coupled Responsibilities
    func saveAccordingToArea() {
        let area = self.area()
        if area > 20 {
            persistence.save(self)
        }
    }
}

class GeometricGraphics {
    func draw(_ square: Square) {
        let squareArea = square.area()
        // Draw the square in the application screen
    }
}

// 코드출처 : https://medium.com/movile-tech/single-responsibility-principle-in-swift-61ee11ee81b5
  • Persistence protocol : Square 객체를 저장하는 save 함수에 대한 protocol. (SRP 개념엔 별로 중요하지 않으므로 해당 protocol을 따르는 클래스 구현은 생략)
  • Square : 정사각형을 표현한 객체로 edge값을 가진다. 면적을 계산하는 area(), persistence 객체를 사용해 정보를 저장하는 save(), 면적이 20보다 큰 경우 persistence 객체로 정보를 저장하는 saveAccordingToArea() 메서드를 가진다.
  • GeometricGraphics: 시각적으로 사각형을 그리는 class

위의 예시는 SRP를 위반한 대표적인 예시이다. 왜냐하면 하나의 Square Class가 면적 계산, 기하학적 정보저장에 대한 책임과 더불어 객체를 저장하는 지속성 책임이 있기 때문이다. 또한 이러한 두 가지 책임이 결합되어 saveAccordingToArea 메서드에서 나타난다. 이렇게 되면 특정 함수 하나에 대한 변경이 다른 책임에 영향을 끼칠 수 있고 그 반대의 경우도 발생 가능하다. 이러한 책임의 중복들은 테스트의 가능성을 방해할 뿐 아니라 코드를 읽고 이해하는 것에도 방해가 된다.

 

이 코드를 SRP를 지키도록 한번 개선시켜보겠다.

 

물론 여러방법이 있겠지만 Square 클래스를 SquareGeometrics 및 SquarePersistence로 분리해보는 것이 가장 단순하면서도 대표적인 방법이다. 첫 번째가 area() 메서드를 가질 것이고, 두 번째는 save() 메서드를 가지도록 짜는 것이다. saveAccordingToArea 같은 혼종은 그냥 없애고 이에 대한 응용은 그냥 이 메서드를 호출한 제 3자가 책임지도록 한다.

 

아니면 그냥 SquareGeometricsProtocol 및 SquarePersistenceProtocol로 protocol 2개를 만들어버리는 방법도 있다. 각각의 protocol에 area()와 save() 메소드를 포함시키고 Square 클래스가 이를 따르도록 하여 두 방법을 구현하도록 하는 것이다. 이 방법은 만약에 하나가 필요 없어지면 protocol 중 하나만 가져오는 방식으로 사용할 수 있다는 점에서 문제를 해결했다고 볼 수 있다. (cf. 이는 디자인 패턴 중 'Facade'와 매우 유사한 접근이다.)

 

두 번째 방법을 선택해 개선한 예시 코드이다.

protocol SquareGeometrics {
    func area() -> Int
}

protocol SquarePersistence {
    func save()
}

class Square: SquareGeometrics, SquarePersistence {
    let edge: Int
    let persistence: Persistence

    init(edge: Int, persistence: Persistence) {
        self.edge = edge
        self.persistence = persistence
    }

    // First Responsibility
    func area() -> Int {
        return edge * edge
    }

    // Second Responsibility
    func save() {
        persistence.save(self)
    }
}

class GeometricGraphics {
    // Note the dependency of this class in Square was changed for 
    // for just one of the responsibilities
    func draw(_ square: SquareGeometrics) {
        let squareArea = square.area()
        // Draw the square in the application screen
    }
}

// 코드출처  : https://medium.com/movile-tech/single-responsibility-principle-in-swift-61ee11ee81b5

이렇게 SRP를 따르도록 코드를 수정했다.

 

봤듯이 SRP는 '하나엔 하나의 책임을' 이라는 굉장히 단순한 원칙이다.

 

그런데 사실 이게 구분하기 어렵기도하고, 책임 중복이 테스트 등에 큰 영향이 없고 분리 안 했을 때의 이득이 더 크다고 판단되면 그냥 두기도 한다. 그래도 되도록이면 개발 전후에 SRP 위반 여부를 판단하고 반영해보는 게 좋은 코드를 작성하는데 도움이 될 것 같다.

반응형