해당 게시글은 아래 Article을 참고하여 작성되었습니다.
https://medium.com/movile-tech/interface-segregation-principle-in-swift-1778bab4452b
SOLID란?
SOLID는 5개의 프로그래밍 디자인 원칙의 앞글자를 딴 합성어이다. 각 디자인 원칙들은 소프트웨어의 이해와 발전뿐 아니라 유연성과 유지보수성을 높여준다. 이러한 원칙들은 교수이자 소프트웨어 엔지니어인 Robert C. Martin(Uncle Bob으로 많이 알려진)으로부터 소개되었다.
- Single Responsibility Principle
- Open-Closed Principle
- Liskov Substitution Principle
- Interface Segregation Principle
- Dependency Inversion Principle.
오늘은 이중 ISP(Interface Segregation Principle)에 대해 알아보겠다.
ISP(Interface Segregation Principle)
ISP는 "클라이언트는 그들이 사용하지 않는 인터페이스에 의존하도록 강요되어선 안된다"는 원칙으로, 쉽게말해 인터페이스를 완전히 나누어야한다는 의미의 원칙이다. (Swift에서는 인터페이스 대신 프로토콜로 이해하자.)
- Fat Interface : Uncle Bob이 정의한 단어로, Interface가 자신이 가진 의미보다 더한 역할을 가진 경우를 뜻한다. 결과적으로보면 ISP를 지키지 않은 Inteface이다.
예시를 보자.
class Document {
var name: String
var content: String
init(name: String, content: String) {
self.name = name
self.content = content
}
}
class PDF {
var document: Document
init(document: Document) {
self.document = document
}
func create() -> Data {
// Creates the PDF with PDFKit. Not important to the example
return Data()
}
}
// 코드출처 : https://medium.com/movile-tech/interface-segregation-principle-in-swift-1778bab4452b
- PDF Class가 Document를 보유하고 있는 구조이다.
protocol Machine {
func convert(document: Document) -> PDF?
func convert(document: Document) -> UIImage?
func fax(document: Document)
}
// 코드출처 : https://medium.com/movile-tech/interface-segregation-principle-in-swift-1778bab4452b
- Document 객체를 파라미터로 받는 함수들이 존재하는 Machine protocol이다.
class FaxMachine: Machine {
func convert(document: Document) -> PDF? {
return nil // This is because a fax machine cannot do that
}
func convert(document: Document) -> UIImage? {
return nil // This is because a fax machine cannot do that
}
func fax(document: Document) {
Swift.print("Implementation of the fax here. This is just an example, so this implementation is not relevant")
}
}
class NewIphone: Machine {
func convert(document: Document) -> PDF? {
return PDF(document: document)
}
func convert(document: Document) -> UIImage? {
// Here we could create an UIImage object with represents an image
// with the name and the content of the document. But to make it simpler we won't
return UIImage()
}
func fax(document: Document) {}
}
class UltraMachine: Machine {
func convert(document: Document) -> PDF? {
return PDF(document: document)
}
func convert(document: Document) -> UIImage? {
// Here we could create an UIImage object with represents an image
// with the name and the content of the document. But to make it simpler we won't
return UIImage()
}
func fax(document: Document) {
print("Implementation of the ultra machine here. This is just an example, so this implementation is not relevant")
}
}
// 코드출처 : https://medium.com/movile-tech/interface-segregation-principle-in-swift-1778bab4452b
- Machine protocol이 적용된 class들이다.
- FaxMachine : fax 메소드만 수행한다. 하지만 protocol을 채택한 이상 나머지도 어떻게든 구현은 해주어야 하기 때문에 nil을 반환하는 형태로 구현했다.
- NewIphone : convert들만 수행하고 fax는 수행하지 않는다.
- UltraMachine : 모든 메소드를 수행한다.
당연히 위 예시는 ISP를 지키지 않았다. protocol만 봐도 2가지 책임을 지고 있어 SRP(Single Responsibility Principle)를 어겼다. 그런데 실질적으로는 protocol의 기능을 모두 필요로 하지 않는데도 protocol을 채택했다. 이 경우 protocol의 특정 기능이 수정될 때 그 기능을 구현하지 않는 객체여도 다시 코드를 짜야한다. 또한 Machine protocl을 채택하고 있음에도 기능구현이 제대로 되어 있지 않아 기능을 사용할 수 없는 문제도 발생한다.
이 코드를 ISP를 지키도록 바꿔보겠다.
protocol DocumentConverter {
func convert(document: Document) -> PDF
func convert(document: Document) -> UIImage
}
protocol Fax {
func fax(document: Document)
}
// 코드출처 : https://medium.com/movile-tech/interface-segregation-principle-in-swift-1778bab4452b
class FaxMachine: Fax {
func fax(document: Document) {
print("Implementation of the fax here. This is just an example, so this implementation is not relevant")
}
}
class NewIphone: DocumentConverter {
func convert(document: Document) -> PDF {
return PDF(document: document)
}
func convert(document: Document) -> UIImage {
// Here we could create an UIImage object with represents an image
// with the name and the content of the document. But to make it simpler we won't
return UIImage()
}
}
class UltraMachine: DocumentConverter & Fax {
func convert(document: Document) -> PDF {
return PDF(document: document)
}
func convert(document: Document) -> UIImage {
// Here we could create an UIImage object with represents an image
// with the name and the content of the document. But to make it simpler we won't
return UIImage()
}
func fax(document: Document) {
print("Implementation of the ultra machine here. This is just an example, so this implementation is not relevant")
}
}
// 코드출처 : https://medium.com/movile-tech/interface-segregation-principle-in-swift-1778bab4452b
방법은 간단하다. protocol을 분리하여 사용하는 것이다. 그리고 두가지 protocol의 기능을 모두 구현하는 class(위의 예시에선 UltraMachine)에는 둘 모두를 채택하도록 구현하면 된다. 이 경우 아래와 같이 protocol로 객체를 받아 활용함에도 아무런 문제가 없다.
func mainExample() {
let document = Document(name: "Document Name", content: "Document Content")
let ultraMachine: DocumentConverter = UltraMachine()
let pdf: PDF = ultraMachine.convert(document: document)
print(pdf)
}
// 코드출처 : https://medium.com/movile-tech/interface-segregation-principle-in-swift-1778bab4452b
ISP를 지키지 않는다는 것은 Fat Interface를 구현한다는 것과 같은 말이다. Fat Interface는 전혀 응집력있는 Interface가 아니며 주로 하나 이상의 책임을 가지기 때문에 SRP와도 연관된다.
ISP를 잘지키면 불필요한 기능구현 및 코드 재건축을 방지할 수 있고, 코드의 이해도와 테스트 용이성 측면에도 좋을 것으로 판단된다. 이를 위해 작은 단위로 책임을 나눠서 interface(protocol)를 구현하는 연습을 해야겠다.
'💻 CS > 객체지향' 카테고리의 다른 글
SOLID in Swift (5): DIP(Dependency Inversion Principle) (0) | 2021.11.09 |
---|---|
SOLID in Swift (3): LSP(Liskov Substitution Principle) (0) | 2021.10.31 |
SOLID in Swift (2): OCP(Open-Closed Principle) (0) | 2021.10.29 |
SOLID in Swift (1): SRP(Single Responsibility Principle) (0) | 2021.10.27 |