Design Pattern: Facade Pattern in Go

Facade pattern adds a new layer on top of any complex subsystem. That way the client does not need to know all the complexity while using the implementation.

Facade does not necessarily cover all the functionality of the subsystems, only the required functionalities are covered.

NOTES

In this article, we discuss the implementation of the Facade Pattern in Go.

See the Facade in other languages in the “Other Code Implementations” section. Or, use the link below to check the details of the Facade Pattern-

Implementation

We have to check the subsystem implementation first, as the facade implementation will depend on the existing subsystem implementation.

Then follow the steps below for facade implementation-

  • Create a facade struct.
  • Define fields of type of the subsystems, these fields will hold references to the subsystem object.
  • Initialize the subsystem reference fields while creating the facade object.
  • Implement some methods in the faced as per requirement.
  • In the methods call/use the methods from the subsystem objects.

Here is a simple implementation example-

// Facade pattern implementation in Go

package main

import "fmt"

// First subsystem
type Subsystem1 struct{}

func NewSubsystem1() (subsystem1 *Subsystem1) {
	subsystem1 = &Subsystem1{}
	return
}

func (s *Subsystem1) operation1() {
	fmt.Println("Subsystem 1: operation 1")
}

func (s *Subsystem1) operation2() {
	fmt.Println("Subsystem 1: operation 2")
}

// Second subsystem
type Subsystem2 struct{}

func NewSubsystem2() (subsystem2 *Subsystem2) {
	subsystem2 = &Subsystem2{}
	return
}

func (s *Subsystem2) operation3() {
	fmt.Println("Subsystem 2: operation 3")
}

func (s *Subsystem2) operation4() {
	fmt.Println("Subsystem 2: operation 4")
}

// Facade
type Facade struct {
	subsystem1 *Subsystem1
	subsystem2 *Subsystem2
}

func NewFacade() (facade *Facade) {
	facade = &Facade{}
	facade.subsystem1 = NewSubsystem1()
	facade.subsystem2 = NewSubsystem2()
	return
}

func (facade *Facade) facadeOperation1() {
	// Some operation

	facade.subsystem1.operation1()

	// Some operations here
}

func (facade *Facade) facadeOperation2() {
	// Some operation

	facade.subsystem1.operation2()

	facade.subsystem2.operation3()

	// Some operations here
}

// Demo
func main() {
	facade := NewFacade()
	facade.facadeOperation1()
	facade.facadeOperation2()
}

Output will be-

Subsystem 1: operation 1

Subsystem 1: operation 2
Subsystem 2: operation 3

Examples

Here are examples of Facade pattern implementation-

Example #1: Travel Plan

Here we have some submodules that are required for planning a journey- Car, Toll, Weather, and Location.

We will create a facade that will hide these subsystems from the client and the client will only interface with the facade.

Car Struct [Subsystem]

  • Create file “car.go”.
  • Define struct “Car”.
  • Create a method “NewCar”, that will create a new “Car” object and return that.
  • Define some methods for the struct, according to the requirement.
// car.go

package main

import (
	"fmt"
	"math/rand"
)

type Car struct {
}

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

func (car *Car) GetDistanceTravelled() (float64) {
	return float64((rand.Intn((10000-100)*10 + 1) + 100*10) / 10.0)
}

func (car *Car) GoLeft() {
	fmt.Println("Go Left: ←")
}

func (car *Car) GoRight() {
	fmt.Println("Go Right: →")
}

func (car *Car) GoStraight() {
	fmt.Println("Go Straight: ↑")
}

func (car *Car) GoBack() {
	fmt.Println("Go Back: ↓")
}

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

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

Location Struct [Subsystem]

  • Create file “location.go”.
  • Define struct “Location”.
  • Create a method “NewLocation” for initiating a new struct.
  • Define some methods for the struct.
// location.go

package main

import (
	"fmt"
	"math/rand"
)

type Location struct {
	startLat float64
	startLng float64
	endLat   float64
	endLng   float64
}

func NewLocation(startLat float64, startLng float64, endLat float64, endLng float64) (location *Location) {
	location = &Location{}
	location.startLat = startLat
	location.startLng = startLng
	location.endLat = endLat
	location.endLng = endLng
	return
}

func (location *Location) GetCurrentLocation() (*Point) {
	currentLat := float64((rand.Intn((90 - -90)*10 + 1) - 90*10) / 10.0)
	currentLng := float64((rand.Intn((180 - -180)*10 + 1) - 180*10) / 10.0)
	return NewPoint(currentLat, currentLng)
}

func (location *Location) GetFullRoute() ([]Point) {
	points := make([]Point, 10)
	for i := 0; i < 10; i++ {
		currentLat := float64((rand.Intn((int)((90-(-90))*10+1))-90*10) / 10.0)
        currentLng := float64((rand.Intn((int)((180-(-180))*10+1))-180*10) / 10.0)

		tempPoint := NewPoint(currentLat, currentLng)
		points[i] = *tempPoint
	}
	return points
}

func (location *Location) GetLocationDetails(lat float64, lng float64) {
	fmt.Println("Country: ABC")
	fmt.Println("City: DEF")
	fmt.Println("State: GHI")
	fmt.Println("Zip: 101010")
}

func (location *Location) GetNextMove() (string) {
	nextMoves := []string{"straight", "left", "right"}
	var moveIndex int = rand.Intn(len(nextMoves));
	return nextMoves[moveIndex]
}

