생성 패턴
종류: Pattern
1. 생성- Builder
빌더패턴은 복합 객체의 생성 과정과 표현 방법을 분리하여 동일한 생성 절차에서 서로 다른 표현 결과를 만들어주는 패턴이다!
ex) 맥북 공장 시스템: 컨베이어 벨트 시스템으로 동일하게 만들지만 맥북마다 스펙이 다르게 커스텀 가능!
구조
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하는 걸 막아준다 일반적인 방법으로 객체를 생성하는 고유의 비용이 너무 클 때 유용하다!
구조
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) 제품에 따라 공장의 생산라인을 바꾸는 게 아니라 기본이 되는 생산라인은 설치해놓고 요청에 맞는 제품을 생산해주는 패턴! 여기서 맹점은 어떠한 요청이 들어올지는 모른다는 것!!!!
구조
- 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
- 아까의 예시대로 한다면 iphone용 버튼, ipad용 버튼으로 나눠서 처리를 해야함
- 그리고 iphone은 iphone button, iphone label 등등을 모두 가지고 있게 되니까 만약에 여기서 radio button이 추가된다면 관리하기가 까다로워 지는 거지!!
- 그리고 apple watch 가 추가된다면 또 그만큼 클래스를 만들어야하게 됨 ㅠㅠ
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() { }
}