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.
NOTES
In this article, we discuss the implementation of the Composite Pattern in Go.
See the composite in other languages in the “Other Code Implementations” section. Or, use the link below to check the details of the Composite Pattern-
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.