디자인 패턴이 너무 어려워서 정리하기 위한 포스팅 / 개인 기록용
팩토리 메서드 패턴, 추상 팩토리 패턴은 객체를 생성하는 패턴으로 생성 패턴으로 분류 된다.
말 그대로 객체를 생성하는 이론이다.
Factory Method Pattern
public class Main {
public static void main(String[] args) {
Foo foo = new Foo("FOO");
}
}
일반적으로 객체를 생성할 때에는 Java의 경우 new 연산자를 통해 생성자를 호출하고 코틀린의 경우 생성자 함수를 호출한다.
팩토리 메서드 패턴은 클라이언트가 객체를 생성할 때 직접 생성자를 통해 객체를 생성하는 것이 아니라 객체 생성 과정을 추상화 하고 있는 객체에게 위임하여 생성한다.
먼저 생성이 되는 객체(Product)를 추상화 하고 이를 구현하여 구체적인 상품을 만든다.
예를 들어 물약을 정의하고, HP 물약, MP 물약을 구체 상품으로 정의한다.
그리고 아이템(객체)를 생성하는 과정을 추상화 한다.
이를 구체화하여 HP 물약을 생성하는 Creator, MP 물약을 생성하는 Creator를 만든다.
디자인 패턴은 글과 그림으로는 이해하기 힘드니 코드로 보자.
abstract class Potion {
abstract fun drink()
abstract fun buff()
}
class HPPotion : Potion() {
override fun drink() {
println("체력을 즉시 50 회복합니다.")
}
override fun buff() {
println("10초간 피해량이 10퍼센트 감소합니다.")
}
}
class MPPotion : Potion() {
override fun drink() {
println("마나를 즉시 50 회복합니다.")
}
override fun buff() {
println("10초간 마나 소모량이 10퍼센트 감소합니다.")
}
}
먼저 생성이 되는 포션을 추상 클래스로 정의하였다. 포션을 마실 수 있고, 버프를 받을 수 있다.
참고로 생성이 되는 아이템을 추상화할 때는 인터페이스로 해도되고 추상 클래스로 해도 된다.
Creator가 반환 되는 타입을 추상화 하기 위한 과정이기 때문이다.
abstract class PotionCreator() {
fun create(): Potion {
farmingMaterials()
boilingMaterials()
addBuffEffect()
return packaging()
}
abstract fun farmingMaterials()
abstract fun boilingMaterials()
abstract fun addBuffEffect()
abstract fun packaging(): Potion
}
class HPPotionCreator : PotionCreator() {
override fun farmingMaterials() {
println("HP 열매를 찾습니다.")
}
override fun boilingMaterials() {
println("HP 열매를 10분간 끓입니다.")
}
override fun addBuffEffect() {
println("HP 버프 효과를 첨가합니다.")
}
override fun packaging(): Potion {
println("물약통에 넣습니다.")
return HPPotion()
}
}
class MPPPotionCreator : PotionCreator() {
override fun farmingMaterials() {
println("MP 열매를 찾습니다.")
}
override fun boilingMaterials() {
println("MP 열매를 10분간 끓입니다.")
}
override fun addBuffEffect() {
println("MP 버프 효과를 첨가합니다.")
}
override fun packaging(): Potion {
println("물약통에 넣습니다.")
return MPPotion()
}
}
PotionCreator 추상 클래스를 정의하고 HP, MP 포션을 만드는 구현체를 만든다.
그런데 위 코드를 보면 템플릿 메서드 패턴이 보인다. 템플릿 메서드 패턴이 팩토리 메서드 패턴에서 나타날 수 있다.
왜냐하면 포션을 만드는 과정 처럼 객체를 생성하는 과정의 전체적인 틀은 종류와 상관없이 동일할 수 있고 추상화할 수 있기 때문이다.
그런데 꼭 위 코드처럼 템플릿 메서드 패턴의 양상을 보이지 않을 수도 있다.
핵심은 결객체를 생성하기 위해 생성자를 호출하는 책임은 Creator라는 것이다.
class DesignPattern {
@Test
fun client() {
val hpPotion = HPPotionCreator().create()
val mpPotion = MPPPotionCreator().create()
}
}
객체를 생성하기 위한 준비는 끝났다. 그리고 단순히 클라이언트는 자신이 원하는 객체를 만들어줄 수 있는 Creator를 통해 생성한다.
이 패턴을 처음보면 그냥 클라이언트에서 바로 생성자를 사용하면 되지. 뭐이리 복잡하게 객체 만드는 데 힘을 쓰냐라고 생각할 수 있다.
나 또한 그런 생각이 들었다. 그런데 단순하게 생각하자.
객체를 생성하는 과정이 더욱 더 복잡하다면 객체를 생성하고자 하는 클라이언트는 너무 많은 책임을 가지게 된다.
예를 들어 클라이언트가 A 객체를 생성해야 하는데 데이터베이스 접근도 하고 HTTP 통신도 한다고 가정하자.
이걸 클라이언트가 전부 처리한다면 너무 많은 책임을 가지는 것이다.
따라서 A 객체를 생성하는 Creator(팩토리)를 만들어서 처리하는 것이 어쩌면 더 효율적일 수 있다는 것이다.
Abstract Factory Pattern
추상 팩토리 패턴은 팩토리 메서드 패턴과는 달리 Factory에서 여러 타입의 종류가 생성된다.
위 그림에서는 GUI Factory에서 GUI와 관련된 객체들을 생성한다. 버튼, 체크박스를 만드는 걸 볼 수 있다.
또한 윈도우, 맥, 리눅스마다 GUI는 다르기 때문에 각 Factory를 구체적으로 구현한다.
이처럼 팩토리 메서드 패턴은 Creator 개념으로 하나의 객체 생성을 처리했지만 추상 팩토리 메서드 패턴은 어떤 특정 주제에 관련된
여러 객체를 만들어 낸다는 것이다.
코드로 더 자세히 알아보자.
open class Car {}
open class Bike {}
class HyundaiCar : Car() {}
class KiaCar : Car() {}
class HyundaiBike : Bike() {}
class KiaBike : Bike() {}
먼저 자동차와 오토바이라는 아이템을 정의한다. 그리고 현대에서 만드는 자동차, 오토바이와 기아에서 만드는 자동차와 오토바이가 있다.
그리고 각각의 Factory에서 제조사의 자동차와 오토바이를 만든다.
interface VehicleFactory {
fun createCar(): Car
fun createBike(): Bike
}
class HyundaiVehicleFactory : VehicleFactory {
override fun createCar(): Car {
return HyundaiCar()
}
override fun createBike(): Bike {
return HyundaiBike()
}
}
class KiaVehicleFactory : VehicleFactory {
override fun createCar(): Car {
return KiaCar()
}
override fun createBike(): Bike {
return KiaBike()
}
}
VehicleFactory는 자동차와 오토바이를 만드는 메서드를 추상화한다.
그리고 현대와 기아는 VehicleFactory를 구현하여 자신들만의 자동차와 오토바이를 만들어낸다.
팩토리 메서드 패턴의 경우였다면 자동차를 만드는 Factory와 오토바이를 만드는 Factory가 별개로 분리되어 있을 수도 있다.
하지만 이 경우에는 현대, 기아라는 하나의 카테고리를 기준으로 여러 종류의 객체를 만들어내는 추상 팩토리 패턴이다.
class DesignPattern {
fun client() {
val hyundaiVehicleFactory = HyundaiVehicleFactory()
val hyundaiCar = hyundaiVehicleFactory.createCar()
val hyundaiBike = hyundaiVehicleFactory.createBike()
val kiaVehicleFactory = KiaVehicleFactory()
val kiaCar = kiaVehicleFactory.createCar()
val kiaBike = kiaVehicleFactory.createBike()
}
}
그리고 클라이언트는 자신이 생성하고자 하는 제조사의 Factory를 이용해 자동차와 오토바이를 만들 수 있다.
그런데 위 클라이언트의 코드는 자신이 직접 Factory 대상을 고르고 있다.
좀 찾아보니까 제일 이상적인 추상 팩토리 패턴은 클라이언트는 자신이 어떤 구체적인 팩토리를 사용하고 있는지 모르는 것이라고 한다.
class DesignPattern {
fun client(factory: VehicleFactory) {
val car = factory.createCar()
val bike = factory.createBike()
}
fun runClient() {
val factory = if (LocalDate.now().monthValue % 2 == 0) HyundaiVehicleFactory() else KiaVehicleFactory()
client(factory)
}
}
위 코드처럼 오늘이 짝수일이면 클라이언트는 현대 공장을 돌리도록, 홀수일이면 기아 공장을 돌리도록 외부에서 factory를 주입해준다면
클라이언트 입장에서는 어떤 Factory를 사용해야할지 고민하지 않아도 된다.
즉 외부에서 Factory를 결정하는 것이 이상적인 추상 팩토리 패턴이라고 할 수 있다.
정리
팩토리 메서드 패턴, 추상 팩토리 패턴은 클라이언트가 직접 객체를 생성하는 것이 아닌 Creator, Factory 객체에게 요청하여 생성한다.
팩토리 메서드 패턴의 경우 Creator는 하나의 아이템(타입)을 생성한다.
반면에 추상 팩토리 패턴은 같은 주제로 묶일 수 있는 여러 아이템(타입)을 생성한다.
모두 객체의 생성 로직이 복잡하고, 계속해서 구체 아이템이 생겨날 수 있는 상황에 사용하기 좋다.
하지만 간단한 객체 생성일 경우 꼭 도입해야할까?
단순히 생성자로 충분하다면 오히려 위 패턴들의 도입이 복잡해질 수 있을 것이라고 생각한다.
'cs 공부 기록용' 카테고리의 다른 글
[디자인 패턴] Bridge, Adapter (0) | 2025.05.30 |
---|---|
[디자인 패턴] Proxy, Decorator (0) | 2025.05.24 |
[디자인 패턴] Strategy, Template Method (0) | 2025.05.23 |
운영체제 기록용 (0) | 2025.02.08 |