생성 패턴

종류: Pattern

1. 생성- Builder

빌더패턴은 복합 객체의 생성 과정과 표현 방법을 분리하여 동일한 생성 절차에서 서로 다른 표현 결과를 만들어주는 패턴이다!

ex) 맥북 공장 시스템: 컨베이어 벨트 시스템으로 동일하게 만들지만 맥북마다 스펙이 다르게 커스텀 가능!

구조

Untitled

builder의 setValues로 커스텀할 값들을 세팅한 후에 build를 통해 product를 생성

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

// Builder
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

cf. prototype: 생성할 객체들의 타입이 프로토타입인 인스턴스로부터 결정되도록 하며 인스턴스는 새 객체를 만들기 위해 자신을 복제

추상팩토리 패턴와 달리 클라이언트 코드 내에서 Creator를 subclass하는 걸 막아준다 일반적인 방법으로 객체를 생성하는 고유의 비용이 너무 클 때 유용하다!

구조

Untitled

clone하는 프로토콜인 Prototype을 만들고 복제하고 싶은 class는 해당 프로토콜을 구현하는 형태

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

class Odongnamu: Prototype {
    var age: Int

    init(age: Int) {
        self.age = age
    }

    func clone() -> Self {
        return Odongnamu(age: self.age) as! Self
    }
}

let odongnamu = Odongnamu(age: 500)
odongnamu.age += 50
print(odongnamu.age) // 100

let odongnamu2 = odongnamu.clone()
odongnamu2.age += 30
print(odongnamu2.age) // 80

3. 생성 - Factory Method Pattern

부모 클래스에 알려지지 않은 구체적인 클래스를 생성하는 패턴 자식 클래스가 어떤 객체를 생성할지 결정

ex) 제품에 따라 공장의 생산라인을 바꾸는 게 아니라 기본이 되는 생산라인은 설치해놓고 요청에 맞는 제품을 생산해주는 패턴! 여기서 맹점은 어떠한 요청이 들어올지는 모른다는 것!!!!

구조

Untitled

  • Creator: Factory의 역할을 정의한 프로토콜
  • Product: Product가 해야하는 역할에 대해 정의한 프로토콜
  • ConcreateCreator: Creator를 채택하면서 product에 맞는 구체적 기능을 구현
  • ConcreateProduct: Product를 채택하며 그에 맞는 실제 객체
// Creator
protocol AppleFactory {
    func createElectronics() -> Product
}

// Concrete Creator
class IPhoneFactory: AppleFactory {
    func createElectronics() -> Product {
        return IPhone()
    }
}

class IPadFactory: AppleFactory {
    func createElectronics() -> Product {
        return IPad()
    }
}

// Product
protocol Product {
    func produceProduct()
}

// Concrete Product
class IPhone: Product {
    func produceProduct() {
        print("Hello, iPhone was made")
    }
}

class IPad: Product {
    func produceProduct() {
        print("Hello, iPad wad made")
    }
}

class Client {
// 프로토콜을 매개변수로 받으니까 어떤 주문이 들어오는지 모르는 상태이고 주문이 들어올 때 알게 됨!
    func order(factory: AppleFactory) {
        let elctronicsProduct = factory.createElectronics()
        elctronicsProduct.produceProduct()
    }
}

var client = Client()

client.order(factory: IPadFactory()) 
client.order(factory: IPhoneFactory())

/*
Hello, iPad was made
Hello, iPhone was made
*/

장점

  • 프로토콜로 기본 기능을 정의해주었기 때문에 기존 코드를 변경하지 않고 새로운 하위클래스를 추가가 가능하기 때문에 유연하고 확장성이 높다.
  • 코드에 수정 사항이 생기더라도 팩토리 메소드만 수정하면 되기 때문에 수정에 용이하다.

단점

  • product가 추가될 때마다 새롭게 하위 클래스를 정의해주어야 하기 때문에 불필요하게 많은 클래스가 정의되어질 수 있고 그러다보면 복잡해지는 문제가 발생할 수 있다.
  • 중첩되어 사용되면 매우 복잡해질 우려가 있다.

4. 생성 - Abstract Factory

기존 팩토리를 한번 더 추상화해서 서로 관련있는 제품군을 생성하게 해줌

Q. 한번 더 추상화가 무슨 말일까???

A. 만약에 버튼을 만들어주는 팩토리와 레이블을 만들어주는 팩토리가 있었음.

근데 ipad, iphone UI가 각각 필요함!!

그러면 버튼, 레이블 팩토리를 한번 더 추상화해서 ipad, iphone 팩토리를 만들면 해결됨!!

언제 사용될까?

  • 생성을 책임지는 클래스를 분리하고 싶을 때
  • 여러 제품군 중 선택해서 시스템을 구성하고 제품군을 대체하고 싶을 때

Factory VS Abstract Factory

Untitled

  • 아까의 예시대로 한다면 iphone용 버튼, ipad용 버튼으로 나눠서 처리를 해야함
  • 그리고 iphone은 iphone button, iphone label 등등을 모두 가지고 있게 되니까 만약에 여기서 radio button이 추가된다면 관리하기가 까다로워 지는 거지!!
  • 그리고 apple watch 가 추가된다면 또 그만큼 클래스를 만들어야하게 됨 ㅠㅠ

Untitled

import Foundation

// 추상화된 Factory
protocol UIFactoryalbe {
    func createButton() -> Buttonalbe
    func createLabel() -> Labelable
}

// 연관된 제품군을 실제로 생성하는 구체 Factory
final class iPadUIFactoy: 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()
    }
}
import Foundation

// 추상화된 Product 
protocol Buttonalbe {
    func touchUP()
}

protocol Labelable {
    var title: String { get }
}

// 실제로 생성될 구체 Product, 객체가 가질 기능과 상태를 구현
final class IPhoneButton: Buttonalbe {
    func touchUP() {
        print("iPhoneButton")
    }
}

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

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

final class IPadLabel: Labelable {
    var title: String = "iPadLabel"
}
import UIKit

class ViewController: UIViewController {

        //UI를 가지고 있는 인스턴스 기기별로 설정
    var iPadUIContent = UIContent(uiFactory: iPadUIFactoy())
    var iPhoneUIContent = UIContent()

    override func viewDidLoad() {
        super.viewDidLoad()
        touchUpButton()
        printLabelTitle()
    }

    func touchUpButton() {
        iPadUIContent.button?.touchUP()
        iPhoneUIContent.button?.touchUP()
    }

    func printLabelTitle() {
        print(iPadUIContent.label?.title ?? "")
        print(iPhoneUIContent.label?.title ?? "")
    }
}

//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()
    }

        //기기에 맞는 UI들 설정
    func setUpUI() {
        label = uiFactory.createLabel()
        button = uiFactory.createButton()
    }
}

5. 생성 - Singleton

특정 클래스의 인스턴스가 오직 하나임을 보장한다!

  • 전역적으로 접근할 수 있는 유일한 객체 하나를 만들자라는 의미!!
class Singleton {
    // 1
    static let defaults = Singleton()
    // 2
    private init() { }
}