Notice
Recent Posts
Recent Comments
Link
관리 메뉴

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

[Refactoring] 11-3 상속 리펙토링 (서브 클래스를 델리게이트로 바꾸기) 본문

Refactoring (리펙토링)

[Refactoring] 11-3 상속 리펙토링 (서브 클래스를 델리게이트로 바꾸기)

jake-kim 2023. 7. 27. 22:39

서브 클래스를 델리게이트로 바꾸기

  • 특정 타입에 따라 동작이 달라지는 경우, 주로 enum case로 분기하여 구현하지만 저번시간에 알아본대로(이전 글 참고) 상속 관계를 만들면 SRP를 지키면서 더욱 유지보수 하기 쉽게 코드 관리가 가능
    • 공통 코드는 수퍼클래스가 책임지고, 각각의 기능은 서브클래스로 정의하여 각 하나의 역할을 담당하도록 구현하는 것
  • 이처럼 서브 클래스의 장점도 많지만, 단점이 존재
    • 단점1) 수퍼클래스와 서브클래스간의 결합도가 증가하여 부모를 수정하면 자식들의 기능을 헤칠 수 있음
    • 단점2) 사람 객체의 동작이 나이대와 소득 수준으로 나뉜다면, 서브클래스를 부자와 서민으로 나눌 것인데 이렇게 된다면 각 서브클래스에서 하나의 인스턴스로 두 기능 모두 사용이 불가능한 상태
  • 서브 클래스를 델리게이트로 만들면 해결
    • Delegate는 객체 사이의 일반적인 관계이므로 상호작용이 필요한 인터페이스를 명확히 표현 가능

서브 클래스를 델리게이트로 바꾸기

상속을 델리게이트로 리펙터링 필요성

  • 상속의 문제점
    • 수퍼클래스에서 서브클래스의 기능을 사용하고 싶은 경우 사용이 불가

ex) 예약할 수 있는 시스템 기능을 제공하는 Booking 클래스와, 프리미움을 대상으로하는 PremiumBooking이 존재

class Booking {
    let showName: String
    let date: Date
    
    init(showName: String, date: Date) {
        self.showName = showName
        self.date = date
    }
    
    func hasTalkback() -> Bool {
        // 성수기가 아닐때만 관객과 대화하는 시간을 제공
        Bool.random()
    }
}

class PremiumBooking: Booking {
    private let extras: Double
    
    init(showName: String, date: Date, extras: Double) {
        self.extras = extras
        super.init(showName: showName, date: date)
    }
    
    override func hasTalkback() -> Bool {
        // 프리미엄 예약은 항상 관객과 대화하는 시간을 제공
        true
    }
}
  • Booking 클래스로 인스턴스를 만든 상태에서, 프리미움으로 업그레이드 하여 프리미움에 있는 기능을 사용하고 싶을때 아래처럼 사용이 불가능
booking.bePremium()
  • 델리게이트를 활용하면 해결이 가능
    • 주의) swift에서 사용하는 Delegate 패턴과는 조금 다른 형태이며, Delegate는 일종의 task라고 이해할 것

델리게이트로 변경

  • 서브 클래싱을 제거하고 델리게이트로 접근하는게 목적
  • PremiumBookingDelegate라는 구조체를 만들고 이곳에서는 extras라는 PremiumBooking에만 있던 필드와 역참조를 위해 Booking 타입 선언
    • 역참조: 수퍼클래스의 필드에 접근할 때 사용 (상속을 사용할 땐 이게 없어도 되지만 델리게이트 방식은 필요)
    • hasTalkback() 메소드는 원래 PremiumBooking에 있던 메소드로 구현
struct PremiumBookingDelegate {
    let extras: Double
    let host: Booking // 역참조
    
    init(extras: Double, hostBooking: Booking) {
        self.extras = extras
        self.host = hostBooking
    }
    
    func hasTalkback() -> Bool {
        true
    }
}
  • Booking에는 위에서 선언한 델리게이트 인스턴스를 가질 수 있도록 구현
    • bePremium(extras:)를 호출하면 premiumDelegate 인스턴스가 만들어지게끔 구현
class Booking {
    var premiumDelegate: PremiumBookingDelegate?
    
    ...
    
    func bePremium(extras: Double) {
        self.premiumDelegate = .init(extras: extras, hostBooking: self)
    }
}
  • 서브 클래스에서 hasTalkBack()이 호출되면 항상 true를 리턴해줬는데, 이 임무를 delegate에서 수행하도록 구현
// Booking.swift

func hasTalkback() -> Bool {
    if let premiumDelegate {
        return premiumDelegate.hasTalkback()
    } else {
        // 성수기가 아닐때만 관객과 대화하는 시간을 제공
        return Bool.random()
    }
}

(완료) 

// 서브 클래스가 있던 Booking을 Delegate로 표현

class Booking {
    let showName: String
    let date: Date
    var premiumDelegate: PremiumBookingDelegate?
    
    init(showName: String, date: Date) {
        self.showName = showName
        self.date = date
    }
    
    func hasTalkback() -> Bool {
        if let premiumDelegate {
            return premiumDelegate.hasTalkback()
        } else {
            // 성수기가 아닐때만 관객과 대화하는 시간을 제공
            return Bool.random()
        }
    }
    
    func bePremium(extras: Double) {
        self.premiumDelegate = .init(extras: extras, hostBooking: self)
    }
}

(사용하는쪽)

let booking = Booking(showName: showName, date: date)
booking.bePremium(extras: extraValue)

 

* 전체 코드: https://github.com/JK0369/ExRefactor_11_3

* 참고

- Refactoring (Martin Flowler)

Comments