FlyWeight Design Pattern in Go
Tags: go, design, programming, productivity
Published: 2020-12-18 12:05:24

Hey there!

New week, new design pattern to learn. This week I'm writing about the FlyWeight Design pattern. This pattern is a little bit different from the ones I've written before because I feel like most of it is oriented toward memory conservation on your computing which we pretty much don't care about on our daily basis. However, if you are working with distributed systems, old computers, or enormous database sets, maybe this pattern is for you.

So, let's imagine that you want to work on an MMO game (massive multiplayer online). This would require you to handle a lot of users with their first name, Lastname and username. A lot of this information would be repeated through your dataset, so let's see what we can do.

Let's create a simple struct for our Users with their full name as an attribute:

```golang
type User struct {
	FullName string
}

func NewUser(fullName string) *User {
	return &User{FullName: fullName}
}
```

And we need to initialize some users on our main function:

```golang
	u1 := NewUser("John Doe")
	u2 := NewUser("Jane Doe")
	u3 := NewUser("Jane Smith")
```

This is where my point is beginning to make sense. See, the name Jane and the surname Doe are both repeated and we are storing them both times in our database. If we had a limited resource database this would be a huge problem when 700 Jan-Michael Vincent join our application.

Then, let's make our application memory oriented. To achieve this, we are going to create a new User struct and a string slice to store all the names:

```golang
var allNames []string

type User2 struct {
	names []uint8
}
```

The interesting thing is that now the new User is not going to store a string for the name but a int8 slice. The downside of this implementation is that the New User constructor will require more processing power.

```golang
func NewUser2(fullName string) *User2 {
	getOrAdd := func(s string) uint8 {
		for i := range allNames {
			if allNames[i] == s {
				return uint8(i)
			}
		}
		allNames = append(allNames, s)
		return uint8(len(allNames) - 1)
	}
	result := User2{}
	parts := strings.Split(fullName, " ")
	for _, p := range parts {
		result.names = append(result.names, getOrAdd(p))
	}
	return &result
}
```

This method has an internal function declared which receives a string (our name) and returns us the position on the allNames slice where that name is. If it's not there, it will store it on the last position of the array and give us the position back. Then the constructor will use this internal function to store or retrieve each of the strings on our name (divided by a space).

Every time we need a New User's Full Name we will use a method that will reconstruct it's name:

```golang
func (u *User2) FullName() string {
	var parts []string
	for _, id := range u.names {
		parts = append(parts, allNames[id])
	}
	return strings.Join(parts, " ")
}
```

Now let's go to our main function to test what we've created:

```golang
	john := NewUser("John Doe")
	jane := NewUser("Jane Doe")
	alsoJane := NewUser("Jane Smith")
	fmt.Println(john.FullName)
	fmt.Println(jane.FullName)
	fmt.Println(alsoJane.FullName)
	fmt.Println("Memory taken by users:",
		len([]byte(john.FullName))+
			len([]byte(jane.FullName))+
			len([]byte(alsoJane.FullName)))

	john2 := NewUser2("John Doe")
	jane2 := NewUser2("Jane Doe")
	alsoJane2 := NewUser2("Jane Smith")
	totalMem := 0
	for _, a := range allNames {
		totalMem += len([]byte(a))
	}

	totalMem += len(john2.names)
	totalMem += len(jane2.names)
	totalMem += len(alsoJane2.names)
	fmt.Println("Memory taken by users2", totalMem)
```

We are creating 3 Users and 3 Users2 and we are printing how much memory do each of the structs consume:

```bash
go run main.go
John Doe
Jane Doe
Jane Smith
Memory taken by users: 26
Memory taken by users2 22
```

We are saving 4 bytes on this example. It ain't much but it's ~~honest work~~ memory saved on 3 strings. When names begin to get redundant on your database these 4 bytes will multiply exponentially saving a lot of memory.

As I stated before, this Design Pattern is a little bit different from the ones I wrote of before. It's memory oriented instead of Behaviour oriented so use it when in need instead of just pushing random patterns all over your applications.

That's it for this week. Hope you liked it.
Happy coding