Decorator

DongHeon·2022년 11월 22일
0

디자인 패턴

목록 보기
8/12

오늘은 Decorator 패턴에 대해 알아보겠습니다.

UML을 보면 이전에 공부했던 Composite랑 비슷하다고 느낄 수 있는데 이 부분에 대해서도 한번 알아보겠습니다.

Decorator?

Decorator 패턴을 사용하면 런타임에 원하는 기능을 추가할 수 있습니다.

런타임과 컴파일 타임에 따른 성능 차이는 존재합니다. 제가 알기로는 컴파일 타임에 무언가를 결정하는 게 성능적으로는 좋지만 유연성이 떨어지는 걸로 알고 있습니다.(사용 하는 타입과 기능이 정해져 있기 때문에 코드가 실행되는 동안 정해진 위치를 바로 찾아가 해당 기능을 실행할 수 있다.)


UML을 보면 Composite와 비슷합니다. 차이점은 Composite는 여러 개의 Component를 가지고 있지만 Decorator 패턴 같은 경우 하나의 Component만 가지고 있습니다.

Composite 패턴은 전체나 부분을 관리하기 때문에 여러 개의 Component를 가지고 있지만 DecoratorConcrete Decorator를 생성해 기능을 확장하고 있습니다.

코드

패턴 적용 전

문자열을 입력받는 서비스와 코드를 사용하는 Client가 있습니다.

class InputService {
    func addInput(comment: String) {
        print(comment)
    }
}

class Client {
    private let inputService: InputService

    init(inputService: InputService) {
        self.inputService = inputService
    }

    func writeInput(input: String) {
        inputService.addInput(comment: input)
    }
}

입력받는 문자열을 대문자로 바꾸고 싶어 Uppercase Service를 상속을 통해 만들어 보겠습니다.

class UppercaseService: InputService {
    override func addInput(comment: String) {
        print(comment.uppercased())
    }
}

추가로 url 주소가 들어간 입력을 필터링하는 Service를 만들어 보겠습니다.

class FilterService: InputService {
    override func addInput(comment: String) {
        if comment.contains("http") {
            print("스팸 차단")
        } else {
            print(comment)
        }
    }
}

두 가지 기능을 동시에 사용하기 위해서는 새로운 타입을 만들어 사용해야 합니다.

class FilterUppercaseService: InputService {
    override func addInput(comment: String) {
        if comment.contains("http") {
            print("스팸 차단")
        } else {
            print(comment.uppercased())
        }
    }
}

즉 기존에 만든 기능을 재사용하지 못하고 새로운 타입을 만들어 기능을 사용해야 합니다.(상속을 사용할 때 문제점)

패턴 적용

  • Component
protocol Service {
    func addInput(comment: String)
}
  • Leaf
class InputService: Service {
    func addInput(comment: String) {
        print(comment)
    }
}
  • Decorator
class InputDecorator: Service {
    let service: Service

    init(service: Service) {
        self.service = service
    }

    func addInput(comment: String) {
        service.addInput(comment: comment)
    }
}

DecoratorService를 사용해 최종적으로 기능을 수행한다.

  • Concrete Decorator
class UppercaseDecorator: InputDecorator {
    override init(service: Service) {
        super.init(service: service)
    }

    private func transformUppercase(comment: String) -> String {
        return comment.uppercased()
    }

    override func addInput(comment: String) {
        super.addInput(comment: transformUppercase(comment: comment))
    }
}

class FilterDecorator: InputDecorator {
    override init(service: Service) {
        super.init(service: service)
    }

    private func filter(comment: String) -> Bool {
        if comment.contains("http") {
            return true
        }

        return false
    }

    override func addInput(comment: String) {
        if filter(comment: comment) {
            super.addInput(comment: "스팸 차단")
        } else {
            super.addInput(comment: comment)
        }
    }
}
  • Client
class Client {
    private let service: Service

    init(service: Service) {
        self.service = service
    }

    func writeInput(input: String) {
        service.addInput(comment: input)
    }
}

class App {
    private var enableFilter = true
    private var enableUppercase = false

    func run() {
        var inputService: Service = InputService()

        if enableFilter {
            inputService = FilterDecorator(service: inputService)
        }

        if enableUppercase {
            inputService = UppercaseDecorator(service: inputService)
        }

        let client = Client(service: inputService)
        client.writeInput(input: "http//dsa/dsadasd")
        client.writeInput(input: "hello")
        client.writeInput(input: "안녕하세요")
    }
}

let app = App()
app.run()

패턴 적용 전처럼 별도의 타입을 만들지 않고 두 가지 기능을 모두 사용할 수 있다.

App 코드의 run 메서드에서 분기 처리를 통해 inputService를 재할당 하고 있다. enableFilter를 확인하는 조건문과 enableUppercase를 확인하는 조건문 순서에 따라 실행 결과가 달라진다.
(아마 마지막에 할당된 inputService의 기능부터 실행되기 때문에 순서에 신경 쓸 필요가 있어 보인다.)

  • 기존 순서

  • 순서 변경

장단점

  • 장점
  1. 새로운 타입을 만들지 않고 기존 기능을 조합할 수 있다.(여러가지 기능을 사용할 수 있다.)
  2. 컴파일 타임이 아닌 런타임에 동적으로 기능을 변경할 수 있다.
  • 단점
  1. 데코레이터를 조합하는 코드가 복잡하다.
    (위에서 패턴을 적용한 코드를 보면 Decorator를 만들고 Concrete Decorator를 조합해 사용하는 부분이 복잡하다고 느낄 수 있다.)

해당 글은 인프런의 코딩으로 학습하는 GoF 디자인 패턴 강의를 참고해 작성했습니다.

⭐️ 부족하거나 잘못된 부분이 있다면 댓글은 언제나 환영입니다!! ⭐️

0개의 댓글