개발을 하다 보면 어떠한 조건을 체크하는 경우가 있는데, 조건이 너무 많으면, if 문이 길어질 수 있습니다.
if문은 처리하는 속도는 빠르지만 길어질 경우 가독성이 떨어지고, 유지보수가 어려운 점이 있습니다.
아래 예시는 새로운 타입이 추가될 때마다 else if문으로 추가해야 합니다.
func checkMediaType(type: String) -> String {
var result = ""
if type == "Picture" {
result = "This media type is Picture"
} else if type == "Video" {
result = "This media type is Video"
} else if type == "Text" {
result = "This media type is Text"
} else if type == "Audio" {
result = "This media type is Audio"
} else if type == "Link" {
result = "This media type is Link"
} else if type == "Streaming" {
result = "This media type is Streaming"
} else {
result = "Can't check Type!"
}
return result
}
좀 더 가독성을 높혀보겠습니다. 자주 쓰는 도구인 Enum을 통해서 코드를 고쳐 보겠습니다.
enum MediaType: String {
case text = "Text"
case picture = "Picture"
case video = "Video"
case audio = "Audio"
case link = "Link"
case streaming = "Streaming"
case notChecked = ""
}
func checkMediaType(type: MediaType) -> String {
var result = ""
switch type {
case .text, .video, .picture, .streaming, .link, .audio:
result = "This media type is \(type.rawValue)"
default:
result = "Can't check Type!"
}
return result
}
enum으로 파라미터를 받아서 잘못된 값을 컨파일러 타임에 알아낼 수 있으니, 안정성도 높아졌고, case 가 늘어나도 result의 형식이 같으니 코드가 길어질 일도 없고 case에 추가만 하면 되니 유지보수도 좋아졌다고 말할 수 있을 것 같습니다.
만약 여기서 result의 형식이 "This media type is \(type.rawValue)" 이 아니라면 따로 케이스 분기를 처리해야합니다. 한 두 개 정도의 따른 처리는 아직 괜찮습니다.
func checkMediaType(type: MediaType) -> String {
var result = ""
switch type {
case .text, .picture, .link, .audio:
result = "This media type is \(type.rawValue)"
case .streaming, .video:
result = "This media type is Combined \(type.rawValue)"
default:
result = "Can't check Type!"
}
return result
}
여기서 모든 타입이 다른 처리를 한다면 어떨까요?
모두 다른 행동을 처리한다면 Switch문도 장점도 사라지게 됩니다.
func executeMediaType(type: String) -> String {
var result = ""
if type == "Picture" {
result = "Show Picture Upload"
} else if type == "Video" {
result = "Play Video"
} else if type == "Text" {
result = "Print Text"
} else if type == "Audio" {
result = "Mute Audio"
} else if type == "Link" {
result = "Tap Link"
} else if type == "Streaming" {
result = "Chatting Streaming "
} else {
result = "Can't check Type!"
}
return result
}
여기서 저는 if 문을 하나의 단위로 보겠습니다.
“Picture면 처리, 아니면 다른 조건문, 마지막 아무것도 없을 때는 없는 것으로 처리”
protocol MediaTpyeCondition {
var nextCondition: MediaTpyeCondition?
func executeCondition(by type: MediaType) -> String?
@discardableResult
func chain(nextCondition: MediaTpyeCondition) -> MediaTypeCondition
}
1. 자신의 조건이 아니라면 다음 조건을 호출하기위한 저장프로퍼티입니다.
2. 자신의 조건코드를 실행시킬 메서드입니다.
3. 자신의 다음 조건을 연결 시킬 수 있는 메서드입니다.(1번의 프로퍼티에 넣어서 연결해 주게 됩니다.)
다음은 MediaTpyeCondition프로토콜을 따르는 구체적인 객체를 정의하겠습니다.
final class PictureCondition: MediaTypeCondition {
var nextCondition: MediaTypeCondition?
func executeCondition(by type: MediaType) -> String? {
if case .picture = tpye {
return "Show Picture Upload"
}
//조건에 맞지 않다면 chainning된 다른 MediaTypeCondition으로 전달
return self.nextCondition?.executeCondition(by: type)
}
@discardableResult
func chain(nextCondition: MediaTypeCondition) -> MediaTypeCondition {
self.nextCondition = nextCondition
return nextCondition
}
}
Picture에 대한 처리문이 하나의 타입으로 정의되었습니다.
나머지도 같은 방식으로 정의하면 아래처럼 체이닝으로 조건을 추가하게 됩니다.
let startCondition = VideoTypeCondition()
startCondition
.chain(condition: TextCondition())
.chain(condition: LinkCondition())
.chain(condition: PictureCondition())
.chain(condition: AudioCondition())
.chain(condition: StreamingCondition())
.chain(condition: NotCheckedCondition())
이제 startCondition를 실행 시킬 역할도 분리하여 만들어 주도록 하겠습니다.
final class ConditionExecutor {
var startCondition: MediaTypeCondition
init(startCondition: MediaTypeCondition) {
self.startCondition = startCondition
}
fuc execute(mediaType: MediaType) -> String? {
return startCondition.exectuteCondition(by: mediaType)
}
}
시작 조건을 받고 해당 시작조건을 실행 시키는 객체를 만들었습니다.
let conditionExecutor = ConditionExecutor(startCondition: startCondition)
print(conditionExecutor.excute(mediaType: .picture))
print(conditionExecutor.excute(mediaType: .text))
해당 예제는 String을 반환하지만, 반환이 필요하지 않은 곳에서는 좀 더 간편하게 사용할 수 있습니다.
마무리
장점
- 객체단위로 조건을 처리할 수 있다.
- chain의 순서의 따라 조건의 순서를 정할 수 있다.(Director같은 패턴을 사용하면 편리!)
- 밖에서 보여지는 코드가 간결하다.
단점
- 구현해야하는 코드의 양이 많아진다.
- 적은 조건이 있는 곳에서는 자칫 배보다 배꼽이 더 커질 수 있다.
개인적으로 조건문이 많이 있는 곳에서는 사용하는 것보다
조건문과 조건문에서 처리하는 로직이 많을 때 분리하는 용도로 사용할 때 좋았습니다.