Proxy Pattern in Go

Proxy Pattern in Go:

 

Hey there!

In this week’s post, I’m bringing you the proxy design pattern.

Proxies may be used for a lot of things, such as communication, logging, caching, etc. So in this post, I’m going to show you two implementations of it.

Protection Proxy

Let’s assume you have a car struct and a Driven interface that implements a Drive() function. Nothing too fancy for this example, but it will make a click in the end:

type Driven interface {
    Drive()
}

type Car struct{}

func (c *Car) Drive() {
    fmt.Println("Car is being driven")
}

The Car struct is implementing the Driven interface with the Drive() function.

So now you’d be able to just instantiate a car and make it drive regardless of the driver’s condition. So for example, if our Car’s driver was underage, there’d be nothing stopping him from driving the car. And that’s a big ‘no no’ where I come from.

Let’s assume there is a Driver struct as well with just an Age attribute:

type Driver struct {
    Age int
}

So Let’s create a CarProxy and a constructor for this:

type CarProxy struct {
    car    Car
    driver *Driver
}

func NewCarProxy(driver *Driver) *CarProxy {
    return &CarProxy{Car{}, driver}
}

The CarProxy will have a Car and a Driver as it’s attributes and will receive the driver as a parameter. Since cars are cars and we don’t care that much about them for this example, we are going to create a generic car for the car proxy.

So now we need to implement the Driven interface for the CarProxy and we are going to restrict the user from being underage to use this method:

func (c *CarProxy) Drive() {
    if c.driver.Age >= 16 {
        c.car.Drive()
    } else {
        fmt.Println("Driver too young!")
    }
}

Let’s test the results in the main function:

    car := NewCarProxy(&Driver{12})
    car.Drive()
go run main.go
Driver too young!

But if we change the Driver’s age to 22 for example

go run main.go
Car is being driven

As you can see we didn’t have to add things to the car struct in order to get the restrictions we needed for it. Instead, we just applied those restrictions to an upper layer (or proxy) to make these modifications.

Virtual Proxy

For this example let’s assume you want to create an Image Drawer for different types of formats.

Then let’s create an Image interface that will have a Draw() signature:

type Image interface {
    Draw()
}

Then we can create a struct for a Bitmap image which will be constructed from a filename:

type Bitmap struct {
    filename string
}

func NewBitmap(filename string) *Bitmap {
    fmt.Println("Loading image from", filename)
    return &Bitmap{filename: filename}
}

func (b *Bitmap) Draw() {
    fmt.Println("Drawing image", b.filename)
}

func DrawImage(image Image) {
    fmt.Println("About to draw the image")
    image.Draw()
    fmt.Println("Done drawing the image")
}

Again, nothing fancy. I also added a DrawImage method which would show the progress of the image while being drawn.

And this would work just fine:

    bmp := NewBitmap("demo.png")
    DrawImage(bmp)
go run main.go

Loading image from demo.png
About to draw the image
Drawing image demo.png
Done drawing the image

But there would be an issue if, for example, we used the constructor without storing it in a variable:

    _ = NewBitmap("demo.png")
Loading image from demo.png

Our constructor would be lying to us! And that’s also a big ‘no no’ from where I come from.

So let’s use a proxy for this issue. To be more specific, a Virtual Proxy.

type LazyBitmap struct {
    filename string
    bitmap   *Bitmap
}

func NewLazyBitmap(filename string) *LazyBitmap {
    return &LazyBitmap{filename: filename}
}

Our LazyBitmap struct will store the filename and a bitmap but will only receive the filename while instantiated leaving the bitmap as nil attribute.

This is because we don’t want the bitmap to be created before being rendered! So Let’s render it when we actually need it to be:

func (l *LazyBitmap) Draw() {
    if l.bitmap == nil {
        l.bitmap = NewBitmap(l.filename)
    }
    l.bitmap.Draw()
}

This way we won’t be loading infinite instances of the bitmap. It will mostly work as a singleton attribute.

    bmp := NewLazyBitmap("demo.png")
    DrawImage(bmp)

We now instantiate the bitmap as a LazyBitmap and we can still use the DrawImage() method because it will call the bitmap’s Draw() method on the inside. However this time the result will be a little bit different:

go run main.go                                                                           

About to draw the image
Loading image from demo.png
Drawing image demo.png
Done drawing the image

As you can see. The image is now loaded after the Draw method is called and not before, which is what we wanted.

from Tumblr https://generouspiratequeen.tumblr.com/post/637374573609910272

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s