팩토리 메소드 패턴에서는 객체를 생성하기 위한 인터페이스를 정의하는데, 어떤 클래스의 인스턴스를 만들지는 서브 클래스에서 결정하게 만드는 패턴이다.
객체 지향 디자인 패턴의 기본 원칙은 확장에 있어서는 열려 있어야 하며, 수정에 있어서는 닫혀 있어야 한다는 것이다. (OCP, Open Closed Principle)
여기서 수정에 있어서 닫혀 있어야 한다는 말에 주목 해보자. 코드를 수정하지 않아도 모듈의 기능을 확장하거나 변경 할 수 있어야 한다. 때문에, 수정이 일어날 가능성이 큰 부분과 그렇지 않은 부분을 분리하는 것이 좋다.
객체는 속성과 함수가 변경, 또는 추가 될 수 있다. 이에 따라 객체의 생성을 담당하는 코드는 변경의 가능성이 높다. 객체의 생성을 담당하는 클래스를 한 곳에서 관리하여 결합도를 줄이기 위하여 팩토리 패턴이 나타나게 된 것이다.
class Product {
init() {}
}
class User {
let p = Product()
}
두 개의 클래스가 있다. 하나는 Product 클래스 이고, 다른 하나는 Product를 사용하는 User 클래스 이다. 이때 Product와 User의 관계를 의존 관계라고 한다. 의존의 대상인 Product 클래스가 사라지면, User 클래스는 컴파일이 불가능해지고, 동작할 수 없게 된다.
간단히 말해, 결합도는 한 클래스에 변경점이 얼마나 다른 클래스에 영향을 주는가를 의미한다.
앞선 예시에 따르면 User클래스는 Product에 강하게 의존하고 있다.
만약, Product의 생성자가 바뀐다고 생각해보자. 그럼 이제 각각의 User 클래스에 있는 모든 Product 객체의 생성자를 변경해 주어야 한다.
생성자는 변경 될 가능성이 높다. 객체의 생성을 담당하는 클래스를 한 곳으로 분리하여 보자.
팩토리 클래스에서는 getInstance()
메소드를 이용하여 인스턴스를 반환한다. 이제, User 클래스 내부에서는 Product 객체를 직접 생성하지 않는다. 팩토리 클래스에 인스턴스를 요청하고, 생성된 인스턴스를 반환받으면 된다.(객체의 생성을 위임했다고 말 할 수 있다.)
만약 Product의 생성자가 변경 된다면??
Factory에 getInstance()
메소드 내부에 있는 Product 생성자만 변경 시켜주면 된다.
조금 더 복잡한 예시를 들어보자. Shape라는 인터페이스를 만들고, 해당 인터페이스를 구현하는 구체적인 클래스를 만든다.
protocol Shape {
func draw()
}
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()
를 구현한다.
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 클래스에서 어떤 클래스를 만들지 결정하게 함으로써 객체의 생성을 캡슐화 한다.
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 인스턴스를 반환 받을 뿐이다.
Inside Circle::draw() method
Inside Rectangle::draw() method
Inside Square::draw() method
장점
단점
추상 팩토리 패턴은 많은 수의 연관된 서브 클래스를 특정 그룹으로 묶어 한번에 교체할 수 있도록 만든 디자인 패턴이다.
추상 팩토리 패턴은 팩토리 메소드 패턴과 유사하다. 하지만 한가지 다른 것은 팩토리를 만드는 상위 팩토리(super-factory) 클래스가 존재한다.
class RoundedRectangle : Shape {
func draw() {
print("Inside RoundedRectangle::draw() method")
}
}
class RoundedSquare : Shape {
func draw() {
print("Inside RoundedSquare::draw() method")
}
}
protocol AbstractFactory {
func getShape(shapeType : String) -> Shape?
}
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
}
}
class FactoryProducer {
static func getFactory(rounded : Bool) -> AbstractFactory {
if rounded {
return RoundedShapeFactory()
}else {
return ShapeFactory()
}
}
}
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()
Inside Rectangle::draw() method
Inside Square::draw() method
Inside RoundedRectangle::draw() method
Inside RoundedSquare::draw() method
장점
단점
팩토리 메소드 패턴 : 객체를 생성하는 인터페이스를 정의 함으로써, 어떤 인스턴스를 생성할 지는 하위 클래스에서 결정하는 패턴이다.
추상 팩토리 패턴 : 팩토리들을 그룹으로 묶어 관리할 수 있는 패턴이다.
참고
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