Design patterns in Go: Composite

01 Jan 2021

Imagine that you have several types of objects, for which you have to extract same data. Things are getting more interesting when you have hierarchies of such types. How this can solved: obviously using Composite design pattern. Let’s see a more specific example, in order to understand better.


Let’s imagine that your are building the ordering model for an ecommerce app. The business asks you to calculate the price of the entire order, including the delivery price that is added for company’s service.

As a naive approach, you will sum all items’ prices with delivery cost. That works really great if you have specific products. But let’s imagine that business also asks you to model some product binding, for which can be applied a discount. Well, that a bit trickier, and if you will apply the same naive approach, it will result into more code that needs to be adjusted.

But in this case, it would be easier to use a common interface for the entire types hierarchy, that will extract the prices for all products and bindings, and sum it up with delivery price. This is how the Composite design pattern works.


So basically the Composite design pattern imply a definition of an interface that will be used and implemented by a container, that will expose the interface for the outside world. In our case described above, the container role has the Order object.

Let’s define the common interface:

type Pricing interface {
	GetPrice() int

It is pretty straightforward, it just return an int, that will have the role of pricing. The int data type is chosen for a reason.

Next let’s define the Product struct:

type Product struct {
	Name  string
	Price int

func NewProduct(name string, price int) *Product {
	return &Product{name, price}

func (p Product) GetPrice() int {
	return p.Price

And the ProductBinding:

type ProductBinding struct {
	Items    []Product
	Discount int

func NewProductBinding(discount int) *ProductBinding {
	return &ProductBinding{Discount: discount}

func (pb *ProductBinding) Add(item Product) *ProductBinding {
	pb.Items = append(pb.Items, item)

	return pb

func (pb ProductBinding) GetPrice() int {
	var result int

	for _, item := range pb.Items {
		result += item.Price

	return result - pb.Discount

and of course the Order struct:

type Order struct {
	DeliveryPrice int
	Package       []Pricing

func (o Order) GetPrice() int {
	result := o.DeliveryPrice

	for _, item := range o.Package {
		result += item.GetPrice()

	return result

func (o *Order) SetPrice(price int) *Order {
	o.DeliveryPrice = price

	return o

func (o *Order) Add(item Pricing) *Order {
	o.Package = append(o.Package, item)

	return o

As you noticed, all structures implemend the Pricing interface, and this is critical, since the Order struct, does operate with that type of objects, even if the internals of each type, is very different.

Now, let’s see the client’s code, how all these structures can be used:

var order Order

singleItem := NewProduct("iPhone", 35590)

binding := NewProductBinding(2000)
binding.Add(*NewProduct("iPhone Charger", 12200))
binding.Add(*NewProduct("AirPods", 15900))


fmt.Printf("Total price: %d\n", order.GetPrice())

which will produce:

Total price: 63190

You can do the math, but I am pretty sure the result is correct :smile:


This way, new kind of items can be added to business logic model, and it will still operate properly, and calculate the package delivery cost, simply by implementing the Pricing interface.