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:
Example | Source Code Link |
---|---|
Example #1: Travel Plan | GitHub |
Other Code Implementations
Use the following links to check the Facade pattern implementation in other programming languages.