๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ

iOS/Swift

Design Pattern in Swift [ ์ƒ์„ฑ ํŒจํ„ด ]

 

 

1) ๋นŒ๋” ํŒจํ„ด (Builder Pattern) 

 

- ์–ด๋–ค Product ์™€ ์ด๋ฅผ ๋งŒ๋“œ๋Š” Builder๋กœ ๊ตฌ์„ฑ

- ์ด ํŒจํ„ด์€ Swift์— ๊ทธ๋ ‡๊ฒŒ๊นŒ์ง€ ์œ ํšจํ•œ ํŒจํ„ด์€ ์•„๋‹˜ -> ๋Œ€๋ถ€๋ถ„ ์ดˆ๊ธฐํ™” ๋ฉ”์„œ๋“œ์™€ ํ”„๋กœํผํ‹ฐ ๊ธฐ๋ณธ๊ฐ’์„ ํ™œ์šฉํ•˜์—ฌ ํ•ด๊ฒฐ ๊ฐ€๋Šฅ 

- ํ”„๋กœํผํ‹ฐ์˜ ๊ฐœ์ˆ˜๊ฐ€ ๊ต‰์žฅํžˆ ๋งŽ์•„์ง€๊ณ  ๋ณต์žกํ•ด์ง€๋Š” ๊ฒฝ์šฐ์— ์œ ์šฉ

 

struct MacBook {
    let color: String
    let memory: Int
    let storage: String
    let hasTouchBar: Bool
}

class MacBookBuilder {
    private var color = "Space Gray"
    private var memory = 16
    private var storage = "256GB"
    private var hasTouchBar = false
    
    func setColor(_ color: String) -> MacBookBuilder {
        self.color = color
        return self
    }
    
    func setMemory(_ memory: Int) -> MacBookBuilder {
        self.memory = memory
        return self
    }
    
    func setStorage(_ storage: String) -> MacBookBuilder {
        self.storage = storage
        return self
    }
    
    func setHasTouchBar(_ has: Bool) -> MacBookBuilder {
        self.hasTouchBar = has
        return self
    }
    
    func build() -> MacBook {
        return MacBook(color: color, memory: memory, storage: storage, hasTouchBar: hasTouchBar)
    }
}

let builder = MacBookBuilder()
let macBook1 = builder.setColor("Silver").setMemory(32).setStorage("512").setHasTouchBar(true).build()
let macBook2 = builder.setMemory(32).setStorage("1TB").build()
let macBook3 = builder.build()

 

 

2) ํ”„๋กœํ† ํƒ€์ž… ํŒจํ„ด (Prototype Pattern) 

 

- ๊ธฐ์กด์˜ ๊ฐ์ฒด๋ฅผ ๋ณต์ œํ•˜๊ธฐ ์œ„ํ•œ ํŒจํ„ด -> ์ž๊ธฐ ์ž์‹ ์„ ๋ณต์ œํ•˜๋Š” ํŒจํ„ด 

 

protocol Prototype: AnyObject {
    func clone() -> Self
}

class Seongah: Prototype {
    
    var age: Int
    
    init(age: Int) {
        self.age = age
    }
    
    func clone() -> Self {
        return Seongah(age: self.age) as! Self
    }
}

let seongah = Seongah(age: 26)
seongah.age += 10
print("10๋…„๋’ค ๋‚˜์˜ ๋‚˜์ด = \(seongah.age)")

let seongah2 = seongah.clone()
seongah2.age += 30
print("30๋…„ ๋’ค ๋‚˜์˜ ๋‚˜์ด = \(seongah2.age)")

 

3) ํŒฉํ† ๋ฆฌ ๋ฉ”์„œ๋“œ ํŒจํ„ด (Factory Method Pattern) 

 

- Creator : Factory ์— ๊ธฐ๋ณธ ์—ญํ• ์„ ์ •์˜ํ•˜๋Š” ๊ฐ์ฒด 

