• 팩토리 패턴은 크게 3가지 정도가 있다.
    • Simple Factory (엄밀히 말하면 패턴은 아니다)
    • Factory Method Pattern
    • Abstract Method Pattern

Simple Factory

엄밀히 말하면 패턴은 아니다. 프로그래밍에서 자주 쓰이는 관용구 정도의 방식이다.

상황

  • 피자 가게에 주문을 받고, 피자를 만드는 시스템이 있다.
  • 피자의 종류에 상관없이 피자를 굽고, 자르고, 포장하는 과정은 모두 똑같다.
  • 주문에 따라 피자의 종류만 달라진다.
class PizzaStore(val factory: SimplePizzaFactory) {
    fun orderPizza(type: String): Pizza {
        val pizza = factory.createPizza(type)

        pizza.prepare()
        pizza.bake()
        pizza.cut()
        pizza.box()

        return pizza
    }
}

class SimplePizzaFactory {
    fun createPizza(type: String): Pizza {
        if (type == "chicago") {
            return ChicagoPizza()
        } else {
            return CheesePizza()
        }
    }
}

interface Pizza {
    fun prepare()
    fun bake()
    fun cut()
    fun box()
}

class CheesePizza : Pizza {
    override fun prepare() {
        // 재료준비
    }
    override fun bake() {
        // 굽기
    }
    override fun cut() {
        // 자르기
    }
    override fun box() {
        // 포장하기
    }
}

class ChicagoPizza : Pizza {
    override fun prepare() {
        // 재료준비
    }
    override fun bake() {
        // 굽기
    }
    override fun cut() {
        // 자르기
    }
    override fun box() {
        // 포장하기
    }
}
  • 인스턴스를 생성하는 역할을 팩토리 클래스에 위임했다.
    • 피자 종류가 추가되어도 PizzaStore를 변경하지 않아도 된다.

Factory Method Pattern

팩토리 메소드 패턴은 슈퍼 클래스에 객체를 생성할 때 필요한 인터페이스를 만든다. 어떤 클래스의 인스턴스를 만들지는 서브 클래스에게 맡기는 것이다.
  • Creator(슈퍼 클래스)는 사용할 객체를 만드는 메소드를 가지고 있다.
    • Product를 반환받아 작업을 할 뿐이다. Product가 무엇인지는 모른다.
    • ex) 라인 조립을 한다고 하자. 나사들 가져와서 임팩트 드릴로 박아야한다.
      • 라인 작업자가 해야할 일
        1. 나사를 만들어주세요.
        1. 나사를 조립한다.
      • 라인 작업자는 나사의 재질이 무엇이고 어느 공장에서 만들어진건지 알 필요가 없다.

상황

Simple Factory 패턴에 이어서, 이제 피자 가게를 확장하기로 했다.

피자를 만드는 과정은 똑같지만, 피자에는 지역 색이 들어간다.

K-Pizza, Italian Pizza, Chicago Pizza, Newyork Pizza …

기존의 방식을 써볼까?

  • 한 공장에서 너무 많은 피자를 만든다.

바꿔보자

package design_pattern

abstract class PizzaStore {
    fun orderPizza(type: String): Pizza {
        val pizza = createPizza(type)

        pizza.prepare()
        pizza.bake()
        pizza.cut()
        pizza.box()

        return pizza
    }

    abstract fun createPizza(type: String): Pizza
}

class KoreanPizzaStore : PizzaStore() {
    override fun createPizza(type: String): Pizza? {
        return when(type) {
            "cheese" -> KoreanStyleCheesePizza()
            "pepperoni" -> KoreanStylePepperoniPizza()
            "combination" -> KoreanStyleCombinationPizza()
            "potato" -> KoreanStylePotatoPizza()
            else -> null
        }
    }
}

class NYPizzaStore : PizzaStore() {
    override fun createPizza(type: String): Pizza? {
        return when(type) {
            "cheese" -> NYStyleCheesePizza()
            "pepperoni" -> NYStylePepperoniPizza()
            "combination" -> NYStyleCombinationPizza()
            "potato" -> NYStylePotatoPizza()
            else -> null
        }
    }
}

interface Pizza {
    fun prepare()
    fun bake()
    fun cut()
    fun box()
}

class NYStylePotatoPizza : Pizza {

}

class NYStyleCombinationPizza : Pizza {
    // 생략
}

class NYStylePepperoniPizza : Pizza {
    // 생략
}

class NYStyleCheesePizza : Pizza {
    // 생략
}

