Decorator vs. Proxy

06/02/20202 Min Read — In Design Pattern

Recently I have been playing around with couple essential design patterns and want to write down my thoughts and compare them.

The two patterns (Decorator and Proxy) are similar but not identical.
Let's talk about what they are exactly one by one.

Decorator

Cite Wiki

The decorator pattern is a design pattern that allows behavior to be added to an individual object, dynamically, without affecting the behavior of other objects from the same class.

Simply put, we use decorator to enhance the interface.

Image we have a class called Car, which has a method Drive

type Car interface {
  Drive()
}

type Sedan struct {}

func (x *Sedan) Drive() {
  // ...
}

type SportsCar struct {}

func (s *SportsCar) Drive() {
  // ...
}

But, what if we want to enhance Car with one more method called SwitchToSportsMode ?

By following Open-closed principle, we create a interface enhancer (decorator).

// Decorator
type CarWithSportsMode struct {
  car Car
  hasSportsMode bool
}

func (c *CarWithSportsMode) Drive() {
  // ...
}

func (c *CarWithSportsMode) SwitchToSportsMode() {
  if c.hasSportsMode {
    // ...
  }
}

Then we can easily decorate the original classes, the following is how we gonna use it

func main() {
  sedan := Sedan{}
  sportsCar := SportsCar{}

  // Drive it
  sedan.Drive()
  sportsCar.Drive()

  // Enhanced cars
  sedanWithSportsMode := CarWithSportsMode{&sedan, false}
  sportsCarWithSportsMode := CarWithSportsMode{&sportsCar, true}

  // Switch to sports-mode
  sedanWithSportsMode.SwitchToSportsMode()
  sportsCarWithSportsMode.SwitchToSportsMode()
}

This simple example shows us we can leverage decorator to add more responsibilities to existing interface without modifying it. It's pretty neat !

Proxy

Alright, let's get to Proxy design pattern. As usual, cite Wiki first

A proxy is a wrapper or agent object that is being called by the client to access the real serving object behind the scenes. Use of the proxy can simply be forwarding to the real object, or can provide additional logic

Wiki already has clear explanation. Let's just go straight to example.

Following the case above: image we have Car and Driver classes, but now we don't want anyone with any ages to drive it.

type Car interface {
  Drive()
}

type Driver struct {
  Age int
}

By applying proxy design pattern, we can hide original Drive implementation and add our custom logics on top of it.

type CarProxy struct {
  car Car
  driver *Driver
}

func (c *CarProxy) Drive() {
  if c.driver.Age <= 20 {
    panic("Driver is too young to drive")
  }
  c.car.Drive()
}

// Using factory to generate new proxy instance
func NewCarProxy(driver *Driver) {
  return &CarProxy{Car{}, driver}
}

So main function looks something like

func main() {
  youngDriver := Driver{18}  
  car := NewCarProxy(&youngDriver)
  car.Drive()
}

Summary

decorator dynamically adds more functionalities to the interface. On the other hand, proxy is about access control to the interface. (idea from what we know about proxy in networking space).

© 2020 by Warren. All rights reserved.
Last build: 11/28/2021
;