Skip to content

Commit

Permalink
adding the decorator pattern 🔥
Browse files Browse the repository at this point in the history
  • Loading branch information
shubhamzanwar committed Oct 6, 2020
1 parent 185c018 commit 857ccce
Show file tree
Hide file tree
Showing 4 changed files with 203 additions and 0 deletions.
127 changes: 127 additions & 0 deletions 6-decorator/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
# Decorator Pattern 📦

The Decorator pattern is a structural design pattern that is used when we need to _enhance_ the functionality of a given object _without changing it's interface._

In this pattern, we usually have a base/bare-bones class that implements a certain interface. Along with this, we have multiple "wrapper" classes that allow users to enhance the methods of the base class without changing the interface.

_This means that even the "Wrapper" classes implement the same interface_

Let's consider an example to understand this better. We will discuss about a usecase that requires storing user information to a storage system and what happens when we need to encrypt some data before storing. 🗃

### Storage system

Assume that you have a storage system for your websites audience that stores the users' data: `email`, in our case. Being a good developer, you had designed this module to be super generic and it is now being used by multiple teams in your organization. 😎

Everything sounds great? Here's the problem. You have a new requirement - Only your team needs to encrypt the users' data before storing it. 🤯

You obviously cannot modify the existing module. Too many teams are using it and they'll kill you if you do 🙃. You also cannot re-write everything from scratch. You don't have enough time for that and it also isn't the best way of solving this.

You remember a colleague telling you about the decorator pattern and you decide to put it into action 💡. Here's how it goes:

1. You have an interface, `StorageSystem` that contains two methods, `writeInfo` and `readInfo`.
2. The struct you had previously created by implementing `StorageSystem` is `GenericStorageSystem` - Assume that this just stores the information in memory.
3. To fulfill the requirement, you still need some struct to implement `StorageSystem` while also encrypting the data. Yes, I repeat, you can't just create a new struct from scratch. You need to use `GenericStorageSystem`.
4. You decide to create `EncryptedStorageSystem`. It wraps over the `GenericStorageSystem` and implements the `writeInfo` and `readInfo` which call `writeInfo` and `readInfo` of `GenericStorageSystem` respectively _after_ encrypting/decrypting the data.

Voila! You've saved a bunch time and also have a scalable solution. Here, have a cookie for your efforts 🍪

Let's see how this looks in Go code:

```go
type StorageSystem interface {
writeInfo(info string)
readInfo() string
}

type GenericStorageSystem struct {
info string
}

func (g *GenericStorageSystem) writeInfo(info string) {
// write information to file system as is.
fmt.Println("Writing info to the file system: ", info)
g.info = info
}

func (g *GenericStorageSystem) readInfo() string {
// Return info from file system as is.
fmt.Println("Reading info from the file system: ", g.info)
return g.info
}
```

```go
type EncryptedStorageSystem struct {
storageSystem StorageSystem
}

func (e *EncryptedStorageSystem) writeInfo(info string) {
fmt.Println("Encrypting the data")
encryptedInfo := encrypt(info)
e.storageSystem.writeInfo(encryptedInfo)
}

func (e *EncryptedStorageSystem) readInfo() string {
info := e.storageSystem.readInfo()
decryptedInfo := decrypt(info)
fmt.Println("Decrypting info from the file system: ", decryptedInfo)
return decryptedInfo
}
```

Let's also create dummy `encrypt` and `decrypt` functions. These just rotate the string in different directions

```go
func encrypt (info string) string {
return info[1:len(info)] + string(info[0])
}


func decrypt (info string) string {
return string(info[len(info) - 1]) + info[0:len(info)-1]
}
```

Aaaand we're done 💃🏻. Let's check it out in action:

```go
func main() {
info := "[email protected]"
genericStorage := GenericStorageSystem{}

genericStorage.writeInfo(info)
genericStorage.readInfo()

fmt.Println("------------")

encryptedStorage = EncryptedStorage{
storageSystem: genericStorage,
}

encryptedStorage.writeInfo(info)
encryptedStorage.readInfo()
}
```

You should see this in the output:

```text
Writing info to the file system: [email protected]
Reading info from the file system: [email protected]
------------
Encrypting the data
Writing info to the file system: [email protected]
Reading info from the file system: [email protected]
Decrypting info from the file system: [email protected]
```

Notice how the end result in both the cases are similar. You wrote a string, and got back the same string when you requested it. However, in the second case, the data was encrypted while storing and decrypted while reading - The most important part is that all of this is abstracted from the user.

There you have it - The decorator pattern 🎉

Now that you have a good understanding of the decorator pattern, let's discuss why it's a good thing:

- Notice how you did not have to create a new storage from scratch. This saved a bunch of time and also didn't change the API for your storage system. Hence, no unhappy users.
- Also notice that the `EncryptedStorageSystem` implements the `StorageSystem` interface and wraps around any `StorageSystem`; not just `GenericStorageSystem`. This is pretty powerful because you will be able to create multiple recursive wrappers. You can now, even _compress_ your data after encrypting, if you're into that 💪🏽😁

> Note: The Decorator pattern is different from the [Adapter pattern](../4-adapter/README.md) because the adapter pattern converts one struct into another. Hence, there is a change in API. The decorator pattern, on the other hand, simply wraps around the existing struct without changing the API.
29 changes: 29 additions & 0 deletions 6-decorator/encryptedStorageSystem.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package main

import "fmt"

func encrypt(info string) string {
return info[1:len(info)] + string(info[0])
}

func decrypt(info string) string {
return string(info[len(info)-1]) + info[0:len(info)-1]
}

// EncryptedStorageSystem is a decorator that encrypts data before storing it
type EncryptedStorageSystem struct {
storageSystem StorageSystem
}

func (e *EncryptedStorageSystem) writeInfo(info string) {
fmt.Println("Encrypting the data")
encryptedInfo := encrypt(info)
e.storageSystem.writeInfo(encryptedInfo)
}

func (e *EncryptedStorageSystem) readInfo() string {
info := e.storageSystem.readInfo()
decryptedInfo := decrypt(info)
fmt.Println("Decrypting info from the file system: ", decryptedInfo)
return decryptedInfo
}
21 changes: 21 additions & 0 deletions 6-decorator/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package main

import "fmt"

func main() {
info := "[email protected]"

genericStorage := GenericStorageSystem{}

genericStorage.writeInfo(info)
genericStorage.readInfo()

fmt.Println("------------")

encryptedStorage := EncryptedStorageSystem{
storageSystem: &genericStorage,
}

encryptedStorage.writeInfo(info)
encryptedStorage.readInfo()
}
26 changes: 26 additions & 0 deletions 6-decorator/storageSystem.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package main

import "fmt"

// StorageSystem defines the generic interface for any storage system
type StorageSystem interface {
writeInfo(info string)
readInfo() string
}

// GenericStorageSystem is a simple storage that keeps data in memory
type GenericStorageSystem struct {
info string
}

func (g *GenericStorageSystem) writeInfo(info string) {
// write information to file system as is.
fmt.Println("Writing info to the file system: ", info)
g.info = info
}

func (g *GenericStorageSystem) readInfo() string {
// Return info from file system as is.
fmt.Println("Reading info from the file system: ", g.info)
return g.info
}

0 comments on commit 857ccce

Please sign in to comment.