【Golang】设计模式 - Structural - Decorator

Posted by 西维蜀黍 on 2021-09-19, Last Modified on 2023-08-23

Decorator Pattern

Decorator design pattern is a structural design pattern. It lets you provide additional functionality or decorates an object without altering that object.

换言之,客户端并不会觉得对象在装饰前和装饰后有什么不同。装饰器模式可以在不需要创造更多子类的情况下,将对象的功能加以扩展。这就是装饰器模式的模式动机。

Refer to https://swsmile.info/post/design-pattern-decorator/ for Decorator Pattern.

Demo

It is better understood with an example. Imagine you are opening a pizza chain. You started with two kinds of pizzas

  • Veggie Mania Pizza
  • Peppy Tofu pizza

Each of the above pizza had its price. So you would create a pizza interface as below

package main

type pizza interface {
	getPrice() int
}

You need to also create two pizza struct with a getPrice function which will return the price. These two pizza structs implement the pizza interface as they define the getPrice() method

Later on, you started to offer toppings along with the pizza with some additional price for each of the topping. So the original base pizza now needs to be decorated with a topping. Imaging you added below two toppings to the menu

  • Tomato Topping
  • Cheese Topping

Also, remember that pizza along with the topping is also a pizza. Customer can choose their pizza in different ways. For eg

  • Veggie mania with tomato topping
  • Veggie main with tomato and cheese topping
  • Peppy Paneer pizza without any topping
  • Peppy Paneer pizza with cheese topping

So how would you design now given that you now have the toppings as well. Decorator pattern will come into picture. It can help additional functionality without actually modifying any of the existing structs. Decorator pattern recommends in this case to create separate structs for each of the topping available. Each topping struct will implement the pizza interface above and also have an embed and instance of pizza.

We now have separate structs for different types of pizza and separate struct for the types of topping available. Each of the pizza and topping has its own price. And whenever you add any topping to a pizza then price of that topping is added to the price of base pizza and that is how you get a resultant price.

So the decorator pattern let’s you decorate the original base pizza object without altering that pizza object. The pizza object knows nothing about toppings. It just knows its price and nothing else.

UML Diagram

Below is UML diagram for the decorator design pattern

The concrete component (Veggie Mania and Peppy Tofu here) and concrete decorator (Toppings here) implement the component interface (Pizza here). Also concrete decorator would embed an instance of component as well.

As in below example we have

  • The component is represented by pizza interface

  • The concrete component is represented by veggieMania and peppyPanner struct. They both implement the pizza interface

  • Concrete decorators are represented by cheeseTopping and tomatoTopping struct. They both implement the pizza interface. Also, they embed an instance of type pizza as well

Code

pizza.go

package main

type pizza interface {
	getPrice() int
}

peppyPaneer.go

package main

type peppyPaneer struct {
}

func (p *peppyPaneer) getPrice() int {
	return 20
}

veggeMania.go

package main

type veggeMania struct {
}

func (p *veggeMania) getPrice() int {
	return 15
}

cheeseTopping.go

package main

type cheeseTopping struct {
	pizza pizza
}

func (c *cheeseTopping) getPrice() int {
	pizzaPrice := c.pizza.getPrice()
	return pizzaPrice + 10
}

tomatoTopping.go

package main
type tomatoTopping struct {
    pizza pizza
}
func (c *tomatoTopping) getPrice() int {
    pizzaPrice := c.pizza.getPrice()
    return pizzaPrice + 7
}

main.go

package main

import "fmt"

func main() {

	veggiePizza := &veggeMania{}

	//Add cheese topping
	veggiePizzaWithCheese := &cheeseTopping{
		pizza: veggiePizza,
	}

	//Add tomato topping
	veggiePizzaWithCheeseAndTomato := &tomatoTopping{
		pizza: veggiePizzaWithCheese,
	}

	fmt.Printf("Price of veggieMania pizza with tomato and cheese topping is %d\n", veggiePizzaWithCheeseAndTomato.getPrice())

	peppyPaneerPizza := &peppyPaneer{}

	//Add cheese topping
	peppyPaneerPizzaWithCheese := &cheeseTopping{
		pizza: peppyPaneerPizza,
	}

	fmt.Printf("Price of peppyPaneer with tomato and cheese topping is %d\n", peppyPaneerPizzaWithCheese.getPrice())
}

Output

Price of veggieMania pizza with tomato and cheese topping is 32
Price of peppyPaneer with tomato and cheese topping is 30

Reference