- Concrete Creator: Creator๋ฅผ ์ฑ„ํƒํ•˜๊ณ  ์žˆ์œผ๋ฉฐ Product์— ๋งž๋Š” ๊ตฌ์ฒด์  ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ 

- Product : Concrete Product ๊ฐ€ ํ•ด์•ผํ•  ๋™์ž‘๋“ค์„ ์„ ์–ธํ•˜๋Š” ๊ฐ์ฒด 

- Concrete Product: Product๋ฅผ ์ฑ„ํƒํ•˜๋ฉฐ ๊ทธ์— ๋งž๊ฒŒ ๋งŒ๋“  ์‹ค์ œ ๊ฐ์ฒด 

 

  •  ์žฅ์  : ํ”„๋กœํ† ์ฝœ๋กœ ๊ธฐ๋ณธ ๊ธฐ๋Šฅ์„ ์ •์˜ํ•ด์ฃผ์—ˆ๊ธฐ ๋•Œ๋ฌธ์— ๊ธฐ์กด ์ฝ”๋“œ๋ฅผ ๋ณ€๊ฒฝํ•˜์ง€ ์•Š๊ณ  ์ƒˆ๋กœ์šด ํ•˜์œ„ํด๋ž˜์Šค ์ถ”๊ฐ€๊ฐ€ ๊ฐ€๋Šฅํ•˜๊ธฐ ๋–„๋ฌธ์— ์œ ์—ฐ + ํ™•์žฅ์„ฑ ์ข‹์Œ 
  • ๋‹จ์ : Product๊ฐ€ ์ถ”๊ฐ€๋  ๋•Œ๋งˆ๋‹ค ์ƒˆ๋กœ์šด ํ•˜์œ„ํด๋ž˜์Šค๋ฅผ ์ •์˜ํ•ด์ฃผ์–ด์•ผ ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ๋ถˆํ•„์š”ํ•˜๊ฒŒ ๋งŽ์€ ํด๋ž˜์Šค๊ฐ€ ์ •์˜๋˜์–ด์งˆ ์ˆ˜ ์žˆ์Œ / ์ค‘์ฒฉ๋˜์–ด ์‚ฌ์šฉํ•˜๋ฉด ๋งค์šฐ ๋ณต์žกํ•ด์งˆ ์šฐ๋ ค ์žˆ์Œ 

 

// Creator : Factory ์— ๊ธฐ๋ณธ ์—ญํ• ์„ ์ •์˜ํ•˜๋Š” ๊ฐ์ฒด
protocol AppleFactory {
    func createElectronics() -> Product
}

// Concrete Creator : Creator๋ฅผ ์ฑ„ํƒํ•˜๊ณ  ์žˆ์œผ๋ฉฐ Product์— ๋งž๋Š” ๊ตฌ์ฒด์  ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„
class iphonFactory: AppleFactory {
    func createElectronics() -> Product {
        return Iphone()
    }
}

class IpadFactory: AppleFactory {
    func createElectronics() -> Product {
        return Ipad()
    }
}

// Product : Concrete Product๊ฐ€ ํ•ด์•ผํ•  ๋™์ž‘๋“ค์„ ์„ ์–ธํ•˜๋Š” ๊ฐ์ฒด
protocol Product {
    func produceProduct()
}

// Concrete Product : Product๋ฅผ ์ฑ„ํƒํ•˜๋ฉฐ ๊ทธ์— ๋งž๊ฒŒ ๋งŒ๋“  ์‹ค์ œ ๊ฐ์ฒด
class Iphone: Product {
    func produceProduct() {
        print("Hello Iphone was made")
    }
}

class Ipad: Product {
    func produceProduct() {
        print("Hello Ipad was made")
    }
}

class Client {
    func order(factory: AppleFactory) {
        let electronicsProduct = factory.createElectronics()
        electronicsProduct.produceProduct()
    }
}

var client = Client()
client.order(factory: IpadFactory())
client.order(factory: iphonFactory())

 