class KoreanStylePotatoPizza : Pizza {
    // 생략
}

class KoreanStyleCombinationPizza : Pizza {
    // 생략
}

class KoreanStylePepperoniPizza : Pizza {
    // 생략
}

class KoreanStyleCheesePizza : Pizza {
    // 생략
}
  • 실제로 클라이언트가 스토어 구현체에 주문하는 것은 아니지만, 무슨 피자를 만들 것인지는 구현체(서브클래스)에 정의되어 있기 때문에 이렇게 그려봤다.
  • 이렇게 그리는게 맞을 거 같다.

Abstract Factory Pattern

추상 팩토리 패턴은 구현 클래스에 의존하지 않고, 서로 연관되거나 의존적인 객체로 이루어진 제품군(Family)을 생산하는 인터페이스를 제공하는 패턴이다.

  • 사용자는 추상적인 Product만 알고 사용한다. 실제로 어떤 것인지는 상관없다. (팩토리 메소드 패턴도 동일)
  • 팩토리를 통해 반환받은 Product만 사용한다.
  • 사용하는 방법은 똑같기 때문에, Mac에서 노션을 쓰든, Windows에서 노션을 쓰든 동일한 GUI를 사용할 수 있다.

상황

피자가게가 전세계로 나아가다 보니, 서로 쓰는 재료가 달라졌다.

그러다보니 같은 나라의 지점이라도 지점 별로 다른 원산지의 재료를 쓰고 있었다.

각 나라별로 원재료를 공급하는 공장을 만들어 피자의 품질을 올리자.

interface PizzaIngredientFactory {
    fun createDough(): Dough
    fun createSource(): Source
    fun createCheese(): Cheese
    fun createPepperoni(): Pepperoni
}

class NYPizzaIngredientFactory : PizzaIngredientFactory {
    override fun createDough(): Dough {
        return ThinCrustDough()
    }

    override fun createSource(): Source {
        return MarinaraSource()
    }

    override fun createCheese(): Cheese {
        return ReggianoCheese()
    }

    override fun createPepperoni(): Pepperoni {
        return SlicedPepperoni()
    }
}
  • 재료 공장을 만들어준다.

interface Pizza {
    var dough: Dough
    var source: Source
    var cheese: Cheese
    var pepperoni: Pepperroni
    
    fun prepare()
    fun bake()
    fun cut()
    fun box()
}

class PepperoniPizza(private val factory: PizzaIngredientFactory) : Pizza {
    override fun prepare() {
        dough = factory.createDough()
        source = factory.createSource()
        cheese = factory.createCheese()
        pepperoni = factory.createPepperoni()
    }

    override fun bake() {
        TODO("Not yet implemented")
    }

    override fun cut() {
        TODO("Not yet implemented")
    }

    override fun box() {
        TODO("Not yet implemented")
    }
}
  • 피자는 재료공장에서 재료를 받아 만들어진다.
  • 어떤 공장에서 오는지는 몰라도 된다.

class NYPizzaStore : PizzaStore() {
    override fun createPizza(type: String): Pizza? {
				val factory: PizzaIngredientFactory = NYPizzaIngredientFactory()
        return when(type) {
            "cheese" -> CheesePizza(factory)
            "pepperoni" -> PepperoniPizza(factory)
            "combination" -> CombinationPizza(factory)
            "potato" -> PotatoPizza(factory)
            else -> null
        }
    }
}

출처

https://refactoring.guru/ko/design-patterns/factory-method

https://refactoring.guru/ko/design-patterns/abstract-factory

https://ko.wikipedia.org/wiki/추상_팩토리_패턴


Uploaded by N2T

예시

abstract class Component {
    abstract fun operate(): String
}

class ConcreteComponent : Component() {
    override fun operate(): String {
        return "This is ConcreteComponent"
    }
}

abstract class Decorator(val component: Component) : Component() {
    abstract override fun operate(): String
}

class ConcreteDecorator(component: Component) : Decorator(component) {

    override fun operate(): String {
        return component.operate() + "with ConcreteDecorator"
    }
}

  • Client
fun main() {
    var concreteComponent: Component = ConcreteComponent()

    concreteComponent = ConcreteDecorator(concreteComponent)
    concreteComponent = ConcreteDecorator(concreteComponent)
    concreteComponent.operate()
}

개념