Toll Struct [Subsystem]

  • Create file “toll.go”.
  • Define struct “Toll”.
  • Create a method “NewToll” for initiating a new struct.
  • Define some methods for the struct.
// toll.go

package main

import "math/rand"

type Toll struct {
}

func NewToll() (toll *Toll) {
	toll = &Toll{}
	return
}

func (toll *Toll) GetTollAmount(tollPointId int) (float64) {
	r := rand.Intn(100)
	return float64((r + 10) / 10.0)
}

func (toll *Toll) GetTollPoints(lat float64, lng float64) ([]Point) {
	points := make([]Point, 100)
	for i := 0; i < 3; i++ {
		currentLat := float64((rand.Intn((int)((90-(-90))*10+1))-90*10) / 10.0)
        currentLng := float64((rand.Intn((int)((180-(-180))*10+1))-180*10) / 10.0)

		tempPoint := NewPoint(currentLat, currentLng)
		points[i] = *tempPoint
	}
	return points
}

func (toll *Toll) GetTotalToll(lat float64, lng float64) (float64) {
	return float64((rand.Intn(((100-1)*10 + 1)) + 10) / 10.0)
}

Weather Struct [Subsystem]

  • Create file “weather.go”.
  • Define struct “Weather”.
  • Create a method “NewWeather” for initiating a new struct.
  • Define some methods for the struct.
// weather.go

package main

import "fmt"

type Weather struct {
}

func NewWeather() (weather *Weather) {
	weather = &Weather{}
	return
}

func (weather *Weather) GetWeatherInfo(lat float64, lng float64) {
	fmt.Println("Temperature: 20.7")
	fmt.Println("Precipitation: 1%")
	fmt.Println("Humidity: 73%")
	fmt.Println("Wind: 8 km/h")
}

Facade

  • Create file “travel-facade.go”.
  • Define the struct “TravelFacade”.
  • Declare some fields of type of the subsystems – location, car, toll, weather.
  • Create method “NewTravelFacade”, accept param as per requirement. Initialize and set field values for – location, toll, car, weather.
  • Define methods for the struct as per requirement. In the method implementations use/call methods form the subsystems.
// travel-facade.go

package main

import "fmt"

type TravelFacade struct {
	startLat float64
	startLng float64
	endLat   float64
	endLng   float64
	location *Location
	toll     *Toll
	car      *Car
	weather  *Weather
}

func NewTravelFacade(startLat float64, startLng float64, endLat float64, endLng float64) (travelFacade *TravelFacade) {
	travelFacade = &TravelFacade{}
	travelFacade.startLat = startLat
	travelFacade.startLng = startLng
	travelFacade.endLat = endLat
	travelFacade.endLng = endLng
	travelFacade.location = NewLocation(startLat, startLng, endLat, endLng)
	travelFacade.car = NewCar()
	travelFacade.toll = NewToll()
	travelFacade.weather = NewWeather()
	return
}

func (travelFacade *TravelFacade) GetCurrentLocation() *Point {
	return travelFacade.location.GetCurrentLocation()
}

func (travelFacade *TravelFacade) GetLocationInfo(lat float64, lng float64) {
	travelFacade.location.GetLocationDetails(lat, lng)
	travelFacade.weather.GetWeatherInfo(lat, lng)
}

func (travelFacade *TravelFacade) GetRoute() []Point {
	return travelFacade.location.GetFullRoute()
}

func (travelFacade *TravelFacade) GetTotalTollAmount(lat float64, lng float64) {
	fmt.Printf("Total Toll Amount: %vn", travelFacade.toll.GetTotalToll(lat, lng))
}

func (travelFacade *TravelFacade) OperateCar() {
	fullRoute := travelFacade.location.GetFullRoute()
	travelFacade.car.StartEngine()

	for i := 1; i <= len(fullRoute); i++ {
		nextMove := travelFacade.location.GetNextMove()
		switch nextMove {
		case "straight":
			travelFacade.car.GoStraight()
		case "left":
			travelFacade.car.GoLeft()
		case "right":
			travelFacade.car.GoRight()
		default:
			travelFacade.car.GoBack()
		}
	}
	travelFacade.car.StopEngine()
}

Demo

Using a facade is simple, just create a new struct of the facade, using the method “NewTravelFacade”. Then use the facade.

// main.go

package main

import "fmt"

func main() {
	travelFacade := NewTravelFacade(10, 10, 20, 30)
	currentLocation := travelFacade.GetCurrentLocation()
	fmt.Printf("Current Latitude: %vn", currentLocation.x)
	fmt.Printf("Current Longitude: %vn", currentLocation.y)

	travelFacade.GetLocationInfo(20, 30)
	travelFacade.GetTotalTollAmount(20, 30)

	travelFacade.OperateCar()
}

Output

Output will be as below-

Current Latitude: -50
Current Longitude: -73

Country: ABC
City: DEF
State: GHI
Zip: 101010

Temperature: 20.7
Precipitation: 1%
Humidity: 73%
Wind: 8 km/h

Total Toll Amount: 69

Start Engine
Go Left: ←
Go Right: →
Go Straight: ↑
Go Right: →
Go Right: →
Go Right: →
Go Straight: ↑
Go Straight: ↑
Go Right: →
Go Right: →
Stop Engine

Source Code

Use the following link to get the source code:

Other Code Implementations

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

Leave a Comment


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