팩토리 패턴(Factory Pattern)

ellyheetov·2021년 2월 28일
17
post-thumbnail

Factory Method Pattern이란?

팩토리 메소드 패턴에서는 객체를 생성하기 위한 인터페이스를 정의하는데, 어떤 클래스의 인스턴스를 만들지는 서브 클래스에서 결정하게 만드는 패턴이다.

왜 Factory Pattern이 나오게 된 걸까?

객체 지향 디자인 패턴의 기본 원칙은 확장에 있어서는 열려 있어야 하며, 수정에 있어서는 닫혀 있어야 한다는 것이다. (OCP, Open Closed Principle)

여기서 수정에 있어서 닫혀 있어야 한다는 말에 주목 해보자. 코드를 수정하지 않아도 모듈의 기능을 확장하거나 변경 할 수 있어야 한다. 때문에, 수정이 일어날 가능성이 큰 부분과 그렇지 않은 부분을 분리하는 것이 좋다.

객체는 속성과 함수가 변경, 또는 추가 될 수 있다. 이에 따라 객체의 생성을 담당하는 코드는 변경의 가능성이 높다. 객체의 생성을 담당하는 클래스를 한 곳에서 관리하여 결합도를 줄이기 위하여 팩토리 패턴이 나타나게 된 것이다.

결합도(의존성)란?

class Product {
    init() {}
}
class User {
    let p = Product()
}

두 개의 클래스가 있다. 하나는 Product 클래스 이고, 다른 하나는 Product를 사용하는 User 클래스 이다. 이때 Product와 User의 관계를 의존 관계라고 한다. 의존의 대상인 Product 클래스가 사라지면, User 클래스는 컴파일이 불가능해지고, 동작할 수 없게 된다.

간단히 말해, 결합도는 한 클래스에 변경점이 얼마나 다른 클래스에 영향을 주는가를 의미한다.

결합도가 높은 예시

앞선 예시에 따르면 User클래스는 Product에 강하게 의존하고 있다.

만약, Product의 생성자가 바뀐다고 생각해보자. 그럼 이제 각각의 User 클래스에 있는 모든 Product 객체의 생성자를 변경해 주어야 한다.

생성자는 변경 될 가능성이 높다. 객체의 생성을 담당하는 클래스를 한 곳으로 분리하여 보자.

팩토리 메소드 패턴 예시1

팩토리 클래스에서는 getInstance() 메소드를 이용하여 인스턴스를 반환한다. 이제, User 클래스 내부에서는 Product 객체를 직접 생성하지 않는다. 팩토리 클래스에 인스턴스를 요청하고, 생성된 인스턴스를 반환받으면 된다.(객체의 생성을 위임했다고 말 할 수 있다.)

만약 Product의 생성자가 변경 된다면??

Factory에 getInstance() 메소드 내부에 있는 Product 생성자만 변경 시켜주면 된다.

팩토리 메소드 패턴 예시2

조금 더 복잡한 예시를 들어보자. Shape라는 인터페이스를 만들고, 해당 인터페이스를 구현하는 구체적인 클래스를 만든다.

Step1. Protocol 생성

protocol Shape {
    func draw()
}

Step2. Class 생성

class Rectangle : Shape {
    func draw() {
        print("Inside Rectangle::draw() method")
    }
}
class Square : Shape {
    func draw() {
        print("Inside Square::draw() method")
    }
}
class Circle : Shape {
    func draw() {
        print("Inside Circle::draw() method")
    }
}

Shape를 프로토콜로 채택한 클래스들은 나름대로 draw()를 구현한다.

Step3. Factory 생성

class ShapeFactory {
    public func getShape(shapeType : String) -> Shape? {
        if shapeType == nil {
            return nil
        }
        if shapeType == "CIRCLE" {
            return Circle()
        }
        else if shapeType == "RECTANGLE" {
            return Rectangle()
        }
        else if shapeType == "SQUARE"{
            return Square()
        }
        return nil
    }
}

이는 객체의 생성을 캡슐화 한다. ShapeFactory 클래스에서 어떤 클래스를 만들지 결정하게 함으로써 객체의 생성을 캡슐화 한다.

Step4. Demo

let shapeFactory = ShapeFactory()

let shape1 = shapeFactory.getShape(shapeType: "CIRCLE")
shape1?.draw()

let shape2 = shapeFactory.getShape(shapeType: "RECTANGLE")
shape2?.draw()

let shape3 = shapeFactory.getShape(shapeType: "SQUARE")
shape3?.draw()

