Template Method Pattern in Swift

design-pattern
Written on Aug 12, 2023


Template Method Pattern은 특정 알고리즘을 반복적으로 사용하고 있을 때 중복을 제거하기 위하여 적용해볼 수 있습니다. 공통 알고리즘을 묶을 템플릿 메서드를 포함할 추상 클래스를 정의하고, 하위 클래스에서 구체적인 내용을 작성하는 패턴입니다. 해당 패턴을 통해 코드 중복을 제거하고 공통된 알고리즘을 재사용할 수 있습니다. 템플릿 메서드의 변경이 필요하다면 하위 클래스에서 재정의를 통해 인터페이스의 변경 없이 세부 구현을 변경할 수도 있습니다.

Template Method Pattern은 GoF의 분류 체계에서 행위(Behavioral) 패턴에 속합니다.

Template Method Pattern을 구현하기 위해 필요한 역할은 다음과 같습니다.

  • AbstractClass(추상 클래스): 템플릿 메서드를 정의하고 하위 클래스에서 구현할 메서드를 정의합니다.
  • ConcreteClass(구현 클래스): 추상 클래스에서 정의한 추상 메서드를 구현합니다.

Java 언어로 배우는 디자인 패턴 입문의 예제를 Swift 코드로 작성해봅시다. AbstractClass 역할을 수행하는 AbstractDisplay 클래스를 정의합니다. AbstractDisplay 클래스는 템플릿 메서드인 display 메서드를 정의하고 있습니다.

Language:swift
protocol AbstractDisplay {
    func open()
    func write()
    func close()
}

extension AbstractDisplay {
    func display() {
        open()
        for _ in 0..<5 {
            write()
        }
        close()
    }
}

AbstractClass는 추상 클래스이므로 구현되어 있는 메서드가 존재하고 인스턴스를 생성할 수 없어야 합니다. protocolextension 키워드를 활용하여 추상 클래스를 정의하였습니다. display 템플릿 메서드를 사용하고 싶다면, AbstractClass를 채택하고 open, write, close 메서드를 구현해야 합니다.

Language:swift
final class CharDisplay: AbstractDisplay {
    private let char: Character

    init(char: Character) {
        self.char = char
    }

    func open() {
        print("<<", terminator: "")
    }

    func write() {
        print(char, terminator: "")
    }

    func close() {
        print(">>")
    }
}
Language:swift
let display: AbstractDisplay = CharDisplay(char: "H")
display.display()

// output:
// <<HHHHH>>

CharDisplay 클래스는 AbstractDisplay를 채택하고 있습니다. CharDisplay 클래스는 open, write, close 메서드를 구현하고 있습니다. CharDisplay 클래스는 display 메서드를 구현하지 않았지만, AbstractDisplaydisplay 메서드를 사용할 수 있습니다.

또 다른 클래스를 정의해봅니다.

Language:swift
final class StringDisplay: AbstractDisplay {
    private let string: String
    private let width: Int

    init(string: String) {
        self.string = string
        self.width = string.count
    }

    func open() {
        printLine()
    }

    func write() {
        print("|\(string)|")
    }

    func close() {
        printLine()
    }

    private func printLine() {
        print("+\(String(repeating: "-", count: width))+")
    }
}
Language:swift
let display: AbstractDisplay = StringDisplay(string: "Hello, World")
display.display()

// output:
// +-------------+
// |Hello, World|
// |Hello, World|
// |Hello, World|
// |Hello, World|
// |Hello, World|
// +-------------+

이렇듯 하나의 공통적인 로직을 템플릿화하여 코드의 중복을 없앨 수 있고, 추상 클래스를 상속(채택)하는 방식으로 인하여 상위 클래스 형식의 변수에 하위 클래스 인스턴스 중 어느 것을 대입해도 제대로 동작해야 한다는 원칙인 리스코프 치환 원칙(LSP, Liskov Substitution Principle)을 만족합니다.

Swift에서는 extension 키워드의 존재로 인터페이스를 변경하지 않고도 쉽게 새로운 공통 로직을 언제든 추가할 수 있습니다.