Design Pattern: Composite Pattern in Go

Using Composite pattern we can make an object and a group of objects, behave the same way. The client will not know any difference between an object and object group/list while using it.

This article demonstrates Composite pattern implementations in GoLang. Check the implementation details and examples.

Implementation

  • Create an interface for the item.
  • Create item structs, and implement the item interface for the structs.
  • Create another struct for the item group. Implement item interface for this struct also. Store a list of items in the struct, and declare a field for that. In the method implementation call item methods in a loop.
  • Define method for adding and deleting items from the item group.

Here is a simple example of Composite pattern-

// Composite pattern example

package main

import "fmt"

// Item interface
type Item interface {
	Operation1()
	Operation2()
}

// First item
type Item1 struct{}

func NewItem1() (item1 *Item1) {
	item1 = &Item1{}
	return
}

func (item1 *Item1) Operation1() {
	fmt.Println("Item 1 => Operation 1")
}

func (item1 *Item1) Operation2() {
	fmt.Println("Item 1 => Operation 2")
}

// Second item
type Item2 struct{}

func NewItem2() (item1 *Item1) {
	item1 = &Item1{}
	return
}

func (item2 *Item2) Operation1() {
	fmt.Println("Item 2 => Operation 1")
}

func (item2 *Item2) Operation2() {
	fmt.Println("Item 2 => Operation 2")
}

// Item group
type ItemGroup struct{
	items []Item
}

func NewItemGroup() (itemGroup *ItemGroup) {
	itemGroup = &ItemGroup{}
	return
}

func (itemGroup *ItemGroup) Operation1() {
	fmt.Println("Item Group => Operation 1")

	for _, item := range itemGroup.items {
		item.Operation1()
	}
}

func (itemGroup *ItemGroup) Operation2() {
	fmt.Println("Item Group => Operation 2")

	for _, item := range itemGroup.items {
		item.Operation2()
	}
}

func (itemGroup *ItemGroup) AddItem(item Item) {
	itemGroup.items = append(itemGroup.items, item)
}

func (itemGroup *ItemGroup) RemoveItem(item Item) {
	itemGroup.items = append(itemGroup.items, item)

	newItems := []Item{}

	for _, currentItem := range itemGroup.items {
		if currentItem == item {
			continue
		}

		newItems = append(newItems, currentItem)
	}

	itemGroup.items = newItems
}

// Demo
func main() {
	item1 := NewItem1()
	item2 := NewItem2()

	item1.Operation1();

	itemGroup := NewItemGroup()
	itemGroup.AddItem(item1)
	itemGroup.AddItem(item2)

	itemGroup.Operation1()
	itemGroup.Operation2()
}

Output generated by the code above is –

Item 1 => Operation 1

Item Group => Operation 1
Item 1 => Operation 1
Item 1 => Operation 1

Item Group => Operation 2
Item 1 => Operation 2
Item 1 => Operation 2

Examples

Here are a few examples of Composite pattern-

Example #1: Transport List

For this example, we have a bunch of transports – Car, Plane, Bike. We want to implement the composite pattern, so that we can treat an individual vehicle and a list of vehicles the same way.

Transport Interface

  • Create a file “transport.go”.
  • Create an interface “Transport”.
  • Declare a few methods – “Start”, “Stop”, and “Operate”.
// transport.go

package main

type Transport interface {
	Start()
	
	Stop()

	Operate()

}

Bike Struct

  • Create a file “bike.go”.
  • Create struct “Bike”.
  • Define method “NewBike”, which is responsible for creating a new “Bike” object.
  • Implement interface “Transport” for struct “Bike”, define methods – “Start”, “Stop”, and “Operate” for the struct.
// bike.go

package main

import "fmt"

type Bike struct {
}

func NewBike() (bike *Bike) {
	bike = &Bike{}
	return
}

func (bike *Bike) Start() {
	fmt.Println("Starting Bike...")
}

func (bike *Bike) Stop() {
	fmt.Println("Stopping Bike...")
}