데코레이터 패턴은 이 객체를 사용하는 코드를 변경하지 않으면서 객체에 추가행동을 더해줄 수 있는 방법이다. 상속보다 유연하게 사용할 수 있다.

  • Component → 기본이 되는 개념 (음료)
  • ConcreteComponent → 개념을 구현한 구현체 (에스프레소)
  • Decorator → 장식의 틀
  • DecoratorComponent → 구현체의 장식 (우유, 휘핑크림)

비유

  • 카페
    • Component → 음료
    • ConcreteComponent → 에스프레소, 하우스 블렌드, 다크 로스트
    • DecoratorComponent → 모카, 우유, 두유, 휘핑
    abstract class Beverage {
        var description = "제목 없음"
        abstract fun cost(): Double
    }
    
    class Espresso : Beverage() {
        init {
            description = "에스프레소"
        }
        
        override fun cost(): Double {
            return 1.99
        }
    }
    
    class HouseBlend : Beverage() {
        init {
            description = "하우스 블렌드"
        }
    
        override fun cost(): Double {
            return 0.89
        }
    }
    
    abstract class CondimentDecorator(val beverage: Beverage) : Beverage() {
        abstract fun getDescription(): String
    }
    
    class Mocha(beverage: Beverage) : CondimentDecorator(beverage) {
        override fun getDescription(): String {
            return "${beverage.description}, 모카"
        }
    
        override fun cost(): Double {
            return beverage.cost() + 0.20
        }
    }
    
    class Whip(beverage: Beverage) : CondimentDecorator(beverage) {
        override fun getDescription(): String {
            return "${beverage.description}, 휘핑"
        }
    
        override fun cost(): Double {
            return beverage.cost() + 0.15
        }
    }
  • 안드로이드 Notification
    • Comonent → 알림
    • ConcreteComponent → 아이콘, 제목, 내용, PendingIntent 등이 지정되어있는 기본 Notification
    • DecoratorComponent → LagreIcon, BigPictureStyle 등등
    abstract class NotificationComponent {
        lateinit var notificationBuilder: NotificationCompat.Builder
        fun build() : Notification {
            return notificationBuilder.build()
        }
    
        abstract fun createNotificationBuilder(
            context: Context,
            pendingIntent: PendingIntent,
            title: String,
            body: String,
            channelId: String
        ): NotificationCompat.Builder
    }
    
    class BaseNotification: NotificationComponent() {
        override fun createNotificationBuilder(
            context: Context,
            pendingIntent: PendingIntent,
            title: String,
            body: String,
            channelId: String
        ): NotificationCompat.Builder {
    
            notificationBuilder =  NotificationCompat.Builder(context, channelId)
                .setSmallIcon(R.drawable.ic_launcher_transparent)
                .setColor(ContextCompat.getColor(context, R.color.icon_color))
                .setContentTitle(title)
                .setContentText(body)
                .setContentIntent(pendingIntent)
                .setAutoCancel(true)
                .setPriority(NotificationCompat.PRIORITY_HIGH)
                .setDefaults(NotificationCompat.DEFAULT_ALL)
            return notificationBuilder
        }
    }
    
    abstract class NotificationDecorator(val component: NotificationComponent): NotificationComponent()
    
    class LargeIconDecorator(component: NotificationComponent, private val largeIcon:Bitmap) : NotificationDecorator(component) {
        override fun createNotificationBuilder(
            context: Context,
            pendingIntent: PendingIntent,
            title: String,
            body: String,
            channelId: String
        ): NotificationCompat.Builder {
            notificationBuilder = component.createNotificationBuilder(context, pendingIntent, title, body, channelId)
                .setLargeIcon(largeIcon)
            return notificationBuilder
        }
    }
    
    class BigPictureDecorator(component: NotificationComponent, private val image: Bitmap) : NotificationDecorator(component) {
        override fun createNotificationBuilder(
            context: Context,
            pendingIntent: PendingIntent,
            title: String,
            body: String,
            channelId: String
        ): NotificationCompat.Builder {
            val bigPictureStyle = NotificationCompat.BigPictureStyle().also {
                it.setBigContentTitle(title)
                it.setSummaryText(body)
                it.bigPicture(image)
            }
    
            notificationBuilder = component.createNotificationBuilder(context, pendingIntent, title, body, channelId)
                .setStyle(bigPictureStyle)
    
            return notificationBuilder
        }
    }
    • 실제로 동작하는 코드입니다.
    • Caller 부분
      • Java
      NotificationComponent notification = new BaseNotification();
      notification = new LargeIconDecorator(notification, image);
      notification = new BigPictureDecorator(notification, image);
      notification.createNotificationBuilder(this, pendingIntent, title, body, CHANNEL_ID);
      
      notificationManager.notify(CHAT_NOTIFICATION_ID, notification.build());
      • Kotlin
      var notification: NotificationComponent = BaseNotification()
      notification = LargeIconDecorator(notification, image)
      notification = BigPictureDecorator(notification, image)
      notification.createNotificationBuilder(this, pendingIntent, title, body, CHANNEL_ID)
      
      notificationManager.notify(CHAT_NOTIFICATION_ID, notification.build())

