In this series, we are discussing Design Patterns. In the previous articles, we have already discussed the Object Factory Design Pattern, Singleton Design Pattern. and Builder Design Pattern. After a long break of about a month, we are back again to explore and implement the Prototype Design Pattern in Golang.
Prototype Design pattern is a creational design pattern that is used to create a copy of objects while keeping the performance in mind. It specifies the kind of objects to create using prototypical instance and creates new objects by copying this prototype.
Confused by the above statement? Let us go easy. Please note that some times object creation is a costly job. For example, if you fetch a row from Database and keep it as a DAO, it is a costly affair. The general idea of the prototype design pattern is having a set of objects (or even an object) that are created at compile-time, but you are free to create any number of copies at runtime. We just mutate the original object and save it to a new object. It saves us from repetitive object creation. Let us go back to the database example. We fetch a column, and keep it as a DAO. Now for every field, we want to update, we just mutate the DAO, instead of calling the database every time.
Let us take one more example, Suppose you have an object with a large number of fields and you have to create a clone. The simple solution is to instantiate an empty object of the same type, and then iterate over all the fields and copy each field from the original object to the cloned object. Apart from being a tiring process, there is one more catch, sometimes you just know the interface and not the exact struct that is given to you. Under such scenarios determining the exact struct that is implementing the interface and instantiate an object of that type in run time can be a tedious job.
All these tiny problems are easily solved with the help of a prototype design pattern. But before we begin, let us understand the terminology:
- Prototype: It declares an interface for cloning itself.
- Concrete Prototype: It implements an operation for cloning itself.
- Client: Creates a new object by asking the prototype to clone itself.
Implementation
We use this Prototype Design Pattern in Golang when we want to customize how an object is cloned. Let us see two examples of catering to different situations.
For the first scenario, let us build a utility that gives us a different kind of user object as per our needs. To begin with we have a struct user
that has email
and mobile
as it’s two properties. We want to mutate the object, without affecting its original instance. The goal of this whole utility is to generate different user objects without losing the flexibility of customizing them without changing the initial object. In simple words, it is a utility that creates new user object, with the desired changes, just by copying an initial object and then changing the fields of the copied objects, leaving the original object unchanged.
package prototype type User struct { email string mobile string } func NewUser(email string, mobile string) User { return User{ email: email, mobile: mobile, } } // WithEmail creates a copy of User with the provided email func (u User) WithEmail(email string) User { u.email = email return u } // WithPhone creates a copy of User with the provided phone func (u User) WithMobile(mobile string) User { u.mobile = mobile return u }
If you observe closely, you will find the we call the functions WithEmail
and WithUser
from User
(and not *User
). Thus, at the time when the functions are called, a new copy of the user object is made and passed to the functions, and all the mutations happen to the copied object. This all may seem not worth the effort right now, but just imagine how useful it becomes when the object instantiation is a costly affair. In this utility we never instantiate an object, we just copy an already instantiated object and mutate is as per our needs.
Now let us look at the other case (which is inspired from here), the case in which we are given an interface and we don’t exactly know which struct is satisfying that interface. Let us try developing an object mimicking file system. Any file system has files and folders and folders itself contain files and folders. Each file and folder can be represented by a node interface. node interface also has the clone() function.
package main type node interface { print(string) clone() node }
Now let us define the two type of entities, files and folders.
package main import fmt type file struct { name string } func (f *file)print(ind string) { fmt.Println(ind+f.name+"_clone") } func (f *file)clone() node { return &file{name: f.name} }
package main import "fmt" type folder struct { children []node name string } func (f *folder) print(indentation string) { fmt.Println(indentation + f.name + "_clone) for _, i := range f.childrens { i.print(indentation + indentation) } } func (f *folder) clone() node { cloneFolder := &folder{name: f.name} var tempChildrens []node for _, i := range f.childrens { copy := i.clone() tempChildrens = append(tempChildrens, copy) } cloneFolder.childrens = tempChildrens return cloneFolder }
Since both file and folder struct implements the print and clone functions, hence they are of type inode. Also, notice the clone function in both file and folder. The clone function in both of them returns a copy of the respective file or folder. While cloning we append the keyword “_clone” for the name field. Let’s write the main function to test things out.
package main import "fmt" func main() { file1 := &file{name: "File1"} file2 := &file{name: "File2"} file3 := &file{name: "File3"} folder1 := &folder{ childrens: []inode{file1}, name: "Folder1", } folder2 := &folder{ childrens: []inode{folder1, file2, file3}, name: "Folder2", } fmt.Println("\nPrinting hierarchy for Folder2") folder2.print(" ") cloneFolder := folder2.clone() fmt.Println("\nPrinting hierarchy for clone Folder") cloneFolder.print(" ") }
Here we see how we could clone an interface without knowing the underlying type using the Prototype design pattern in Golang.
Golang has an inbuilt mechanism of cloning objects, and thus you would not find a lot of uses of this particular design pattern. However, if you wish to know about the design patterns, this is just one more flavour.
0 Comments