Factory Method

DongHeon·2022년 11월 1일
0

디자인 패턴

목록 보기
2/12

지난 Singleton 패턴에 이어 이번에는 GoF 패턴 중 생성에 속해있는 Factory Method에 대해 작성해 보겠습니다.

Factory Method?

구체적으로 어떤 인스턴스를 만들지는 서브 클래스가 결정하는 패턴을 말합니다.

  • 구체적인 인스턴스는 ConcreteProduct에서 생성한다.
  • 기본 구현은 프로토콜에서 제공하고 Creator 프로토콜을 채택한 타입에서는 각각 다른 타입을 반환합니다.

Product 같은 경우 프로토콜을 이용해 구현할 수 있지만 제가 생각했을 때 좀 더 구체적인 타입에서 인스턴스를 생성한다는 말이 상속이 어울리는 것 같아 UML을 위에 이미지처럼 작성했습니다.

코드

하나의 Factory에서 모든 자동차를 만드는 코드를 작성해 보겠습니다.
패턴 적용 전

class Car {
    var name: String
    var color: String
    
    init(name: String, color: String) {
        self.name = name
        self.color = color
    }
}
class CarFactory {
    func makeCar(name: String, color: String) -> Car? {
        if name == "k3" {
            return Car(name: name, color: color)
        } else if name == "K5" {
            return Car(name: name, color: color)
        } else if name == "K7" {
            return Car(name: name, color: color)
        }
        
        return nil
    }
}

위 코드를 보면 CarFactory에서 makeCar 메서드를 이용해 자동차를 만들고 있습니다.

위 코드의 문제점
1. makeCar 메서드 내부에서 분기 처리를 통해 각각 다른 인스턴스를 생성해야 한다.
2. 특별한 프로퍼티를 갖는 객체를 만들고 싶을 때 기존에 구현한 Car를 수정해야 한다. 그렇다면 의도하지 않은 변경이 발생할 수 있고 기존의 코드를 변경해야 한다.

기존의 코드를 변경하게 되면 객체지향 원칙 중 OCP를 위반하게 된다
(Car를 확장하기 위해서는 계속해서 기존 코드를 변경해야 한다.)

OCP?
확장에는 열려 있어야 하지만 변경에는 닫혀 있어야 하는 객체 지향 다섯 가지 원칙 중 하나이다.

패턴 적용

  • Creator
protocol Factory {
    func orederCar(color: String) -> Car
    func makeCar(color: String) -> Car
}

extension Factory {
    func orederCar(color: String) -> Car {
        prepare()
        let car = makeCar(color: color)
        success()
        return car
    }
    
    private func prepare() {
        print("차를 준비중입니다.")
    }
    
    private func success() {
        print("완성했습니다.")
    }
}

orederCar 메서드 같은 경우 프로토콜을 확장해 기본 구현을 해주었다.

  • ConcreteFactory
class K3Factory: Factory {
    func makeCar(color: String) -> Car {
        return K3(name: "K3", color: color)
    }
}

class CarnivalFactory: Factory {
    func makeCar(color: String) -> Car {
        return Carnival(option: "추가", color: color)
    }
}

각 Factory에서 다른 인스턴스를 생성해 반환한다.

  • Product
class Car {
    var name: String
    var color: String
    
    init(name: String, color: String) {
        self.name = name
        self.color = color
    }
}
  • ConcreteProduct
class K3: Car { }

class Carnival: Car {
    let option: String
    
    init(option: String, color: String) {
        self.option = option
        super.init(name: "Carnival", color: color)
    }
}
  • 사용
let myCar = K3Factory().orederCar(color: "White")
let secondCar = CarnivalFactory().orederCar(color: "black") as! Carnival

타입 캐스팅 을 통해 구체적인 타입으로 변환하고 해당 타입에 있는 메소드를 사용하거나 프로퍼티에 접근할 수 있습니다.

장단점

이제 Factory Method의 장점과 단점을 알아보겠습니다.

  • 장점
  1. OCP를 준수할 수 있습니다.
  2. 타입을 명확하게 사용할 수 있기 때문에 SRP를 준수할 수 있습니다.
  3. Creator와 Product의 관계를 느슨하게 유지할 수 있습니다.

🤔 프로토콜에 의존하기 때문에 관계를 느슨하게 유지하는 걸까?
상속을 사용 하더라도 상위 클래스에 의존하기 때문에 관계를 느슨하게 유지 할 수 있는지가 궁금합니다...... (아시는 분은 댓글로 남겨주세요 😭)

  • 단점
  1. 원하는 인스턴스를 생성하기 위해서는 구체적인 Creator를 알고 있어야 합니다.
  2. 관리해야 하는 객체들이 많아집니다. 위 예시에서 생각해보면 자동차의 종류에 따라 별도의 Creator가 존재합니다.

해당 글은 인프런의 코딩으로 학습하는 GoF 디자인 패턴 강의와 블로그를 참고해 작성했습니다.

참고 블로그

Refactoring.Guru

0개의 댓글