장단점

장점

  • 서브클래스를 새로 만들지 않아도 객체의 행동을 추가할 수 있다.
  • 런타임에 행동을 추가할 수 있다.
  • SRP
    • 각 ConcreteDecorator가 하나의 책임을 가진다.

단점

  • 특정 데코레이터를 제거하기 어렵다.
  • 데코레이터의 순서에 의존한다.

이런 경우가 생길 수 있다.

출처

https://refactoring.guru/ko/design-patterns/decorator


Uploaded by N2T

예시 코드

class Subject {
    private val observers = mutableListOf<Observer>()

    fun registerObserver(observer: Observer) {
        observers.add(observer)
    }

    fun unregisterObserver(observer: Observer) {
        observers.remove(observer)
    }

    fun notifyObservers() {
        for (observer in observers) {
            observer.notifyObserver()
        }
    }
}
interface Observer {
    fun notifyObserver()
}

class ConcreteObserverA : Observer {
    override fun notifyObserver() {
        // Do something
    }
}

class ConcreteObserverB : Observer {
    override fun notifyObserver() {
        // Do something
    }
}

개념

Observer Pattern은 한 객체의 상태변화를 다른 객체 그룹에 알려야 할 때 사용되는 디자인 패턴이다. 상태변화를 알려야하는 객체 그룹을 미리 알 수 없거나, 그룹요소들이 동적으로 변경되는 경우 사용할 수 있다.

비유

  • 단체 채팅방

    상황

    • 기능 → 단체 채팅방
    • 행동 → 한 명이 채팅을 보내면 채팅방의 구성원들이 알람 수신

    적용

    interface EventListener {
        fun update(msg: String)
    }
    
    class ChattingMessageListener(val name: String) : EventListener {
        override fun update(msg: String) {
            createPushNotification(msg)
        }
    }
    class ChattingRoom {
        private val members = mutableListOf<ChattingMessageListener>()
    
        fun addMember(member: ChattingMessageListener) {
            members.add(member)
        }
    
        fun removeMember(with = member: ChattingMessageListener) {
            members.remove(member)
        }
    
        fun sendMessageToAllMembers(msg: String, author: String) {
            for (member in members) {
                member.update(msg, author)
            }
        }
    }
    
    fun main() {
        val chattingRoom = ChattingRoom()
        
        val members = arrayOf<ChattingMessageListener>(
            ChattingMessageListener("John"),
            ChattingMessageListener("Choi"),
            ChattingMessageListener("Yoon")
        )
        
        members.forEach { member -> chattingRoom.addMember(with = member) }
    
        chattingRoom.sendMessage("Hello", members[1].name)
    }
  • 주식 알림

    상황

    • 기능 → 주식 가격 알림
    • 행동 → 종목 가격이 변경되면 투자자들에게 알림을 보내줌

    적용

    abstract class Stock(val symbol: String, private val price: Double) {
    	private val investors = mutableListOf<IInvestor>()
    
    	fun attach(investor: IInvestor) {
    		investors.add(investor)
    	}
    
    	fun detach(investor: IInvestor) {
    		investors.remove(investor)
    	}
    
    	fun notify() {
    		for (investor in inverstors) {
    			investor.update(this)
    		}
    	}
    
    	fun setPrice(price: Double) {
    		this.price = price
    		notify()
    	}
    }
    class Apple(symbol: String, price: Double) : Stock(symbol, price)
    interface IInvestor {
    	fun update(stock: Stock)
    }
    class Investor(val name: String) : IInvestor {
    	override fun update(stock: Stock) {
    		println("$name is notified that ${stock.symbol}'s price change to ${stock.price}")
    	}
    }

장단점

장점

  • OCP (개방-폐쇄 원칙)
    • 확장에는 열려있고, 수정에는 닫혀 있어야 한다.

