해당 게시글은 아래 Article을 참고하여 작성되었습니다.
https://medium.com/movile-tech/single-responsibility-principle-in-swift-61ee11ee81b5
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 위반 여부를 판단하고 반영해보는 게 좋은 코드를 작성하는데 도움이 될 것 같다.
'💻 CS > 객체지향' 카테고리의 다른 글
SOLID in Swift (5): DIP(Dependency Inversion Principle) (0) | 2021.11.09 |
---|---|
SOLID in Swift (4): ISP(Interface Segregation Principle) (0) | 2021.11.02 |
SOLID in Swift (3): LSP(Liskov Substitution Principle) (0) | 2021.10.31 |
SOLID in Swift (2): OCP(Open-Closed Principle) (0) | 2021.10.29 |