해당 포스팅에 사용된 예제는 이 블로그 를 참고했습니다 

개요

등장 배경

  • 객체 지향 디자인 패턴의 기본 원칙(OCP; Open Closed Principle)
    • “확장에 있어서는 열려 있어야 하며, 수정에 있어서는 닫혀있어야 한다”
    • 코드를 수정하지 않아도 모듈의 기능을 확장하거나 변경할 수 있어야 함
    수정이 일어날 가능성이 큰 부분과 그렇지 않은 부분을 분리하는 것이 좋음
  • 객체는 속성과 함수가 변경, 추가될 가능성이 높음 → 객체의 생성을 담당하는 코드는 변경될 가능성이 높음
  • 객체의 생성을 담당하는 클래스를 한 곳에서 관리하여 클라이언트 코드와의 결합도를 줄이기 위해 사용
    • 결합도(의존도)란 한 클래스의 변경 사항이 다른 클래스에 얼마나 영향을 주는가를 의미
    • 클라이언트 코드란 분리시킨 객체 생성 코드를 호출하는 쪽을 말함

 

정의 및 특징

  • 객체를 생성하는 작업을 (팩토리) 클래스에 따로 모아두는 것을 의미
  • 상속 관계에서 부모 클래스가 중요한 뼈대를 결정하고, 자식 클래스가 객체 생성에 관한 구체적인 내용을 결정함
  • 상위 클래스와 하위 클래스가 분리되기 때문에 느슨한 결합을 가짐
  • 크게 2가지 종류를 가짐
    • 팩토리 메소드 패턴: 객체를 생성하기 위한 인터페이스를 정의할 때, 어떤 클래스의 인스턴스를 만들지 서브 클래스에서 결정
    • 추상 팩토리 패턴: 서로 연관된, 또는 의존하는 객체를 구상 클래스를 지정하지 않고도 인터페이스를 이용하여 생성할 수 있음
      (ex: 핸드폰=기종+OS+…)
    • 추상 팩토리 패턴에 팩토리 메소드 패턴이 포함될 수 있음

 

장점

  • 서로 간의 결합도를 낮추고 확장을 쉽게 함 → 유지보수성 향상
  • 상위 클래스에서는 인스턴스 생성 방식에 대해 전혀 알 필요가 없기 때문에 더 많은 유연성을 가짐
  • 사용자가 사용할 때는 정의된 인터페이스에 정의된 추상 메소드만 사용하면 됨
  • 단일 책임 원칙을 따름. 프로그램의 코드에서 생성자 코드를 분리함으로써 코드를 더욱 간결하게 만들 수 있음
  • 개방 폐쇄 원칙을 따름. 기존 client의 코드를 파괴하지 않고 새로운 타입을 추가할 수 있음

 

활용

  • 어떤 클래스가 자신이 생성해야 하는 객체의 클래스를 예측할 수 없는 경우
  • 생성할 객체를 기술하는 책임을 자신의 서브 클래스가 지정했으면 할 경우
  • java.util 패키지에 있는 Calendar, ResourceBundle, NumberFormat 등의 클래스에 정의된 getInstance() 메소드에서 팩토리 패턴을 사용
  • Boolean, Integer, Long 등 Wrapper 클래스에 정의된 valueOf() 메소드에서 팩토리 패턴을 사용

 

단점

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

 

종합

  • 팩토리 패턴에 사용되는 슈퍼 클래스는 인터페이스나 추상 클래스, 혹은 그냥 평범한 자바 클래스여도 상관 없음
  • 팩토리 클래스를 Singleton으로 구현해도 되고, 서브 클래스를 리턴하는 static 메소드로 구현해도 됨
  • 팩토리 메소드는 입력된 파라미터에 따라 다른 서브 클래스의 인스턴스를 생성하고 리턴함

팩토리 메소드 패턴

정의 및 특징

  • 객체를 생성하기 위한 인터페이스는 미리 정의하되, 어떤 클래스의 인스턴스를 만들지는 서브 클래스에서 결정하도록 함
  • 즉, 클래스의 인스턴스를 만드는 일을 서브클래스에게 맡기는 것
  • 여러 개의 서브 클래스를 가진 슈퍼 클래스가 있을 때, 인풋에 따라 하나의 자식 클래스의 인스턴스를 리턴해주는 방식

 

예제 코드

1. 프로토콜 생성

protocol Shape {
    func draw()
}

2. 클래스 생성

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")
    }
}

3. 팩토리 생성

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
    }
}

4. 데모

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()

5. 출력 결과

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

추상 팩토리 패턴

정의 및 특징

  • 팩토리 메소드 패턴과 유사하나, 팩토리를 만드는 상위 팩토리(super-factory) 클래스가 존재
  • 연관된 서브 클래스를 그룹화할 수 있고 그 그룹을 자유롭게 교체할 수 있는 패턴
  • 인터페이스를 이용하여 구체적인 클래스에 의존하지 않고 서로 연관되거나 의존적인 객체들의 조합을 생성함

 

예제 코드

1. 클래스 생성

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

2. 슈퍼 팩토리 생성

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

3. 하위 팩토리 생성

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
    }
}

4. 팩토리를 생성하는 Producer 생성

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

5. 데모

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()

6. 출력 결과

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

 


References

+ Recent posts