Design Patterns – Prototype
Hi guys! Today I talk about Prototype Design Pattern!
This is a Creational pattern.
Objective
Clone/Copy an existing object hidden the complex to create a new instance.
If we have an object with a particular state (private) and we can clone this object, we need to make a new instance and make the same steps to achieve this particular state in the clone object.
When creating an object include costly operations, private constructor or many business operations: Prototype Design pattern is a good option for us.
Common uses:
- Create a new instance include costly operations
- To achieve the same state in the clone object, we need to do many business operations.
- An API returns us an object with a private constructor, for this reason, we can’t instantiate it. We can model the API allowing clone the returned objects.
Structure
Steps
- Create a IPrototype Interface and specify the Clone method, this method must return an IPrototype
- Create a ConcretePrototype that implements IPrototype Interface.
- ConcretePrototype must have a constructor who receives as parameter a ConcretePrototype.
- Clone method must make a new instance of the current object.
Original approach 🙂
This is a simple example: we have a Person class with these features:
- Constructor makes a complex operation.
- AsignCode() method: executes a complex algorithm
We do the same operations to achieve the same state in both objects:
This approach took 40124 milliseconds.
Now, we implement Prototype pattern!
This approach took 20029 milliseconds!
IClonable C# 😉
C# has a particular Interface that helps us to implement IPrototype pattern to clone objects: IClonable.
We will use this interface and the method: MemberwiseClone() to clone the state of an object.
- Use Iclonable interface in the target class
- Implement Clone method –> Call to built-in method: MemberwiseClone()
- Call Clone() method in the main –> That’s all!
Final example! 😀
For the final example we have the following business requirements:
- We have a Match class when we create a new instance its constructor call an API and inflate it with the result (costly operation).
- The Match class have information about Team A and Team B. In addition, it has a method to predict the probability that Team A wins (apply many business rules to calculate it)
- You can see that Match Class has complex operations in its constructor and complex methods that modify its state, for these reasons, Match class implement Prototype pattern to allows clone an object.
- Finally, we have BasketMatch class, we use this class to retrieve information about Basket match for that, we call a specific EndPoint. This endpoint returns a BasketMatch object, all constructors are PRIVATE but how the API is well designed, BasketMatch implements Prototype pattern! For this reason, we can clone the object but can’t instantiate a new BasketMatch
Match Class:
using System;
using System.Threading;
namespace Prototype.Version4.Model
{
class Match: ICloneable
{
public int Id { get; private set; }
public DateTime SyncDate { get; private set; }
public string TeamA { get; private set; }
public string TeamB { get; private set; }
public int ScoreTeamA { get; private set; }
public int ScoreTeamB { get; private set; }
public float ProbabilityThatTeamAWins { get; private set; }
public Match()
{
CallApi();
ProbabilityThatTeamAWins = CalculateProbabilityThatTeamAWins();
}
private void CallApi()
{
Thread.Sleep(TimeSpan.FromSeconds(10));
Id = 1;
SyncDate = DateTime.Now;
TeamA = "River Plate";
TeamB = "Barcelona F.C";
ScoreTeamA = 0;
ScoreTeamB = 1;
}
private float CalculateProbabilityThatTeamAWins()
{
float probability = 0f;
DoComplexOperationA(ref probability);
DoComplexOperationB(ref probability);
DoComplexOperationC(ref probability);
ApplyNBusinessRules(ref probability);
return probability;
}
private void ApplyNBusinessRules(ref float probability)
{
Thread.Sleep(TimeSpan.FromSeconds(5));
probability = new Random().Next(0, 100) / 100.0f;
}
private void DoComplexOperationC(ref float probability)
{
Thread.Sleep(TimeSpan.FromSeconds(4));
probability = new Random().Next(0, 100) / 100.0f;
}
private void DoComplexOperationB(ref float probability)
{
Thread.Sleep(TimeSpan.FromSeconds(2));
probability = new Random().Next(0, 100) / 100.0f;
}
private void DoComplexOperationA(ref float probability)
{
Thread.Sleep(TimeSpan.FromSeconds(6));
probability = new Random().Next(0, 100) / 100.0f;
}
public string Print()
{
return $"Match Id: { Id } - Team A: { TeamA} - Team B: { TeamB} - Score Team A: { ScoreTeamA} - Score Team B: { ScoreTeamB } - Probability that Team A wins the match: { ProbabilityThatTeamAWins * 100 } %";
}
public object Clone()
{
return MemberwiseClone();
}
}
}
BasketMatch class
using System;
namespace Prototype.BasketApi.Model
{
public class BasketMatch: ICloneable
{
public int Id { get; set; }
public string TeamA { get; set; }
public string TeamB { get; set; }
public int ScoreTeamA { get; set; }
public int ScoreTeamB { get; set; }
private BasketMatch()
{
}
private BasketMatch(int id)
{
Id = id;
}
private BasketMatch(int id, string teamA, string teamB)
: this(id)
{
TeamA = teamA;
TeamB = teamB;
}
internal BasketMatch(int id, string teamA, string teamB, int scoreA, int scoreB)
: this(id, teamA, teamB)
{
ScoreTeamA = scoreA;
ScoreTeamB = scoreB;
}
public string Print()
{
return $"Match Id: { Id } - Team A: { TeamA} - Team B: { TeamB} - Score Team A: { ScoreTeamA} - Score Team B: { ScoreTeamB }";
}
public object Clone()
{
return MemberwiseClone();
}
}
}
This class lives in another assembly, you can see how your constructors are INTERNAL for this reason, we can’t instantiate a new BasketMatch in our main program.
Main Program
That’s all! We can see how the Prototype Pattern can help us in specific situations!
You can see all code in my github!
Big hug!