단점

  • 레이스 컨디션
    • 알림을 보내는 중에 옵저버가 등록됐을 때
    • 알림을 보내는 중에 옵저버가 등록해제됐을 때
  • 순환 실행
    • 이벤트 X가 발생했을 때, 옵저버 A가 옵저버 B를 갱신하고, 옵저버 B가 옵저버 A를 갱신한다면 순환 실행이 일어난다.


출처

https://refactoring.guru/ko/design-patterns/observer

https://ko.wikipedia.org/wiki/옵서버_패턴


Uploaded by N2T

예시 코드

interface Strategy {
    fun execute(a: Int, b: Int) : Int
}

class ConcreteStrategyAdd : Strategy {
    override fun execute(a: Int, b: Int): Int {
        return a + b
    }
}

class ConcreteStrategySubtract : Strategy {
    override fun execute(a: Int, b: Int): Int {
        return a - b
    }
}

class ConcreteStrategyMultiply : Strategy {
    override fun execute(a: Int, b: Int): Int {
        return a * b
    }
}

class Context(private var strategy: Strategy) {

    fun executeStrategy(a: Int, b: Int): Int {
        return strategy.execute(a, b)
    }

    fun setStrategy(strategy: Strategy) {
        this.strategy = strategy
    }
}
  • Client
    fun main() {
        val context = Context(ConcreteStrategyAdd())
    
        println(context.executeStrategy(10, 5))
    
        context.setStrategy(ConcreteStrategySubtract())
        println(context.executeStrategy(10, 5))
    
        context.setStrategy(ConcreteStrategyMultiply())
        println(context.executeStrategy(10, 5))
    }

개념

Strategy Pattern이란 객체의 특정 행동(메소드)에 대해 다양한 알고리즘을 효율적으로 사용할 수 있게 하거나, 실행 중에 다른 알고리즘으로 전환할 수 있게 하는 디자인 패턴이다.

*효율적 → 코드 재사용성이 높고, 변화에 대응하기 좋은 구조

  1. 특정한 행동을 인터페이스로 만들고,
  1. 구체적인 행동 클래스를 구현한다.
  1. 행동을 할 주체(객체)에서 인터페이스를 참조하고, 구현체로는 인터페이스를 구현한 클래스를 받는다.
    • 행동을 하는 주체(객체)는 행동을 요청하지만 실제로 어떤 행동이 일어나는 지는 모른다.

비유

  • 네비게이션 어플

    상황

    • 기능 → 목적지 안내
    • 행동 → 목적지로 가는 경로 생성
    • 변화

      → 자차 경로, 도보 경로, 자전거 경로 등, 경로에 대한 옵션이 추가될 수 있음

      → 최단 시간, 최소 환승, 최소 비용 등, 경로 탐색 알고리즘이 달라질 수 있음

    적용

    1. 경로를 구하는 알고리즘을 인터페이스로 만든다.
      interface RouteStrategy {
      	fun buildRoute(from: LngLat, to: LngLat) : Route
      }
    1. 구체적인 경로 알고리즘 클래스를 구현한다.
      class MinimumTimeRoute : RouteStrategy {
      	override fun buildRoute(from: LngLat, to: LngLat) : Route {
      		// algorithm..
      	}
      }
      
      class MinimumCostRoute : RouteStrategy {
      	override fun buildRoute(from: LngLat, to: LngLat) : Route {
      		// algorithm..
      	}
      }
    1. 네비게이션은 루트를 구해달라는 요청만 하고, 실제로 어떤 알고리즘을 사용했는지는 모른다.
      class Navigator(private val routeStrategy: RouteStrategy) {
      
      	fun drawRoute(from: LngLat, to: LngLat) {
      		val route = buildRoute(from, to)
      		// draw to screen...
      	}
      
      	fun buildRoute(from: LngLat, to: LngLat) : Route {
      		routeStrategy.buildRoute(from, to)
      	}
      }
      fun main() {
      	val navi = Navigator(MinimumCostRoute())
      	navi.drawRoute(LngLat(37.232, 126.232), LngLat(37.56, 127.022))
      }

장단점

장점

  • OCP (개방-폐쇄 원칙)
    • 확장에는 열려있고, 수정에는 닫혀 있어야 한다.

단점

  • 행동에 대한 알고리즘 별로 없고, 잘 변하지 않는다면 보일러플레이트 코드가 많이 생긴다.
  • 클라이언트가 전략을 선택해야한다.


출처

https://refactoring.guru/ko/design-patterns/strategy

https://ko.wikipedia.org/wiki/전략_패턴


Uploaded by N2T

+ Recent posts