4) ์ถ”์ƒ ํŒฉํ† ๋ฆฌ ํŒจํ„ด (Abstract Factory Method) 

 

- Factory๊ฐ€ ์ถ”๊ฐ€๋˜๊ณ  ๊ธฐ์กด์— ์กด์žฌํ•˜๋Š” Product๋กœ Factory๋ฅผ ๊ตฌ์„ฑํ•  ๋•Œ๋Š” ๋งค์šฐ ํšจ๊ณผ์ ์ธ ํŒจํ„ด

- ํ•œ๊ณ„: ์ƒˆ๋กœ์šด ์ข…๋ฅ˜์˜ Product๊ฐ€ ์ถ”๊ฐ€๋˜๋ฉด ๊ฐ๊ฐ์˜ Factory์—๋„ ์ถ”๊ฐ€ํ•ด์ค˜์•ผ ํ•˜๋Š” ๊ฒฝ์šฐ๋„ ์ƒ๊น€ / Product์˜ ์ถ”๊ฐ€๋‚˜ ๋ณ€๋™์ด ์žฆ์•„์ง„๋‹ค๋ฉด ๋ชจ๋“  Factory์— ๋ณ€๋™์ด ์ƒ๊ธธ ์œ„ํ—˜์ด ์žˆ์Œ 

// 1. ์ƒ์„ฑ์„ ๋‹ด๋‹นํ•  Factory ๊ตฌํ˜„
// 1) ์ถ”์ƒํ™”๋œ Factory
protocol UIFactoryalbe {
    func createButton() -> Buttonalbe
    func createLabel() -> Labelable
}

// 2) ์—ฐ๊ด€๋œ ์ œํ’ˆ๊ตฐ์„ ์‹ค์ œ๋กœ ์ƒ์„ฑํ•˜๋Š” ๊ตฌ์ฒด factory
final class ipadUIFactory: UIFactoryalbe {

    func createButton() -> Buttonalbe {
        return IpadButton()
    }
    
    func createLabel() -> Labelable {
        return IpadLabel()
    }
    
}

final class iphoneUIFactory: UIFactoryalbe {
    func createButton() -> Buttonalbe {
        return IphoneButton()
    }
    
    func createLabel() -> Labelable {
        return IPhoneLabel()
    }
    
    
}

// 2. ์ƒ์„ฑ๋  Product ๊ตฌํ˜„
// 1) ์ถ”์ƒํ™”๋œ Product
protocol Buttonalbe {
    func touchUP()
}

protocol Labelable {
    var title: String { get }
}

// 2) ์‹ค์ œ๋กœ ์ƒ์„ฑ๋  ๊ตฌ์ฒด Product, ๊ฐ์ฒด๊ฐ€ ๊ฐ€์งˆ ๊ธฐ๋Šฅ๊ณผ ์ƒํƒœ๋ฅผ ๊ตฌํ˜„
final class IphoneButton: Buttonalbe {
    func touchUP() {
        print("iphoneButton")
    }
}

final class IPhoneLabel: Labelable {
    var title: String = "iPhoneLabel"
}

final class IpadButton: Buttonalbe {
    func touchUP() {
        print("IpadButton")
    }
}

final class IphoneLabel: Labelable {
    var title: String = "IphoneLabel"
}

final class IpadLabel: Labelable {
    var title: String = "ipadLabel"
}

// Factory ๋ฅผ ํ†ตํ•ด UI๋ฅผ ๋งŒ๋“ค๊ณ  ๊ฐ€์ง€๊ณ  ์žˆ๋Š” Class
class UIContent {
    var uiFactory: UIFactoryalbe
    var label: Labelable?
    var button: Buttonalbe?
    
    // ์‚ฌ์šฉํ•  UI์˜ Default๊ฐ’์€ iphone
    init(uiFactory: UIFactoryalbe = iphoneUIFactory()) {
        self.uiFactory = uiFactory
        setupUI()
    }
    
    func setupUI() {
        label = uiFactory.createLabel()
        button = uiFactory.createButton()
    }
}

