Introduction
The proxy pattern is a very useful design pattern. The basic function is to make sure that a caller has no direct acces to the instance of an object but rather goes through another class which does have that access.
I know this all sounds rather cryptic, so maybe a diagram will help here:
This could be the diagram for a mobility service, albeit very simplified.
So, what do we see here?
- A Drivable interface, with exactly one method: drive(int instance).
- Two classes, Car and Bicycle which implement the Drivable interface.
- A VehicleProxy class which implements the Drivable interface, but which also holds a concrete class, in this case a Car class, which also implements the Drivable interface.
- A concrete VehicleManager class which holds an instance of the VehicleProxy. This class has no knowledge of the exact kind of vehicle that will be driven, nor of any of the implementation details.
This pattern can be very useful in some cases:
- If you want to control access to the concrete class.
- If you want to have conditional access to the concrete classes. In this particular example, the VehicleProxy class could also get the age of the driver as a parameter. If the driver is younger than 18, he or she is not allowed to drive a car, for example.
How to implement this in Go?
I built a very simple implementation of this in Go. We’ll go through it step by step.
First of all, we need to initialize the project. So, open your terminal in an empty directory and type:
go mod init github.com/goproxy
This will initialize the go.mod file. Now open your IDE of choice, in my case Visual Studio Code, and add a main.go to the directory and type:
package main
import "fmt"
Now add the drivable interface to the file:
type Drivable interface {
drive(distance int)
}
Now add the two concrete classes:
type Car struct {
}
type Bicycle struct {
}
In Go, you don’t have use an ‘implements’ keyword or something like that, just implement all the methods of an interface on a struct and the struct implements the interface automatically.
So that is what we will do:
func (vehicle Car) drive(distance int) {
fmt.Printf("I drove %d kilometres in a car", distance)
}
func (vehicle Bicycle) drive(distance int) {
fmt.Printf("I drove %d kilometres on a bicycle", distance)
}
Now implement the VehicleProxy:
type VehicleProxy struct {
realSubject Drivable
}
func (proxy VehicleProxy) drive(distance int) {
proxy.realSubject.drive(distance)
}
This struct has one field: realSubject (which is the more common term in this pattern) which implements the Drivable interface.
We also notice that the VehicleProxy class also implements the Drivable interface.
Now it is time for the final piece in this pattern: the VehicleManager:
type VehicleManager struct {
proxy VehicleProxy
}
func (manager VehicleManager) drive(distance int) {
manager.proxy.drive(distance)
}
As you can see this struct has one field, the proxy for the vehicles.
Now it is time to test this:
func main() {
myVehicle := new(Car)
myProxy := VehicleProxy{realSubject: myVehicle}
myManager := VehicleManager{proxy: myProxy}
myManager.drive(20)
}
Let’s go through this line by line:
- myVehicle is initialized with the type of Car.
- myProxy is initialized with this vehicle. Since realSubject just is the Drivable interface, we can assign a Car to it.
- myManager is initialized with a proxy. I chose to have a concrete class for the proxy, but this could equally well be an interface type.
- The drive method is called on the manager, and the call is being dispatched to the Car struct.
Now type:
go run .
And you should see ‘I drove 20 kilometres in a car’.
That is it. In the future, I hope to think of a more elaborate example than this: I know this is very simple.
What it shows well is Go’s flexibility and simplicity. Even without the normal OOP features, this pattern is relatively painless to implement this pattern.