func (bike *Bike) Operate() {
	fmt.Println("Riding Bike")
}

Plane Struct

  • Create a file “plane.go”.
  • Create struct “Plane”.
  • Define method “NewPlane”.
  • Implement interface “Transport” for struct “Plane”.
// plane.go

package main

import "fmt"

type Plane struct {
}

func NewPlane() (plane *Plane) {
	plane = &Plane{}
	return
}

func (plane *Plane) Start() {
	fmt.Println("Starting Plane...")
}

func (plane *Plane) Stop() {
	fmt.Println("Stopping Plane...")
}

func (plane *Plane) Operate() {
	fmt.Println("Flying Plane")
}

Car Struct

  • Create a file “car.go”.
  • Create struct “Car”.
  • Define the method “NewCar”.
  • Implement interface “Transport” for struct “Car”.
// car.go

package main

import "fmt"

type Car struct {
}

func NewCar() (car *Car) {
	car = &Car{}
	return
}

func (car *Car) Start() {
	fmt.Println("Starting Car...")
}

func (car *Car) Stop() {
	fmt.Println("Stopping Car...")
}

func (car *Car) Operate() {
	fmt.Println("Driving Car")
}

Composite Class

  • Create a file “transport_group.go”.
  • Create struct “TransportGroup”.
  • Define a field “transportList”, which is a slice of type “Transport”. This will store the list of transport for the group.
  • Define the method “NewTransportGroup”.
  • Define method “AddTransport” and “RemoveTransport” so that we can add and/or remove elements to the list.
  • Implement interface “Transport” for struct “Car”. Define “Start”, “Stop”, and “Repair” methods for the struct, as part of the interface implementation. In the method implementations, use the list and perform operations on individual element.
// transport_group.go

package main

type TransportGroup struct {
	transportList []Transport
}

func NewTransportGroup() (transportGroup *TransportGroup) {
	transportGroup = &TransportGroup{}
	return
}

func (transportGroup *TransportGroup) AddTransport(transport Transport) {
	transportGroup.transportList = append(transportGroup.transportList, transport)
}

func (transportGroup *TransportGroup) RemoveTransport(transport Transport) {
	newTransportGroup := []Transport{}

	for _, currentTransport := range transportGroup.transportList {
		if currentTransport == transport {
			continue
		}

		newTransportGroup = append(newTransportGroup, currentTransport)
	}

	transportGroup.transportList = newTransportGroup
}

func (transportGroup *TransportGroup) Start() {
	for _, transport := range transportGroup.transportList {
		transport.Start()
	}
}

func (transportGroup *TransportGroup) Stop() {
	for _, transport := range transportGroup.transportList {
		transport.Stop()
	}
}

func (transportGroup *TransportGroup) Operate() {
	for _, transport := range transportGroup.transportList {
		transport.Operate()
	}
}

Demo

To use the implementation, we can create individual transport objects. Then add/remove elements to the transport group list.

And we can perform operations on a transport object, or on the group/list, like below-

// main.go

package main

import "fmt"

func main() {
	bike := NewBike()
	plane := NewPlane()
	car := NewCar()
	secondCar := NewCar()
	transports := NewTransportGroup()
	transports.AddTransport(bike)
	transports.AddTransport(plane)
	transports.AddTransport(car)
	transports.AddTransport(secondCar)

	fmt.Println("-----------------Output with 4 transports------------------")

	transports.Start()
	transports.Operate()
	transports.Stop()

	fmt.Println("n-----------------Output when plane is removed------------------")

	transports.RemoveTransport(plane)
	transports.Start()
	transports.Operate()
	transports.Stop()
}

Output

The output of the demo above will be like below.

-----------------Output with 4 transports------------------

Starting Bike...
Starting Plane...
Starting Car...
Starting Car...

Riding Bike
Flying Plane
Driving Car
Driving Car

Stopping Bike...
Stopping Plane...
Stopping Car...
Stopping Car...

