안녕하세요 코찐 입니다.
오늘은 템플릿 메소드 패턴에 대해 공부해보겠습니다.
개념
상위 클래스에서 처리의 뼈대를 결정하고, 하위 클래스에서 그 구체적인 내용을 결정하는 디자인 패턴을 템플릿 메소드 패턴이라고 부릅니다.
- Java 언어로 배우는 디자인 패턴 입문 p.71
반복되는 절차로 수행하는 작업을 추상화해둡니다. 각 절차에 들어갈 세부 내용은 구현체에서 결정하는 방식입니다.
iOS 안에서의 예시
iOS 앱 개발자들은 이미 익숙한 패턴일텐데요. 저희가 흔히 사용하는 UIViewController에도 템플릿 메소드가 있습니다.
약속된 LifeCycle에 따라서 각 함수가 호출됩니다.
틀을 미리 제공하기 때문에 클라이언트는 실행되기 원하는 코드를 틀에 넣어주면 됩니다.
틀을 가지고 동작하는 내부 원리는 몰라도 되기 때문에 관심사 분리를 할 수 있고 안정적인 코드를 작성할 수 있습니다.
과제 구현
이번에는 책에 있는 '문자나 문자열을 5회 반복해서 표시하기' 예제를 Swift로 구현해보겠습니다.
아래와 같은 결과물을 출력하는 프로그램을 만드는게 목표입니다.
작업한 코드는 아래 레포에서 볼 수 있습니다.
github.com/cozzin/DesignPattern/tree/main/DesignPattern/TemplateMethod
일단 테스트를 만들어두고 시작하겠습니다.
import XCTest
@testable import DesignPattern
final class TemplateMethodTests: XCTestCase {
func testTemplateMethodPattern() throws {
let display1 = CharDisplay("H")
let display2 = StringDisplay("Hello, world.")
let display3 = StringDisplay("안녕하세요.")
XCTAssertEqual(
display1.display(),
"<<HHHHH>>"
)
XCTAssertEqual(
display2.display(),
"""
+-------------+
|Hello, world.|
|Hello, world.|
|Hello, world.|
|Hello, world.|
|Hello, world.|
+-------------+
"""
)
XCTAssertEqual(
display3.display(),
"""
+------+
|안녕하세요.|
|안녕하세요.|
|안녕하세요.|
|안녕하세요.|
|안녕하세요.|
+------+
"""
)
}
}
결과물에는 반복되는 패턴이 있는데요.
시작 / 5 x 중간 / 끝 이렇게 세가지로 나눠볼 수 있습니다.
반복되는 패턴을 출력하려고 할때 템플릿 메소드 패턴을 사용할 수 있습니다!
자바에는 Abstract class가 있어서 미구현된 함수를 명시적으로 표현하는 키워드가 있습니다.
스위프트에는 abstract 키워드가 없어서 구현이 조금 애매한 부분이 있습니다.
class로 구현하는 방법
class를 사용하면 display() 부분을 final 키워드를 설정해서
하위 클래스에서 상속을 받아서 변경하지 못하도록 막을 수 있습니다.
현재 display 함수의 구현부는 변경되지 않아야 하기 때문에 이 방식으로 구현했습니다.
import Foundation
class AbstractDisplay {
func open() -> String {
return ""
}
func print() -> String {
return ""
}
func close() -> String {
return ""
}
final func display() -> String {
var result = ""
result += open()
(1...5).forEach { _ in
result += print()
}
result += close()
return result
}
}
protocol로 구현하는 방법
protocol로 구현하면 구현체에서 준수해야할 함수가 명확하다는 장점이 있습니다.
다만 display() 와 같은 고정된 함수를 변경하지 못하도록 막을 수 없습니다.
예를 들어 AbstractDisplay를 준수한 클래스에서 display() 함수를 다시 정의할 수 있는 단점이 있습니다.
protocol AbstractDisplay {
func open() -> String
func print() -> String
func close() -> String
}
extension AbstractDisplay {
func display() -> String {
var result = ""
result += open()
(1...5).forEach { _ in
result += print()
}
result += close()
return result
}
}
과제의 요구사항인 CharDisplay와 StringDisplay는 아래와 같이 구현할 수 있습니다.
import Foundation
final class CharDisplay: AbstractDisplay {
private let character: Character
init(_ character: Character) {
self.character = character
}
override func open() -> String {
return "<<"
}
override func print() -> String {
return "\(character)"
}
override func close() -> String {
return ">>"
}
}
책에서는 getByets().length로 문자 길이를 계산했는데, 눈에 보이는 문자 길이와 다를 수 있어서 스위프트의 count를 써서 구현했습니다.
import Foundation
final class StringDisplay: AbstractDisplay {
private let string: String
private let width: Int
init(_ string: String) {
self.string = string
width = string.count
}
override func open() -> String {
return printLine() + "\n"
}
override func print() -> String {
return "|" + string + "|\n"
}
override func close() -> String {
return printLine()
}
private func printLine() -> String {
var result = "+"
(1...width).forEach { _ in
result += "-"
}
result += "+"
return result
}
}
오늘의 교훈
작업하면서 처음에 StringDisplay의 printLine()을 잘못구현해서 테스트가 실패되었었는데요.
open / print / close 함수가 구분되어 있었기 때문에 버그가 발생하는 지점을 빠르게 확인할 수 있었습니다.
반복되는 로직을 템플릿 메소드로 구현해두면 디버깅 시에도 편하다는 것을 알게되었습니다.
'스터디' 카테고리의 다른 글
[디자인패턴] 전광판을 예시로 풀어본 Bridge 패턴 (0) | 2021.05.24 |
---|---|
[엘레강트 오브젝트] '생성자에 코드를 넣지 마세요' 적용해보기 (0) | 2021.05.19 |
[디자인패턴] Builder 패턴 (1) | 2021.05.18 |
[디자인패턴] Iterator 패턴을 Swift로 구현해보기 (0) | 2021.05.03 |