[Kotlin] 디자인패턴 (7) - 데커레이터 패턴

Kotlin 데커레이터 패턴

Posted by 동식이 블로그 on June 7, 2020

Kotlin 객체지향 디자인 패턴

Java 객체지향 디자인 패턴 책을 보고 Kotlin으로 변환하면서 공부한 내용입니다

Java객체지향 디자인패턴

10장. 데커레이터 패턴

  1. 데커레이터 패턴이란

  • 기본 기능에 추가할 수 있는 기능의 종류가 많은 경우에 각 추가 기능을 Decorator 클래스로 정의한 후 필요한 Decorator 객체를 조합합으로써 추가 기능의 조합을 설계하는 방식이다

10-1

  • 데커레이터 패턴에서 나타나는 역할이 수행하는 작업이다
    • Component
      • 기본 기능을 뜻하는 ConcreteComponent와 추가기능을 뜻하는 Decorator의 공통 기능을 정의한다
      • 즉 클라이언트는 Component를 통해 실제 객체를 사용한다
    • ConcreteComponent
      • 기본 기능을 구현하는 클래스
    • Decorator
      • 많은 수가 존재하는 구체적인 Decorator의 공통 기능을 제공
    • ConcreteDecoratorA, ConcreteDecoratorB
      • Decorator의 하위 클래스로 기본 기능에 추가되는 개별적인 기능을 뜻한다
  1. 도로 표시 방법 예시

  • 기본 도로 표시 기능을 제공하는 RoadDisplay 클래스와 추가적으로 차선을 표시하는 RoadDisplayWithLane 클래스
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
open class RoadDisplay {
    open fun draw() {
        println("기본 도로 표시")
    }
}

class RoadDisplayWithLane : RoadDisplay() {
    override fun draw() {
        super.draw()
        drawLane()
    }
    
    private fun drawLane() {
        println("차선 표시")
    }
}

fun main() {
    val road: RoadDisplay = RoadDisplay()
    road.draw()
    
    val roadWithLane: RoadDisplay = RoadDisplayWithLane()
    roadWithLane.draw()
}

main()
  1. 문제점

  • 또 다른 도로 표시 기능을 추가로 구현하고 싶다면?
    • 기본 도로 표시에 교통량을 표시하고 싶다면?
  • 여러가지 추가 기능을 조합헤 제공하고 싶다면?
    • 기본 도로 표시에 차선표시 기능과 교통량 표시 기능을 함께 제공하고 싶다면?
  1. 해결책

  • 상속을 이용한 해결
1
2
3
4
5
6
7
8
9
10
11
// 교통량 표시 클래스
class RoadDisplayWithTraffic : RoadDisplay() {
    override fun draw() {
        super.draw()
        drawTraffic()
    }
    
    fun drawTraffic() {
        println("교통량 표시")
    }
}
  • 상속을 이용하면 추가되는 기능의 조합별로 하위클래스를 구현해야하는 문제가 발생한다
    • 조합 수가 늘어나는 문제가 발생
  • 이를 위해 추가 기능별로 개별적인 클래스를 설계하고 기능을 조합할 때 각 클래스의 객체 조합을 이용하면 된다
    • 데코레이터 패턴
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
abstract class Display {
    abstract fun draw()
}

// 기본 도로 표시 클래스
class RoadDisplay : Display() {
    override fun draw() {
        println("기본 도로 표시")
    }
}

// 다양한 추가 기능에 대한 공통 클래스
abstract class DisplayDecorator(private val decoratedDisplay: Display) : Display() {
    override fun draw() {
        decoratedDisplay.draw()
    }
}

// 차선 표시를 추가하는 클래스
class LaneDecorator(decoratedDisplay: Display) : DisplayDecorator(decoratedDisplay) {
    override fun draw() {
        super.draw()
        drawLane()
    }
    
    private fun drawLane() {
        println("\t차선 표시")
    }
}

// 교통량 표시를 추가하는 클래스
class TrafficDecorator(decoratedDisplay: Display) : DisplayDecorator(decoratedDisplay) {
    override fun draw() {
        super.draw()
        drawTraffic()
    }
    
    private fun drawTraffic() {
        println("\t 교통량 표시")
    }
}

fun client() {
    // 기본 도로 표시
    val road = RoadDisplay()
    road.draw()
    // 기본 도로 표시 + 차선 표시
    val roadWithLane = LaneDecorator(RoadDisplay())
    roadWithLane.draw()
    // 기본 도로 표시 + 교통량 표시
    val roadWithTraffic = TrafficDecorator(RoadDisplay())
    roadWithTraffic.draw()
}

client()
  • road, roadWithLane, roadWithTraffic 객체의 접근이 모두 Display 클래스를 통해 이루어진다
  • Client에서는 동일한 Display 클래스만을 통해 일관성있는 방식으로 도로 정보를 표시할 수 있다

  • 이러한 방식의 설계를 이요하면 추가 기능 조합별로 별도의 클래스를 구현하는 대신 각 추가 기능에 해당하는 클래스의 객체를 조합해 추가 기능의 조합을 구현할 수가 있다
1
2
3
4
fun client() {
    val roadWithLaneAndTraffic = TrafficDecorator(LaneDecorator(RoadDisplay()))
    roadWithLaneAndTraffic.draw()
}
  • 가장 먼저 생성된 RoadDisplay 객체의 draw가 먼저 실행되고, 첫 번째 추가 기능인 LaneDecorator의 drawLane 메서드가 실행되고, 두 번째 추가 기능인 TrafficDecorator의 drawTraffic 메서드가 실행된다

  • 실행 결과

    1
    2
    3
    
    기본 도로 표시
    		차선 표시
    		교통량 표시