-----------------Output when plane is removed------------------

Starting Bike...
Starting Car...
Starting Car...

Riding Bike
Driving Car
Driving Car

Stopping Bike...
Stopping Car...
Stopping Car...

Example #2: Player Group

For this example, we have considered a bunch of individual players (of different games) and also a group of players. Let’s use composite pattern to treat an individual player and group the same way.

Player Interface

  • Create a file “player.go”.
  • Define interface “Player”.
// player.go

package main

type Player interface {
	PrintDetails()
}

Basketball Player Struct

  • Create a file “basketball_player.go”.
  • Define struct “BasketBallPlayer”.
  • Define fields – “name”, “age”, “point”.
  • Define method “NewBasketBallPlayer”, which takes “name”, “age”, and “point” as param and creates a new “BasketBallPlayer” object.
  • Implement “Player” interface for the struct, define the method “PrintDetails” for the struct as part of the interface implementation.
// baseketball_player.go

package main

import "fmt"

type BasketBallPlayer struct {
	name  string
	age   int
	point int
}

func NewBasketBallPlayer(name string, age int, point int) (basketballPlayer *BasketBallPlayer) {
	basketballPlayer = &BasketBallPlayer{}
	basketballPlayer.name = name
	basketballPlayer.age = age
	basketballPlayer.point = point
	return
}

func (basketballPlayer *BasketBallPlayer) PrintDetails() {
	fmt.Println("Game: Basketball")
	fmt.Printf("Name: %vn", basketballPlayer.name)
	fmt.Printf("Age: %vn", basketballPlayer.age)
	fmt.Printf("Points: %vn", basketballPlayer.point)
}

Football Player Struct

  • Create a file “football_player.go”.
  • Define struct “FootballPlayer”. 
  • Define fields – “name”, “age”, and “goal”.
  • Define method “NewFootballPlayer”, which takes params and creates a new “FootballPlayer” object.
  • Implement “Player” interface for the struct.
// football_player.go

package main

import "fmt"

type FootballPlayer struct {
	name string
	age  int
	goal int
}

func NewFootballPlayer(name string, age int, goal int) (footballPlayer *FootballPlayer) {
	footballPlayer = &FootballPlayer{}
	footballPlayer.name = name
	footballPlayer.age = age
	footballPlayer.goal = goal
	return
}

func (footballPlayer *FootballPlayer) PrintDetails() {
	fmt.Println("Game: Football")
	fmt.Printf("Name: %vn", footballPlayer.name)
	fmt.Printf("Age: %vn", footballPlayer.age)
	fmt.Printf("Goals: %vn", footballPlayer.goal)
}

Cricket Player Struct

  • Create a file “cricket_player.go”.
  • Define struct “CricketPlayer”. 
  • Define fields – “name”, “age”, and “run”.
  • Define method “NewCricketPlayer”, which takes params and creates a new “CricketPlayer” object.
  • Implement “Player” interface for the struct.
// cricket_player.go

package main

import "fmt"

type CricketPlayer struct {
	name string
	age  int
	run  int
}

func NewCricketPlayer(name string, age int, run int) (cricketPlayer *CricketPlayer) {
	cricketPlayer = &CricketPlayer{}
	cricketPlayer.name = name
	cricketPlayer.age = age
	cricketPlayer.run = run
	return
}

func (cricketPlayer *CricketPlayer) PrintDetails() {
	fmt.Println("Game: Cricket")
	fmt.Printf("Name: %vn", cricketPlayer.name)
	fmt.Printf("Age: %vn", cricketPlayer.age)
	fmt.Printf("Runs: %vn", cricketPlayer.run)
}

Player Group

  • Create file “player_group.go”.
  • Define struct “PlayerGroup”. 
  • Define fields “playerList” of type “Player” slice. This will store a list of players.
  • Define method “PlayerGroup”, which takes params and creates a new “PlayerGroup” object.
  • Define methods “AddElement” and “RemoveElement”.
  • Implement “Player” interface for the struct. Define method “PrintDetails” for the struct, in the method loop through the list of players and perform operations on individual player objects.
