Learn about the Prototype Design Pattern in .NET C#
Prototype in English
In this article we will discuss the Prototype Design Pattern in .NET C#
However, let’s first explore the meaning of the word Prototype in English.
▶ According to dictionary.cambridge.org:
▶ According to merriam-webster.com:
As you can see, prototyping is mainly about creating a copy of something to be then extended.
Prototype Design Pattern Definition
The Prototype Design Pattern is one of the creational design patterns. It is mainly concerned about creating a new object by copying an already existing one without missing any of its encapsulated internal details and at the same time without depending on the class structure.
Wow, a big definition, right? Let me simplify it for you.
When you use any diagram builder software tools, you always look for the feature of copying one of the diagram shapes you had already added.
Using this feature, you expect the following:
You can duplicate a diagram shape.
Then apply minor changes like changing the color without having to configure all the shape properties from scratch.
The color of the main shape you copied from should not be affected by the new color you set to the duplicate shape.
All what you expected is actually what the Prototype Design Pattern is about. The diagram shape you are copying or duplicating is actually an object created from a class defined in the software code.
Now, to enable the feature you are looking for and to make it work as you expected, this what should happen:
The software should be able to copy the source diagram shape object.
The new copy object should be identical to the source object, yet a totally separate object which is totally disconnected from the source object.
Changing a property or a setting on the copy object should not have any effect on the source object.
Is it that all? Actually no.
As you know, in the software world having something working is not enough. We should always pay attention to other factors like readability, maintainability, extensibility,… and some other goals. One of these goals is to make sure our code is loosely coupled and the least dependent on other modules or classes.
Therefore, one of the goals the Prototype Design Pattern is concerned about is to make sure we can achieve copying an object without actually depending on its class definition.
Now, you might ask:
This is not right. Actually my module already knows and depends on the object class, otherwise, how could my module use this object in the first place?!!
You are partially right. Your module knows what the object class exposes to the outer world, but this is not all.
The class which defines this object could have other private, or internal, or protected members which your module is totally not aware of.
Based on this fact, we can conclude that your module can’t actually handle the copying task itself as it doesn’t have all the info required. What should actually happen here is to delegate the copying task to the object class itself as it is fully aware of its internals.
Not convinced? let me show you.
Hidden Fields Example
Let’s assume that we have a Person class defined as follows:
As you can notice, all members are public except for the private separator field which would be used to evaluate the FullName property.
Now, let’s say that at some module we would have an instance object of the Person class and we want to copy it as follows:
As you can see, on line 4 where we are trying to copy the object, we don’t know what to pass for the separator as we don’t have access to the one already set for the original ahmedTarek object.
This means that we can’t handle this copying task in our module.
Nested Objects Example
Let’s assume that we have a Node class defined as follows:
Now let’s create a series of nested nodes as follows:
Running this should end up with the following result:
Now we want to create a copy of node1. Someone might think that the right way to do it is as follows:
But actually running this would end up with this:
This is wrong as the new copy of Node 1 is referring to Node 2, not a copy of Node 2, and so on…
To fix this, we need to follow a more complex approach. We need to create a copy of each node and make everyone referring to the next copy.
Following this, we should do something like this:
And running this we would get the following result:
Now, it is working fine but we can notice that the code tends to be complex.
Prototype Design Pattern Comes to the Rescue
As we now understand, copying an object on a separate module could be either impossible due to the existence of hidden members or possible but complex. The Prototype Design Pattern offers the solution.
In .NET C#, there is already an implementation of the pattern represented into the ICloneable interface. If we extend our classes by implementing this interface, we would have a public object Clone() method to implement.
However this approach already exists and supported by the .NET framework itself, I have some concerns about it. But, let me walk you through using this approach first and then we can discuss my concerns.
Using ICloneable
In this section we are going to use ICloneable with the Hidden Fields and the Nested Objects examples to see if it is actually going to work or not.
Using ICloneable With Hidden Fields Example
Let’s get back to our Person class example. However, this time, it would implement ICloneable as follows:
See, now on line 18 we are creating a new instance of the Person class and we have access to all the private fields. It is easy.
Now, using this would be easy as follows:
And running this would end up with this result:
See, it is working as charm.
Using ICloneable With Nested Objects Example
Let’s get back to our Node class example. However, this time, it would implement ICloneable as follows:
See, now on line 28 we are creating a new instance of the Node class and we are using the Clone method of the Next node to copy it as well. It is easy.
Note: A problem here is that the way we are naming the copy of each node is hardcoded and encapsulated inside the Node class itself. This is something that we would fix later, so stay tuned.
Now, using this would be easy as follows:
And running this would end up with this result:
See, it is working as charm.
My Concerns About ICloneable
Although using ICloneable seems to work, I believe that it has some serious problems.
Immutable Copy
ICloneable provides us with a public object Clone() method which we can use for copying an object. But, what if the object is immutable? in other words, what if even the public properties of the object can not be set after the object is created?
In this case, the object we got back from calling the Clone method could not be manipulated by any means which is useless in most of the cases. Let’s think about it.
When we are trying to copy an object, most probably we are doing this because we need to have an exact copy of an existing object and then follow it with some updates to the copy, right? Now, if I tell you that the copy you got can not be updated, in this case, it is worthless.
The only case where using ICloneable would be useful if you just need an exact copy without following it with any updates.
Returning Object
As you noticed, ICloneable provides us with a public object Clone() method which returns object, not Person or Node. That’s why we needed to cast the returned object from the Clone method to Person or Node.
It is not a big deal actually if you are doing it for few times but it would be a problem if you are doing this too many times too frequently. This would impact the overall performance.
The Other Way
First, let me highlight that I believe copying an object most probably should be a class-specific task. This is because every class would define its own members and also define which of these members is immutable and which is not.
Having that said, let’s now show you another ways of handling copying objects.
Better Solution For Hidden Fields Example
Back to our Person class:
Notice that FirstName and LastName are both immutable.
Let’s say that the Person class would allow itself to be copied. In this case the Person class should define a Clone method that would return a copy of the current Person object. However, just returning an immutable object might be useless to the caller module as it needs to apply some updates on the copied object.
To allow this, we can do something like this:
See what we did here? we added a Clone method where the caller has an opportunity to override any of the properties. Then, the returned object would still be immutable.
Additionally, the Clone method now returns a Person, not an Object.
Furthermore, we added a simpler parameter-less Clone method which would return an exact copy without any overrides.
Now, using this would be as follows:
And running this would end up with this:
Tremendous, working like charm.
Better Solution For Nested Objects Example
Back to our Node class:
So, following the same concept as before, we can do something like this:
So, as you can notice here, the caller of the Clone method would have more than one option; either to override or not.
Additionally, now the logic of setting the name of the copied Node is delegated to the caller, not the Node class itself.
Now, running this would end up with this:
Great, right?
Final Thoughts
In this article we discussed the Prototype Design Pattern in .NET C#. We showed some examples and analyzed them.
What I want to mention here is that one of the common usages of the Prototype Design Pattern is in the Builder Design Pattern itself. If you want to know more about this, you can read my article Builder Design Pattern in .NET C#.
Someone might argue that following the enhanced implementation provided into this article is not providing any abstracted layer which different system modules can depend on. However, my reply would be that we can still abstract the different Clone methods we provided. I didn’t do it here to avoid distractions but for sure it could be done.
Now you understand what the Prototype Design Pattern is about. However, this is not the end of the story.
You would need to search the internet for more articles and tutorials about the Prototype Design Pattern and its usages. This would help you understand it more.
Finally, I hope you found reading this article as interesting as I found writing it.
Comentários