[Swift/디자인패턴] Factory Pattern

frogKing·2023년 4월 24일
0

디자인패턴

목록 보기
6/6
post-thumbnail

팩토리 패턴은 Factory Method와 Abstract Factory로 나뉜다. 오늘은 각 패턴의 특징과 차이점을 알아보는 시간을 가져볼까 한다.

Factory Method

In class-based programming, the factory method pattern is a creational pattern that uses factory methods to deal with the problem of creating objects without having to specify the exact class of the object that will be created. This is done by creating objects by calling a factory method—either specified in an interface and implemented by child classes, or implemented in a base class and optionally overridden by derived classes—rather than by calling a constructor. - Wikipedia

팩토리 메서드는 어떤 객체가 생성되는지 알지 못하게 객체를 생성하는 패턴이라고 한다. 그저 팩토리 메서드를 호출해서 객체를 생성하면 되기 때문에 팩토리 메서드를 호출하는 쪽에서는 반환값이 어떤 타입인지만 알 수 있을 뿐 어떻게 생성되는지는 알 수 없다.

그렇다면 객체를 사용하는 곳에서 어떤 객체가 생성되는지 알 수 없게 하면 어떤 이점이 있길래 팩토리 메서드를 사용하는 것일까?

Example

동물농장이라는 게임에서 코끼리가 있다고 가정해보자.

class Elephant {
    var name: String
    var age: Int
    var havingParents: Bool
    
    init(name: String, age: Int, havingParents: Bool = true) {
        self.name = name
        self.age = age
        self.havingParents = havingParents
    }
}

처음에는 동물농장에서 부모 코끼리들 사이에서 코끼리가 태어났기 때문에 갓 태어난 코끼리(0살)만 만들어주면 되었다.

let 유토실 = Elephant(name: "유토실", age: 0)

하지만 이후에 외부 동물원에서 코끼리를 들여오는 시스템이 생겼다. 아마 코드는 이렇게 될 것이다.

let 유비실 = Elephant(name: "유비실", age: 2, havingParents: false)

이후에 동물농장 게임이 번창하게 되면 건강한 코끼리, 병약한 코끼리, 식탐이 많은 코끼리 등 다양한 코끼리 생성 방식들이 생겨나면서 객체 생성 코드는 방대해질 것이다.

이렇듯 객체를 사용하는 쪽에서 객체의 생성과 관련된 로직을 몰아넣게 되면 코드가 방대해지고 알아보기 힘들어진다. 이를 팩토리 메서드 패턴을 통해 단순하게 바꿔보자.

우선 다음과 같이 코끼리 팩토리 프로토콜을 정의한다.

protocol ElephantFactory {
    func create() -> Elephant
}

그리고 코끼리 팩토리 프로토콜을 채택하는 코끼리 생성 클래스를 만들어준다.

class HealthyElephantFactory: ElephantFactory {
    func create() -> Elephant {
        return Elephant(age: 0, isHealthy: true)
    }
}

class OrphantElephantFactory: ElephantFactory {
    func create() -> Elephant {
        return Elephant(age: 0, havingParents: false)
    }
}

이런 식으로 생성할 코끼리를 팩토리 별로 나눠주면 분기 처리와 함께 요긴하게 사용할 수 있다.

var elephant: Elephant
if isHealthyState {
    elephant = HealthyElephantFactory().create()
}

// ...

if isOrphan {
    elephant = OrphantElephantFactory().create()
}

정의 그대로 Elephant가 생성될 것은 알지만 어떤 Elephant인지는 알지 못하게 되었다. 한 객체에서 다양한 조합이 나올 수 있다면 이렇게 팩토리 메서드로 묶어서 관리하면 되겠다.

Abstract Factory

The abstract factory pattern in software engineering is a design that provides a way to create families of related objects without imposing their concrete classes, by encapsulating a group of individual factories that have a common theme without specifying their concrete classes. According to this pattern, a client software component creates a concrete implementation of the abstract factory and then uses the generic interface of the factory to create the concrete objects that are part of the family. - Wekipedia

