GoF 디자인 패턴을 스위프트로 구현해가며 정리하는 저장소입니다.
생성 패턴 | 구조 패턴 | 행위 패턴 |
---|---|---|
추상 팩토리(Abstract Factory) | 어댑터(Adapter) | 책임 연쇄(Chain of Responsibility) |
빌더(Builder) | 브릿지(Bridge) | 커맨드(Command) |
팩토리 메서드(Factory Methods) | 컴포지트(Composite) | 인터프리터(Interpreter) |
프로토타입(Prototype) | 퍼사드(Facade) | 미디에이터(Mediator) |
싱글톤(Singleton) | 플라이웨이트(Flyweight) | 메멘토(Memento) |
프록시(Proxy) | 옵저버(Observer) | |
데코레이터(Decorator) | 스테이트(State) | |
전략(Strategy) | ||
템플릿 메서드(Template Method) | ||
반복자(Iterator) | ||
방문(Visitor) |
추상 팩토리 패턴은 한 팩토리에 여러 연관된 객체를 생성하는 팩토리 메서드를 정의하는 방법입니다. 더 자세히 알아보기
import Foundation
protocol View: CustomStringConvertible {
var id: String { get set }
var color: String { get set }
}
extension View {
var description: String {
return "type: view id: \(id) color \(color)"
}
}
protocol Button: CustomStringConvertible {
var id: String { get set }
var color: String { get set }
}
extension Button {
var description: String {
return "type: button id: \(id) color \(color)"
}
}
class YellowView: View {
var id: String
var color: String = "Yellow"
init(id: String) {
self.id = id
}
}
class BlackView: View {
var id: String
var color: String = "Black"
init(id: String) {
self.id = id
}
}
class YellowButton: Button {
var id: String
var color: String = "Yellow"
init(id: String) {
self.id = id
}
}
class BlackButton: Button {
var id: String
var color: String = "Black"
init(id: String) {
self.id = id
}
}
protocol ButtonBoxFactory {
func createView() -> View
func createButton() -> Button
}
class DarkButtonBoxFactory: ButtonBoxFactory {
func createView() -> View {
return BlackView(id: "bv")
}
func createButton() -> Button {
return YellowButton(id: "yb")
}
}
class LightButtonBoxFactory: ButtonBoxFactory {
func createView() -> View {
return YellowView(id: "yv")
}
func createButton() -> Button {
return BlackButton(id: "bb")
}
}
class ButtonBox {
enum ColorTheme {
case dark, light
}
private var colorTheme: ColorTheme
private var buttonBoxFacotry: ButtonBoxFactory
private var button: Button?
private var view: View?
init(colorTheme: ColorTheme) {
self.colorTheme = colorTheme
self.buttonBoxFacotry = colorTheme == .dark ? DarkButtonBoxFactory() : LightButtonBoxFactory()
self.createButtonBox()
}
private func createButtonBox() {
self.button = buttonBoxFacotry.createButton()
self.view = buttonBoxFacotry.createView()
}
func change(colorTheme: ColorTheme) {
self.colorTheme = colorTheme
self.buttonBoxFacotry = colorTheme == .dark ? DarkButtonBoxFactory() : LightButtonBoxFactory()
self.createButtonBox()
}
func printComponents() {
print(self.view!)
print(self.button!)
}
}
var buttonBox = ButtonBox(colorTheme: .dark)
buttonBox.printComponents()
print("\n## Change Factory ##\n")
buttonBox.change(colorTheme: .light)
buttonBox.printComponents()
// type: view id: bv color Black
// type: button id: yb color Yellow
//
// ## Change Factory ##
//
// type: view id: yv color Yellow
// type: button id: bb color Black
// Program ended with exit code: 0
팩토리 메서드 패턴은 객체의 인스턴스를 생성하는 인터페이스를 제공하고, 인스턴스의 생성은 서브클래스에서 정의하도록 하는 방법입니다. 따라서 인스턴스가 생성될 때는 입력에 따른 적절한 팩토리 객체를 선택해 해당 객체를 통해 인스턴스를 생성하게 됩니다. 더 자세히 알아보기
import Foundation
protocol Animal {
var name: String { get set }
func sound()
}
class Dog: Animal {
var name: String
init(name: String) {
self.name = name
}
func sound() {
print("\(name) Bark! 🐶")
}
}
class Cat: Animal {
var name: String
init(name: String) {
self.name = name
}
func sound() {
print("\(name) Meow! 😸")
}
}
protocol AnimalFactory {
func make(with name: String) -> Animal
}
class RandomAnimalFactory: AnimalFactory {
func make(with name: String) -> Animal {
return Int.random(in: 0...1) == 0 ? Dog(name: name) : Cat(name: name)
}
}
class EvenAnimalFactory: AnimalFactory {
var previousState: Animal.Type?
func make(with name: String) -> Animal {
if previousState == Cat.self {
self.previousState = Dog.self
return Dog(name: name)
} else {
self.previousState = Cat.self
return Cat(name: name)
}
}
}
class AnimalCafe {
private var animals = [Animal]()
private var factory: AnimalFactory
init(factory: AnimalFactory) {
self.factory = factory
}
func addAnimal(with name: String) {
self.animals.append(self.factory.make(with: name))
}
func printAnimals() {
self.animals.forEach { animal in
animal.sound()
}
}
func change(factory: AnimalFactory) {
self.factory = factory
}
func clear() {
self.animals = []
}
}
let animalCafe = AnimalCafe(factory: EvenAnimalFactory())
animalCafe.addAnimal(with: "A")
animalCafe.addAnimal(with: "B")
animalCafe.addAnimal(with: "C")
animalCafe.addAnimal(with: "D")
animalCafe.addAnimal(with: "E")
animalCafe.addAnimal(with: "F")
animalCafe.printAnimals()
animalCafe.clear()
print("\n## Change Factory ##\n")
animalCafe.change(factory: RandomAnimalFactory())
animalCafe.addAnimal(with: "A")
animalCafe.addAnimal(with: "B")
animalCafe.addAnimal(with: "C")
animalCafe.addAnimal(with: "D")
animalCafe.addAnimal(with: "E")
animalCafe.addAnimal(with: "F")
animalCafe.printAnimals()
//A Meow! 😸
//B Bark! 🐶
//C Meow! 😸
//D Bark! 🐶
//E Meow! 😸
//F Bark! 🐶
//
//## Change Factory ##
//
//A Meow! 😸
//B Bark! 🐶
//C Bark! 🐶
//D Meow! 😸
//E Meow! 😸
//F Meow! 😸
싱글톤 패턴은 단 하나의 인스턴스만을 생성하고 추가적인 인스턴스를 생성하지 못하도록하며 코드 전역에서 인스턴스를 공유할 수 있도록 하는 디자인 패턴입니다. 더 자세히 알아보기
class Singleton {
static let shared = Singleton()
var state = true
private init() {}
}
class A {
let singleton = Singleton.shared
func printState() {
print(singleton.state)
}
func updateState() {
singleton.state.toggle()
}
}
class B {
let singleton = Singleton.shared
func printState() {
print(singleton.state)
}
func updateState() {
singleton.state.toggle()
}
}
let a = A()
let b = B()
a.printState() // true
b.printState() // true
a.updateState()
a.printState() // false
b.printState() // false
데코레이터 패턴은 데코레이터를 통해 유연하게 새로운 기능이나 책임을 추가할 수 있게 하는 패턴입니다. 더 자세히 알아보기
import Foundation
protocol Sandwich: CustomStringConvertible {
func cost () -> Int
}
protocol Decorating: Sandwich {
var sandwich: Sandwich { get set }
}
class OriginalSandwich: Sandwich {
var description: String {
return "OriginalSandwich"
}
func cost() -> Int {
return 3000
}
}
class VeganSandwich: Sandwich {
var description: String {
return "VeganSandwich"
}
func cost() -> Int {
return 2500
}
}
class Avocado: Decorating {
var sandwich: Sandwich
var description: String {
return self.sandwich.description + " + Avocado"
}
init(sandwich: Sandwich) {
self.sandwich = sandwich
}
func cost() -> Int {
return self.sandwich.cost() + 1000
}
}
class MeatBall: Decorating {
var sandwich: Sandwich
var description: String {
return self.sandwich.description + " + MeatBall"
}
init(sandwich: Sandwich) {
self.sandwich = sandwich
}
func cost() -> Int {
return self.sandwich.cost() + 1500
}
}
class Tomato: Decorating {
var sandwich: Sandwich
var description: String {
return self.sandwich.description + " + Tomato"
}
init(sandwich: Sandwich) {
self.sandwich = sandwich
}
func cost() -> Int {
return self.sandwich.cost() + 500
}
}
let avocadoMeatBallSandwich = MeatBall(sandwich: Avocado(sandwich: OriginalSandwich()))
print(avocadoMeatBallSandwich.cost()) // 5500
print(avocadoMeatBallSandwich) // OriginalSandwich + Avocado + MeatBall
let tomatoAdded = Tomato(sandwich: avocadoMeatBallSandwich)
print(tomatoAdded.cost()) // 6000
print(tomatoAdded) // OriginalSandwich + Avocado + MeatBall + Tomato
커맨드 패턴은 객체로 보내는 요청를 캡슐화하여 요청들을 파라미터화 하고 역으로 동작을 수행할 수 있도록 하는 디자인 패턴입니다. 더 자세히 알아보기
//
// main.swift
// example
//
// Created by USER on 2022/02/20.
//
import Foundation
final class TVRemoteControl {
private let onCommand: Commanding
private let offCommand: Commanding
private let upCommand: Commanding
private let downCommand: Commanding
private var commandHistory: [Commanding] = []
init(
on: Commanding,
off: Commanding,
up: Commanding,
down: Commanding
) {
self.onCommand = on
self.offCommand = off
self.upCommand = up
self.downCommand = down
}
func on() {
self.onCommand.execute()
self.commandHistory.append(self.onCommand)
}
func off() {
self.offCommand.execute()
self.commandHistory.append(self.offCommand)
}
func up() {
self.upCommand.execute()
self.commandHistory.append(self.upCommand)
}
func down() {
self.downCommand.execute()
self.commandHistory.append(self.downCommand)
}
func reverseAll() {
while self.commandHistory.isEmpty == false {
let command = self.commandHistory.removeLast()
command.unexecute()
}
}
}
final class TV {
func on() {
print("on")
}
func off() {
print("off")
}
func up() {
print("up")
}
func down() {
print("down")
}
}
protocol Commanding {
func execute()
func unexecute()
}
final class OnCommand: Commanding {
let tv: TV
init(tv: TV) {
self.tv = tv
}
func execute() {
tv.on()
}
func unexecute() {
tv.off()
}
}
final class OffCommand: Commanding {
let tv: TV
init(tv: TV) {
self.tv = tv
}
func execute() {
tv.off()
}
func unexecute() {
tv.on()
}
}
final class UpCommand: Commanding {
let tv: TV
init(tv: TV) {
self.tv = tv
}
func execute() {
tv.up()
}
func unexecute() {
tv.down()
}
}
final class DownCommand: Commanding {
let tv: TV
init(tv: TV) {
self.tv = tv
}
func execute() {
tv.down()
}
func unexecute() {
tv.up()
}
}
let appleTV = TV()
let appleRemoteControl = TVRemoteControl(
on: OnCommand(tv: appleTV),
off: OffCommand(tv: appleTV),
up: UpCommand(tv: appleTV),
down: DownCommand(tv: appleTV)
)
appleRemoteControl.on()
appleRemoteControl.off()
appleRemoteControl.down()
appleRemoteControl.up()
appleRemoteControl.reverseAll()
//on
//off
//down
//up
//down
//up
//on
//off
//Program ended with exit code: 0
전략 패턴은 비슷한 목적의 알고리즘을 전략이라는 인터페이스로 캡슐화하고, 클라이언트로부터 알고리즘 객체를 분리해 런타임에 알고리즘을 변경할 수 있게 합니다. 또한 클라이언트와 알고리즘 객체의 느슨한 연결을 통해 수정과 변경이 용이한 구조를 만들 수 있게 하는 디자인 패턴입니다. 더 자세히 알아보기
import Foundation
protocol Validatable {
func validate(text: String) -> Bool
}
protocol Validator {
var validationStrategy: Validatable { get set }
func validate(text: String) -> Bool
}
final class StringValidator: Validator {
var validationStrategy: Validatable
init(strategy: Validatable) {
self.validationStrategy = strategy
}
func change(strategy: Validatable) {
self.validationStrategy = strategy
}
func validate(text: String) -> Bool {
return validationStrategy.validate(text: text)
}
}
class NumberValidator: Validatable {
func validate(text: String) -> Bool {
return text.allSatisfy({ $0.isNumber })
}
}
class LengthValidator: Validatable {
func validate(text: String) -> Bool {
return text.count < 10
}
}
class AsciiValidator: Validatable {
func validate(text: String) -> Bool {
return text.allSatisfy({ $0.isASCII })
}
}
let validator = StringValidator(strategy: LengthValidator())
print(validator.validate(text: "12345678910")) // false
validator.change(strategy: NumberValidator())
print(validator.validate(text: "12345678910")) // true
validator.change(strategy: AsciiValidator())
print(validator.validate(text: "12345678910")) // true
func validateAll(text: String) -> Bool {
let strategies: [Validatable] = [LengthValidator(), NumberValidator(), AsciiValidator()]
return strategies.filter({ strategy in
return StringValidator(strategy: strategy).validate(text: text)
}).isEmpty
}
print(validateAll(text: "12345678910")) // false
옵저버 패턴은 어떤 객체에서 발생하는 이벤트를 해당 객체를 관찰하고 있는 객체에게 알리는 방식으로 메서드를 호출하는 패턴입니다 더 자세히 알아보기
protocol Observable {
func notify (post: String)
func add(follower: Follower)
func remove(follower: Follower)
}
class Celebrity: Observable {
let name: String
var followers: [Follower] = []
init(name: String) {
self.name = name
}
func notify(post: String) {
for follower in followers {
follower.update(post: post)
}
}
func add(follower: Follower) {
self.followers.append(follower)
}
func remove(follower: Follower) {
guard let removeIndex = followers.firstIndex(where: { $0 === follower }) else { return }
self.followers.remove(at: removeIndex)
}
}
protocol Followable {
func update (post: String)
}
class Follower: Followable {
let name: String
init(name: String) {
self.name = name
}
func update(post: String) {
print("\(name)입니다! -> \(post)")
}
}
let han = Celebrity(name: "한호열")
han.add(follower: Follower(name: "안준호"))
han.add(follower: Follower(name: "황장수"))
han.add(follower: Follower(name: "조석봉"))
han.notify(post: "호랑이 열정, 호열이에요~")
// 안준호입니다! -> 호랑이 열정, 호열이에요~
// 황장수입니다! -> 호랑이 열정, 호열이에요~
// 조석봉입니다! -> 호랑이 열정, 호열이에요~
템플릿 메소드 패턴은 알고리즘의 골격을 정의합니다. 템플릿 메소드를 사용하면 알고리즘의 일부 단계를 서브클래스에서 구현해 알고리즘의 단계는 그대로 유지하면서 일부 단계의 내용만 재정의 할 수 있도록 합니다. 더 자세히 알아보기
import Foundation
protocol RegistrationSupportable {
// template method
func register()
// steps
func inputId()
func validateId()
func inputPassword()
func validatePassword()
func reinputPassword()
func validateReInputPassword()
func presentSuccess()
}
extension RegistrationSupportable {
func register() {
inputId()
validateId()
inputPassword()
validatePassword()
reinputPassword()
validateReInputPassword()
presentSuccess()
}
func inputId() {
print("input id")
}
func inputPassword() {
print("input password")
}
func reinputPassword() {
print("reinput password")
}
func validateReInputPassword(){
print("password and repassword should be identical")
}
func presentSuccess() {
print("Success!")
}
}
final class LooseRuleRegistration: RegistrationSupportable {
func validatePassword() {
print("password should be more than 1 letter")
}
func validateId() {
print("id should be more than 1 letter and alphanumeric letters are allowed")
}
}
final class StrongRuleRegistration: RegistrationSupportable {
func validatePassword() {
print("password should be more than 8 letters. At leat 2 speical characters should be included")
}
func validateId() {
print("id should be more than 5 letter and only alphabet is allowed")
}
}
let reg = LooseRuleRegistration()
reg.register()
// input id
// id should be more than 1 letter and alphanumeric letters are allowed
// input password
// password should be more than 1 letter
// reinput password
// password and repassword should be identical
// Success!
let strongReg = StrongRuleRegistration()
strongReg.register()
// input id
// id should be more than 5 letter and only alphabet is allowed
// input password
// password should be more than 8 letters. At leat 2 speical characters should be included
// reinput password
// password and repassword should be identical
// Success!
반복자 패턴은 컬렉션의 내부 구현을 노출하지 않으면서 컬렉션의 모든 요소에 접근할 수 있는 방법을 제공합니다. 더 자세히 알아보기
protocol Iterable {
associatedtype Iterator
func makeIterator() -> Iterator
}
protocol Iterator {
associatedtype Element
func hasNext() -> Bool
func next() -> Element?
}
final class defaultIterator<T>: Iterator {
typealias Element = T
private var items: [Element] = []
private var current = 0
init(items: [Element]) {
self.items = items
}
func next() -> Element? {
guard hasNext() else { return nil }
defer { self.current += 1 }
return items[current]
}
func hasNext() -> Bool {
current < items.count
}
}
final class MapCollection: Iterable {
private var map: [String: String] = [:]
func add(element: String, for key: String) {
map.updateValue(element, forKey: key)
}
func makeIterator() -> defaultIterator<String> {
return defaultIterator<String>(items: self.map.values.map({ $0 }))
}
}
final class ListCollection: Iterable {
private var list: [String] = []
func add(element: String) {
list.append(element)
}
func makeIterator() -> defaultIterator<String> {
return defaultIterator<String>(items: self.list)
}
}
let map = MapCollection()
let list = ListCollection()
map.add(element: "1", for: "1")
map.add(element: "2", for: "2")
map.add(element: "3", for: "3")
list.add(element: "11")
list.add(element: "22")
list.add(element: "33")
let iterators = [map.makeIterator(), list.makeIterator()]
for iterator in iterators {
while iterator.hasNext() {
print(iterator.next() ?? "0")
}
}