// player_group.go

package main

type PlayerGroup struct {
	playerList []Player
}

func NewPlayerGroup() (playerGroup *PlayerGroup) {
	playerGroup = &PlayerGroup{}
	return
}

func (playerGroup *PlayerGroup) AddElement(player Player) {
	playerGroup.playerList = append(playerGroup.playerList, player)
}

func (playerGroup *PlayerGroup) RemoveElement(player Player) {
	newPlayerList := []Player{}

	for _, currentPlayer := range playerGroup.playerList {
		if currentPlayer == player {
			continue
		}

		newPlayerList = append(newPlayerList, currentPlayer)
	}

	playerGroup.playerList = newPlayerList
}

func (playerGroup *PlayerGroup) PrintDetails() {
	for _, player := range playerGroup.playerList {
		player.PrintDetails()
	}
}

Demo

To use it, we can create a group and add players to the group. We can also create group of groups.

We can perform operations on individual objects or groups, like below.-

// main.go

package main

func main() {
	under15Players := NewPlayerGroup()
	under15Players.AddElement(NewFootballPlayer("FPlayer 15_1", 13, 23))
	under15Players.AddElement(NewFootballPlayer("FPlayer 15_2", 14, 30))
	under15Players.AddElement(NewBasketBallPlayer("BPlayer 15_1", 12, 80))
	under15Players.AddElement(NewBasketBallPlayer("BPlayer 15_2", 14, 100))
	under15Players.AddElement(NewCricketPlayer("CPlayer 15_1", 14, 467))

	under19Players := NewPlayerGroup()
	under19Players.AddElement(NewFootballPlayer("FPlayer 19_1", 18, 43))
	under19Players.AddElement(NewBasketBallPlayer("BPlayer 19_1", 17, 77))
	under19Players.AddElement(NewCricketPlayer("CPlayer 19_1", 18, 654))
	under19Players.AddElement(NewCricketPlayer("CPlayer 19_2", 16, 789))

	nationalTeamPlayers := NewPlayerGroup()
	nationalTeamPlayers.AddElement(NewFootballPlayer("FPlayer N_1", 18, 43))
	nationalTeamPlayers.AddElement(NewBasketBallPlayer("BPlayer N_1", 17, 77))
	nationalTeamPlayers.AddElement(NewCricketPlayer("CPlayer N_1", 18, 654))

	allTeams := NewPlayerGroup()
	allTeams.AddElement(under15Players)
	allTeams.AddElement(under19Players)
	allTeams.AddElement(nationalTeamPlayers)

	allTeams.PrintDetails()
}

Output

The above demo code will generate the below output-

Game: Football
Name: FPlayer 15_1
Age: 13
Goals: 23

Game: Football
Name: FPlayer 15_2
Age: 14
Goals: 30

Game: Basketball
Name: BPlayer 15_1
Age: 12
Points: 80

Game: Basketball
Name: BPlayer 15_2
Age: 14
Points: 100

Game: Cricket
Name: CPlayer 15_1
Age: 14
Runs: 467

Game: Football
Name: FPlayer 19_1
Age: 18
Goals: 43

Game: Basketball
Name: BPlayer 19_1
Age: 17
Points: 77

Game: Cricket
Name: CPlayer 19_1
Age: 18
Runs: 654

Game: Cricket
Name: CPlayer 19_2
Age: 16
Runs: 789

Game: Football
Name: FPlayer N_1
Age: 18
Goals: 43

Game: Basketball
Name: BPlayer N_1
Age: 17
Points: 77

Game: Cricket
Name: CPlayer N_1
Age: 18
Runs: 654

Source Code

Use the following link to get the source code:

Other Code Implementations

Use the following links to check Composite pattern implementation in other programming languages.

Leave a Comment


The reCAPTCHA verification period has expired. Please reload the page.