shape1, shape2, shape3 는 모두 Circle, Rectangle, Square의 생성자가 어떻게 생겼는지 모른다. 단지 Factory에게 "Rectangle 모양 주세요" 라고 요청 한 후, Rectangle 인스턴스를 반환 받을 뿐이다.

Step5. Output

Inside Circle::draw() method
Inside Rectangle::draw() method
Inside Square::draw() method

Factory Method의 Pros and Cons

장점

  • 객체간의 결합도를 낮출 수 있다.
  • 단일 책임 원칙을 따른다. 프로그램의 코드에서 생성자 코드를 분리함으로써 코드를 더욱 간결하게 만들 수 있다.
  • 개방 폐쇄 원칙을 따른다. 기존 client의 코드를 파괴하지 않고 새로운 타입을 추가 할 수 있다.

단점

  • 패턴을 구현할 많은 서브 클래스를 도입합으로써 코드가 복잡 해 질 수 있다.

Abstract Factory Pattern이란?

추상 팩토리 패턴은 많은 수의 연관된 서브 클래스를 특정 그룹으로 묶어 한번에 교체할 수 있도록 만든 디자인 패턴이다.

추상 팩토리 패턴은 팩토리 메소드 패턴과 유사하다. 하지만 한가지 다른 것은 팩토리를 만드는 상위 팩토리(super-factory) 클래스가 존재한다.

추상 팩토리 패턴 예시

Step1. Class 생성

class RoundedRectangle : Shape {
    func draw() {
        print("Inside RoundedRectangle::draw() method")
    }
}
class RoundedSquare : Shape {
    func draw() {
        print("Inside RoundedSquare::draw() method")
    }
}

Step2. Super Factory 생성

protocol AbstractFactory {
    func getShape(shapeType : String) -> Shape?
}

Step3. 하위 Factory 생성

class ShapeFactory : AbstractFactory {
    public func getShape(shapeType : String) -> Shape? {
        if shapeType == "RECTANGLE" {
            return Rectangle()
        } else if shapeType == "SQUARE"{
            return Square()
        }
        return nil
    }
}
class RoundedShapeFactory : AbstractFactory {
    public func getShape(shapeType : String) -> Shape? {
        if shapeType == "RECTANGLE" {
            return RoundedRectangle()
        } else if shapeType == "SQUARE"{
            return RoundedSquare()
        }
        return nil
    }
}

Step4. Factory를 생성하는 Producer 생성

class FactoryProducer {
    static func getFactory(rounded : Bool) -> AbstractFactory {
        if rounded {
            return RoundedShapeFactory()
        }else {
            return ShapeFactory()
        }
    }
}

Step5. Demo

let shapeFactory1 = FactoryProducer.getFactory(rounded: false)

let shape1 = shapeFactory1.getShape(shapeType: "RECTANGLE")
shape1?.draw()

let shape2 = shapeFactory1.getShape(shapeType: "SQUARE")
shape2?.draw()


let shapeFactory2 = FactoryProducer.getFactory(rounded: true)

let shape3 = shapeFactory2.getShape(shapeType: "RECTANGLE")
shape3?.draw()

let shape4 = shapeFactory2.getShape(shapeType: "SQUARE")
shape4?.draw()

Step6. Output

Inside Rectangle::draw() method
Inside Square::draw() method
Inside RoundedRectangle::draw() method
Inside RoundedSquare::draw() method

Abstract Factory Pattern의 Pros and Cons

장점

  • Factory로 부터 만들어진 각 객체들이 서로 compatible 함을 보장할 수 있다.
  • 객체간의 결합도를 낮출 수 있다.
  • 단일 책임 원칙을 따른다. 프로그램의 코드에서 생성자 코드를 분리함으로써 코드를 더욱 간결하게 만들 수 있다.
  • 개방 폐쇄 원칙을 따른다. 기존 client의 코드를 파괴하지 않고 새로운 타입을 추가 할 수 있다.

단점

  • 패턴을 구현할 많은 서브 클래스를 도입합으로써 코드가 복잡 해 질 수 있다.

정리

팩토리 메소드 패턴 : 객체를 생성하는 인터페이스를 정의 함으로써, 어떤 인스턴스를 생성할 지는 하위 클래스에서 결정하는 패턴이다.
추상 팩토리 패턴 : 팩토리들을 그룹으로 묶어 관리할 수 있는 패턴이다.

참고
https://www.tutorialspoint.com/design_pattern/abstract_factory_pattern.htm
https://refactoring.guru/design-patterns/abstract-factory
https://www.youtube.com/watch?v=QrNVYepenws&ab_channel=%ED%94%84%EB%A6%AC%EB%A0%89

profile
 iOS Developer 좋아하는 것만 해도 부족한 시간

0개의 댓글