Notice
Recent Posts
Recent Comments
Link
관리 메뉴

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

[Refactoring] 11-2 상속 리펙토링 (enum case 타입 코드를 서브클래스로 바꾸기) 본문

Refactoring (리펙토링)

[Refactoring] 11-2 상속 리펙토링 (enum case 타입 코드를 서브클래스로 바꾸기)

jake-kim 2023. 7. 25. 01:47

케이스를 서브클래스로 바꾸기

  • 특정 타입에 따라서 기능을 달리해야하는 경우 보통 enum case로 타입을 구분하고 하나의 파일에서 분기를 넣는데, 이렇게되면 하나의 역할에 대해서만 책임 (Single Responsibility Prinsiple, SRP)을 지키지 못하는 코드로 변동
    • SRP를 지키지 못하면 type에 대한 특정 동작이 하나의 파일에 섞여나게되어, type에 대한 기능이 수정되어야 할 때 특정 type과 관련없는 코드가 변경될 우려가 존재
    • 변경사항에 대해 코드를 수정해야하는 개발자 입장에서는 변경하려는 type 외에도 다른 type들도 고려하게되어 수정하기가 쉽지 않게 유지
  • SRP를 지키도록 type으로 하나의 파일에 여러개의 역할에 대해 구분할 때, 서브 클래스로 만들면 유지보수에 용이

케이스를 서브클래스로 바꾸기 리펙토링

리펙토링 실습

  • 툴팁 뷰 구현에 있어서, tip 부분이 위에 있는 코드와 아래 있는 코드가 있는데 이를 enum으로 받아서 하나의 뷰 안에서 분기문이 들어가 있는 상태

아래에 tip이 있는 툴팁
위에 tip이 있는 툴팁

  • 리펙토링이 필요한 부분
    • TipView (삼각형 뷰) 부분에 enum타입으로 아래에 tip이 위치할지, 위에 tip이 위치할지 결정하는 분기가 존재
    • TipView를 사용하는쪽에서 bottom, top 위치를 필요로 하는데, 이 2가지 기능에 대해 TipView에서 2가지의 책임을 지게 되므로 SRP 위배
private class TipView: UIView {
    var tipPoistion = TipPosition.bottom
    var bgColor = UIColor.gray
    
    override func draw(_ rect: CGRect) {
        backgroundColor = .clear
        bgColor.setFill()
        
        tipPoistion == .top ? drawTopTip(rect) : drawBottomTip(rect)
    }
    
    private func drawTopTip(_ rect: CGRect) {
        let path = UIBezierPath()
        path.move(to: CGPoint(x: rect.midX, y: 0))
        path.addLine(to: CGPoint(x: rect.maxX, y: rect.maxY))
        path.addLine(to: CGPoint(x: rect.minX, y: rect.maxY))
        path.close()
        path.fill()
    }
    
    private func drawBottomTip(_ rect: CGRect) {
        let path = UIBezierPath()
        path.move(to: CGPoint(x: 0, y: 0))
        path.addLine(to: CGPoint(x: rect.maxX, y: 0))
        path.addLine(to: CGPoint(x: rect.midX, y: rect.maxY))
        path.close()
        path.fill()
    }
}
  • 하나의 클래스에서 하나의 책임만을 담당하도록 서브클래싱하여 리펙터링
    • TopTipView와 BottomTipView로 나누어서 구현
private class TipView: UIView {
    var bgColor = UIColor.gray
    
    override func draw(_ rect: CGRect) {
        backgroundColor = .clear
        bgColor.setFill()
        drawTip(rect)
    }
    
    func drawTip(_ rect: CGRect) {
        // override point
    }
}

private class TopTipView: TipView {
    override func drawTip(_ rect: CGRect) {
        let path = UIBezierPath()
        path.move(to: CGPoint(x: rect.midX, y: 0))
        path.addLine(to: CGPoint(x: rect.maxX, y: rect.maxY))
        path.addLine(to: CGPoint(x: rect.minX, y: rect.maxY))
        path.close()
        path.fill()
    }
}

private class BottomTipView: TipView {
    override func drawTip(_ rect: CGRect) {
        let path = UIBezierPath()
        path.move(to: CGPoint(x: 0, y: 0))
        path.addLine(to: CGPoint(x: rect.maxX, y: 0))
        path.addLine(to: CGPoint(x: rect.midX, y: rect.maxY))
        path.close()
        path.fill()
    }
}
  • 사용하는 쪽
    • TopTipView(), BottomTipView() 나누어서 사용
init(title: String?, textColor: UIColor = .white, backgroundColor: UIColor = .gray, tipPoistion: TipPosition = .bottom) {
	...
    tipView = tipPoistion == .top ? TopTipView() : BottomTipView()
    super.init(frame: .zero)
    ...
}

(리펙토링 전 코드 Github)

 

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

* 참고

- Refactoring (Martin Flowler)

Comments