class ViewController: UIViewController {
    
    var iPadUIContent = UIContent(uiFactory: ipadUIFactory())
    var iphonUICOntent = UIContent()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        touchupButton()
        printLabelTitle()
    }
    
    func touchupButton() {
        iPadUIContent.button?.touchUP()
        iphonUICOntent.button?.touchUP()
    }
    
    func printLabelTitle() {
        print(iPadUIContent.label?.title ?? "")
        print(iphonUICOntent.label?.title ?? "")
    }
}

 

5) ์‹ฑ๊ธ€ํ„ด ํŒจํ„ด 

- ์•ฑ ์ „์ฒด์— ๊ฑธ์ณ์„œ ์œ ์ผํ•œ ๊ฐ์ฒด๋ฅผ ๋งŒ๋“ค๊ธฐ ์œ„ํ•ด์„œ ์‚ฌ์šฉ -> ์–ด๋””์—์„œ๋‚˜ ์ „์—ญ์ ์œผ๋กœ ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๋Š” ์œ ์ผํ•œ ๊ฐ์ฒด 

- ์ผ๋ฐ˜์ ์œผ๋กœ ์‹ฑ๊ธ€ํ„ด ํŒจํ„ด์˜ ๋ชฉ์ ์ธ ์œ ์ผํ•œ ๊ฐ์ฒด๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด Class ๋ฅผ ์‚ฌ์šฉ 

- ๊ตฌ์กฐ์ฒด๋กœ ์‹ฑ๊ธ€ํ„ด์„ ๊ตฌํ˜„ํ•˜๊ฒŒ ๋œ๋‹ค๋ฉด ๋งŒ์•ฝ ์‹ฑ๊ธ€ํ„ด ๊ฐ์ฒด๋ฅผ ์ธ์Šคํ„ด์Šคํ™”ํ•˜๊ฒŒ ๋  ๋•Œ ์œ ์ผํ•˜์ง€ ๋ชปํ•œ ๊ฐ์ฒด๊ฐ€ ๋จ 

 

  • ์žฅ์  : ์œ ์ผํ•œ ๊ฐ์ฒด๋ฅผ ๋งŒ๋“ค์–ด์„œ ๋‹ค์–‘ํ•œ ๊ฐ์ฒด๋“ค์—๊ฒŒ ๊ณต์œ ๋˜๋Š” ๊ฐ์ฒด๋ฅผ ๋งŒ๋“ค ์ˆ˜ ์žˆ๋‹ค๋Š” ์žฅ์  / ์žฌ์‚ฌ์šฉ์ด ๊ฐ€๋Šฅํ•˜์—ฌ ๋ฉ”๋ชจ๋ฆฌ ๋‚ญ๋น„๋ฅผ ๋ฐฉ์ง€ 
  • ๋‹จ์ : ๊ฐ์ฒด ์ง€ํ–ฅ ๊ด€์ ์—์„œ ๋ณธ๋‹ค๋ฉด ์ธ์Šคํ„ด์Šค๋“ค ๊ฐ„์— ๊ฒฐํ•ฉ๋„๊ฐ€ ๋†’์•„์ ธ์„œ OCP(๊ฐœ๋ฐฉ-ํ์‡„ ์›์น™, Open-Closed Principle) ์„ ์œ„๋ฐฐํ•˜๊ฒŒ ๋œ๋‹ค๋Š” ๋‹จ์ ์ด ์žˆ์Œ (OCP๋Š” ๋ชจ๋“ˆ์˜ ํ™•์žฅ์—๋Š” ์—ด๋ ค์žˆ์–ด์•ผ ํ•˜๊ณ , ๋ณ€๊ฒฝ์—๋Š” ๋‹ซํ˜€์žˆ์–ด์•ผ ํ•œ๋‹ค๋Š” ๊ฐ์ฒด ์ง€ํ–ฅ ์„ค๊ณ„์˜ ์›์น™ ์ค‘ ํ•˜๋‚˜)