상세 컨텐츠

본문 제목

enum을 configuration으로 사용할 때 발생하는 안티 패턴 제거해보기

iOS

by 쑤야. 2023. 12. 14. 00:06

본문

프로젝트를 진행하면서, configuration 데이터들을 각각 데이터가 필요한 View 파일에서 fileprivate 또는 private 접근제어로 선언해 줬었다. 하지만 프로젝트를 진행할수록, 데이터의 중복 선언이 많아졌다. 이로 인해 한 곳에서 관리할 필요성을 느꼈으며, enum의 안티 패턴에 대해서도 많은 공감을 했다. 이를 빠르게 개선할 필요성을 느껴 리팩토링을 진행해 보았다. 

 

configuration으로 사용하는  enum


프로젝트를 진행하다보면, enum 타입을 통해 케이스를 나열하게 된다. 

만약 enum이 configuration 값을 가져야 한다면, 아래와 같이 extension에 연산 프로퍼티를 추가하고 switch 문을 통해 각 케이스 별 데이터가 반환되도록 코드를 작성할 것이다. 

public enum Choice {
    public enum Option {
        case A
        case B
    }
}
extension Choice.Option {
    
    var title: String {
        switch self {
        case .A:        return "A"
        case .B:        return "B"
        }
    }
    
    var backgroundColor: UIColor {
        switch self {
        case .A:        return Color.mainA
        case .B:        return Color.mainB
        }
    }
    
    var cornerRadiusPosition: CACornerMask {
        switch self {
        case .A:        return [.layerMaxXMaxYCorner, .layerMaxXMinYCorner]
        case .B:        return [.layerMinXMaxYCorner, .layerMinXMinYCorner]
        }
    }

}

 

하지만 이와 같은 코드는 안티 패턴이라고 불린다. 

 

configuration으로 사용하는 enum은 왜 안티 패턴인가?


이유는 바로 개방 폐쇄 원칙에 위반되기 때문이다.

 

현재 Option에 A와 B 케이스가 선언되어 있다. 만약 여기에 C 케이스가 추가된다면 어떤 일이 발생하는가?

위에 예시로 선언한 3가지 연산 프로퍼티 내의 switch 문에 각각 데이터들을 추가해줘야 한다. 

즉, 케이스 하나를 추가하는데 3번을 수정해야 하는 것이다.

만약 configuration 데이터 값이 더 많다면? 데이터 개수만큼 추가 작업이 들어간다. 

 

실제 프로젝트를 진행할 때 케이스를 추가해줘야 할 때마다 굉장히 귀찮고 번거로움을 느꼈었다. 

 

* 개방 폐쇄 원칙이란, 확장에는 열려있으나 변경에는 닫혀있어야 함을 뜻하는 원칙으로 SOLID 원칙 중 하나이다.

기능 또는 케이스를 추가할 때 기존 코드를 변경되어서는 안 됨을 의미한다. 

 

개선 방법에 대하여


configuration 데이터들을 저장하기 위해서 protocol을 활용할 것이다. 

 

protocol에 어떤 데이터와 책임을 가져야 하는지 선언을 해준다. 

public protocol ChoiceOptionContent {
    var title: String { get }
    var color: UIColor { get }
    var corenrMask: CACornerMask { get }
    var gradientLayer: CAGradientLayer { get}
}

 

Choice Option의 각 케이스에 대한 데이터 구조체를 만들어준다. 

public struct AChoiceOptionContent: ChoiceOptionContent {
    public let title: String = "A"
    public let color: UIColor = Color.mainA
    public let corenrMask: CACornerMask = [.layerMaxXMaxYCorner, .layerMaxXMinYCorner]
    public let gradientLayer: CAGradientLayer = .generate(startX: 1, endX: 0, color: Color.mainA)
}

public struct BChoiceOptionContent: ChoiceOptionContent {
    public let title: String = "B"
    public let color: UIColor = Color.mainB
    public let corenrMask: CACornerMask = [.layerMinXMaxYCorner, .layerMinXMinYCorner]
    public let gradientLayer: CAGradientLayer = .generate(startX: 0, endX: 1, color: Color.mainB)
}

 

configuration 데이터들을 담은 Cotent 구조체는 extension의 연산 프로퍼티로 설정해 주었다. 

extension Choice.Option {
    public var content: ChoiceOptionContent {
        switch self {
        case .A:        return AChoiceOptionContent()
        case .B:        return BChoiceOptionContent()
        }
    }
}

 

만약 C라는 케이스가 추가된다면, content 연산 프로퍼티에서 C 케이스가 CChoiceOptionContent()를 반환하도록 추가해 주면 된다. 

관련글 더보기