추상 팩토리 패턴은 구체적인 클래스를 지정하지 않고 공통 주제를 가진 개별 팩토리 그룹을 캡슐화하여 관련 객체의 패밀리를 생성하는 방법을 제공한다.

구체적인 클래스를 지정하지 않고? 관련 객체의 패밀리를 생성? 말로만 들어서는 왇하지가 않는다. 코드를 통해 알아보자.

Example

당신이 카페를 운영하고 있다고 치자. 매장에서는 두 제품을 묶어서 파는 이벤트를 종종 진행하는데, 신제품인 캬라멜 컵 케익과 아메리카노의 궁합은 가히 환상적이기 때문에 이벤트로 이 두 제품을 묶어서 팔기로 했다. 묶어서 팔기 전 캬라멜 컵 케익과 아메리카노 클래스는 다음과 같다.

protocol Dessert {
    var cost: Int { get }
    var description: String { get }
}

protocol Beverage {
    var cost: Int { get }
    var capacity: Int { get }
    var description: String { get }
}

class CaramelCupCake: Dessert {
    var cost: Int = 6500
    
    var description: String = "정말 맛있는 캬라멜 컵 케이크!"
}

class Americano: Beverage {
    var cost: Int = 3000
    var capacity: Int = 500
    
    var description: String = "씁쓰름한 아메리카노!"
}
}

이벤트로 둘을 묶어서 팔기로 했으니 다음과 같이 추상 팩토리를 정의하였다.

protocol BundleEventFactory {
    func createBeverage() -> Beverage
    func createDessert() -> Dessert
}

class CaramelAmericanoFactory: BundleEventFactory {
    func createBeverage() -> Beverage {
        return Americano()
    }
    func createDessert() -> Dessert {
        return CaramelCupCake()
    }
}

이렇게 카라멜 컵 케익과 아메리카노를 생성해주는 팩토리를 만듦으로써 이벤트에 맞는 객체만 생성해줄 수 있게 되었다.

이후에도 음료와 디저트를 묶어서 파는 이벤트가 생기면 BundleEventFactory 를 채택하는 클래스를 만들면 된다.

특징

Factory Method vs Abstract Factory

우선 팩토리 메서드 패턴을 한 마디로 잘 요약한 말이 있어서 가져와 보았다.

팩토리 메서드 패턴은 하위 클래스에 의해 재정의될 수 있는 메서드를 의미한다.

앞에서 내가 들었던 예시는 정확히 따지면 하위 클래스에 의한 재정의가 아니라 프로토콜 채택을 통한 수평적 확장이라고 볼 수 있다. 하지만 결국 특정 객체를 반환하는 메서드를 만들어놓고, 해당 메서드를 재정의 혹은 확장을 함으로써 기존 클라이언트의 코드를 손상시키지 않고 새로운 타입의 객체를 생성할 수 있게 된다.

추상 팩토리 패턴을 한 마디로 요약한 것은 다음과 같다.

여러 개의 팩토리 메서드를 가질 수 있는 객체.

이 말도 앞에서 예로 들었던 것들을 다시 보고 오면 이해가 될 것이다. 팩토리 메서드는 어떤 객체가 생성되는지 알지 못하고 타입 정도만 알수 있다.

추상 팩토리 패턴은 앞에서의 케익과 아메리카노처럼 공통 주제를 가진 개별 팩토리 메서드들을 모아놓은 것에 불과하다.

// 추상 팩토리
protocol BundleEventFactory {
    func createBeverage() -> Beverage // 팩토리 메서드 1
    func createDessert() -> Dessert   // 팩토리 메서드 2
}

장점

  1. 객체를 생성하는 쪽과 객체를 사용하는 쪽을 분리함으로써 단일책임원칙(SRP)를 지킬 수 있다.
  2. 기존의 클라이언트 코드를 손상시키지 않고 새로운 타입의 Product를 프로그램에 도입할 수 있어 개방폐쇄원칙(OCP)를 지킬 수 있다.

단점

  1. 패턴을 구현하기 위해 다소 복잡하게 많은 하위 클래스를 도입해야 한다.
profile
내가 이걸 알고 있다고 말할 수 있을까

0개의 댓글