The Creational Pattern of the Life ... and the Music :-)
In the last article about Patterns Design we have saw two creational design patterns, the "Factory Method" and the "Abstract Factory Class", now in this article I talk about another important creational pattern called "Prototype".
"Prototype" is a creational design pattern that lets us copy existing objects without making our code dependent on the concrete classes (1.).
Suppose we have an object and we want to make an exact copy of it.
How to do? First we should create a new object of the same class. Then we have to go through all the fields of the original object and copy their values to the new object. But not all objects can be copied in this way because some of the object's fields may be private and not visible from outside the object itself.
So what can be the solution?
The solution is to delegate the problem of creating the object to the object itself, this operation is called "cloning", the clone method creates a new instance with all the property values. An object that supports the cloning operation is called "prototype".
What is the reason for the name given to this pattern?
The name "prototype" is due to the technique of taking an object, the prototype in fact, to give us another of its same class.
In real life a much closer analogy to this pattern is the biological process of "Cell Mitosis".
The mitotic division of a cell, resulting in two identical cells, is an example of a prototype that plays an active role in copying itself and thus, demonstrates the Prototype pattern. When a cell splits, two cells of identical genotype result. In other words, the cell clones itself.
figure 1 : Cell Mitosis class Diagram.
Now let’s see the basic implementation of this pattern:
We declare an abstract base class that specifies a pure virtual "clone" method and maintain a dictionary of all concrete "cloneable" derived classes. Any class that needs a "polymorphic constructor" capability must derive from the abstract base class, register its prototype instance, and implement the "clone" operation.
The client then, instead of writing code that invokes the "new" operator on a hardwired class name, calls a "clone" operation on the abstract base class, providing a string or enumerated data type that identifies the particular derived and concrete class desired.
The class diagram is the follows:
figure 2 : Prototype Pattern class diagram.
This pattern is made up of the following participants:
"Prototype" : declare an interface for cloning.
"ConcretePrototype" : implements a self-cloning operation.
"PrototypeRegistry" : provides an easy way to access frequently-used prototypes. It stores a set of pre-built objects that are ready to be copied. The simplest prototype registry is a hash map of prototypes with the name as key.
"Client" : invokes the cloning of the object through the "PrototypeRegistry".
When creating the clone of the object, we must pay attention to the creation of nested objects. A class may contain references to other classes within it, therefore the cloning of the main object must also clone all the other objects within it. Cloning the entire tree of objects generates a clone called "deep-clone" as it is a copy of all the objects present. If cloning is limited only to the main "container" object, then the same references to the secondary objects will be kept in the clone, in this case we speak of "shallow-clone".
I take as the first real example of this pattern a project that I followed about twenty-five years ago. At that time I was in charge of developing software for electronic musical keyboards and also tools for musicians. I had to develop a sheet music editor for use on the computer. Composed music could be exported (in MIDI format) to the musical keyboard for playback.
At the time I had recently read the first edition of "Design Patterns" by "Gang of Four" (1995), where it was described as an example of application of the prototype pattern the creation of an editor for music scores (by customizing a general framework for graphical editors and adding new objects that represent notes, rests, and staves).
It was practically my case, I too had decided to use "Unidraw" a framework that was then widely used to develop different types of editors (2.).
"Unidraw" was a framework for creating graphical editors in domains such as technical and artistic drawing, music composition, and circuit design. The "Unidraw" architecture simplified the construction of these editors by providing programming abstractions that are common?across domains. "Unidraw" defined four basic abstractions: "components" for encapsulate the appearance and behavior of objects, "tools" for support direct manipulation of components, "commands" for define operations on components, and "external representations" for define the mapping between components and the file formatted generated by the editor.
Let's see the class diagram below:
figure 3 : class diagram of "Music Scores Editor".
The editor framework needed to have a tool palette to add these musical objects to the score. The palette also had to include tools for selecting, moving and otherwise manipulating musical objects. Users, for example, can use the move tool to move a note up or down on the staff, thereby changing its pitch.
The framework provided an abstract "Graphic" class for graphics components, such as notes and staves. It also provided an abstract "Tool" class to define tools such as those in the palette. The framework also provided a "GraphicTool" subclass for tools that create instances of graphic objects and add them to the document. The classes for notes and staves are specific to our application, but the "GraphicTool" class belongs to the framework.
So "GraphicTool" doesn't know how to create instances of our music classes to add to the score.
The solution was to have "GraphicTool" create a new graphic object by copying or "cloning" an instance of a graphic subclass. This instance was therefore a "prototype". If all Graphic subclasses support a cloning operation, "GraphicTool" can clone any type of Graphic.
So, in the music editor, each tool to create a musical object was an instance of "GraphicTool" initialized with a different prototype. Each instance of "GraphicTool" provided a musical object by cloning its prototype and adding the clone to the score.
The main features for this music scores editor were:
-) It was possible to add notes and rests of full measure, a half, a quarter, an eighth, a sixteenth and a thirty-second;
-) It was possible to create scores in the key of Violin, Bass, Alto;
-) It was possible to drag notes to change their pitch or position;
-) It was possible to listen to our compositions in MIDI playback;
-) It was possible to export our scores as MIDI format.
Unfortunately after so many years I have not kept neither the UML documentation nor the source code of the project. So I only drew a class diagram based on what just said (figure 3).
Instead, in order to capture the essence of the project, I will refer to an example I made to introduce the topic to my team. This is an example that works for geometric shapes.
The problem is that of the famous "Paint" software tool, and that is we have a sheet and we want to be able to insert geometric shapes. A "ShapeCache" allows us to take the shape we want and to put it on the sheet.
In figure 4 we can see the related class diagram.
figure 4 : class diagram of Shapes Palette.
below we can see a simplified snippet of the kotlin code: on the left side we see the realization of the prototype pattern for the shapes and on the right side we see the unit test to try it out.
figure 5 : prototype pattern for shapes, on the left the code on the right the unit test.
Another nice implementation of the "Prototype Pattern" can be found in the "Event Handling Model" which I have already talked about in one of my previous articles (3.).
In that article I showed you a real implementation in C ++ of the "Event Handling Framework" that I created with the consultant Carlo Pescio for a software platform of cardiovascular exercise equipment.?
in figure 6 we can see the class diagram of "Event Handling framework", and here is the link to repository sources:
领英推荐
Figure 6: EventHandling class diagram.
If you want an in-depth description of the framework, please refer to (3.). Now I will only make a brief and simplified description with the intention of highlighting the presence of the "Prototype Pattern".
So we will refer to a simplified version of the "EventHandling" model that is easier to understand. I have eliminated all the part related to the Invoker map, obviously this means that the structure loses significantly in quality. The "EventSink" no longer has the event management functions that start automatically on the "HandleEvent()", but instead the management of the events is done with a switch-case on the event tag. Before this switch-case is the call to "getEvent()" which takes the event from the queue. In figure 7 we see the class diagram of this simplified version.
Figure 7: "EventHandling" semplified class diagram.
let's see the main components:
Event?is the base class for all the concrete events. Each event is defined by a tag (a number). Event is an abstract class: Clone is pure virtual.?Clone?as in the "Prototype Pattern" creates (and returns) a clone of the object on which it is called. Must be redefined in each derived class, at each inheritance level.
SimpleEvent?is a simple event class, having no data other than the tag. Useful whenever the event has no need to carry additional data. Clone creates and returns a clone of the object.
EventSink is the base class for the receiver of events, that is, all the classes that want to receive events must derive from EventSink. When an EventSource dispatches an Event which is subscribed from a specific EventSink, the source clones the event and calls the Notify method of the sink. The default behavior of the Notify method is to add the event into the EventQueue e associated with the EventSink. Derived classes can pop the events from the queue by calling GetEvent. Note that GetEvent returns an auto_ptr<Event > since the receiver is ultimately responsible to delete the received event (this framework was developed several years ago, in fact auto_ptr?was deprecated in favor of? unique_ptr) .
EventQueue?a simple FIFO queue containing pointers to Events.
EventSource?is the base class for all the sources of Events. The EventSource maintains a map of all the subscribed events, from all the event sinks. The EventSink subscribe to an event using the Subscribe function. Before destruction, they must also unsubscribe using the?UnSubscribe?function. To send an event to all the subscribers, the EventSource (or an object of derived class, or in some cases another calling class) uses the Dispatch function.?The Subscribe(Tag, EventSink)?method add the pair (tag, EventSink) to the (multi)map of the subscribed events. Whenever an event with the specified tag is dispatched, the specified EventSink will be notified.?The method Dispatch(Event)?send the event to all the event sinks that have subscribed its tag. The UnSubscribe(EventSink)?method removes the event sink from the dispatching map. This function must be called before the event sink is destroyed. Otherwise, an invalid pointer will remain in the map, with undefined run-time behavior (most likely a crash).
Tag?a tag is, in the current release, just an unsigned int (32 bit).
here is the source code (written in C++) of this simplified version of "EventHandling":
Figure 8: Tag, Event, SimpleEvent source code.
Figure 9: EventSink source code.
Figure 10: EventSource source code.
Below is the "UnitTest" of the simplified framework (figure 11). An EventSource is created and also two concrete EventSink: FirstEventSynk and SecondEventSynk are created and started. FirstEventSynk subscribes to events Tag10 and Tag30 while SecondEventSink subscribes to events Tag1 and Tag2. The EventSource dispatches a sequence of events. These events arrive at FirstEventSynk and SecondEventSynk who have subscribed to them. The time order of arrival is logged.
Figure 11: unit test for Simplified EventHandling.
This last example ends our discussion about "Prototype Pattern".
But let's make one last consideration. In the first two examples we have seen, that of the Music Scores Editor and that of Geometric Shapes, prototype presented itself exactly like the Pattern Design identified and cataloged by the Gang of Four.
Instead in the last example we did, that of the EventHandling framework, prototype takes a different aspect. In this case the PrototypeRegistry part is missing and the prototype looks like a transformation technique to avoid creational dependencies.
In "Systematic Object Oriented Design" (SysOOD) by Carlo Pescio (4.) prototype is represented as one of the transformation techniques to ensure compliance with the third rule of SysOOD: "avoid creational dependencies from concrete classes". Obviously, if an object knows how to clone itself, the problem of knowing the right concrete class disappears.
The SysOOD is a systematic approach to design. It is based on design rules and transformation techniques. The SysOOD does not exclude either the traditional methodology based on principles or the one based on patterns, but focuses above all on transformation rules, derived from the study of successful solutions to real and verified problems that can be repeated in general. The four rules of SysOOD are simple, objectively verifiable and free from the ambiguities of the great "Design Principles". Nonetheless, they capture a large set of case studies, and a design that respects them has a good chance of guaranteeing those famous properties of extensibility, reusability, maintainability that are always talked about when it comes to Object Oriented Design.
I invite you to read the articles (by Carlo Pescio) about Systematic Object Oriented Design (4.) because SysOOD is really interesting and always current.
Finally one last consideration, there are now many Object Oriented Programming Languages that have the Clonable interface and therefore prototype tends to become more and more an idiom.
Let's remember that the main difference between "patterns" and "idioms" to be one of size. An idiom is something small, like "use an interface like "clonable" or "observable" while Patterns tend to be larger. The smallness of idioms does mean that they're more often language specific, unlike patterns that are language agnostic.
In the next article we will finish the discussion about creational patterns with Singleton and Builder.
I remind you my newsletter?"Sw Design & Clean Architecture": https://lnkd.in/eUzYBuEX?where you can find my previous articles and where you can register, if you have not already done, so to be notified every time I publish new articles.
thanks for reading my article, and I hope you have found the topic useful,
Feel free to leave any feedback.
Your feedback is very appreciated.
?Thanks again.
Stefano
References:
1.?Gamma, Helm, Johnson, Vlissides, “Design Patterns” Addison Wesley (2° Edition October 2002).
2.?J.Vlissides, M.Linton, “A Framework for Building Domain-Specific Graphical Editors”,?Stanford University, 1990 ?https://dl.acm.org/doi/pdf/10.1145/98188.98197
3.?S.Santilli: "Stability for software components: the Stable Dependency Principle" : https://www.dhirubhai.net/pulse/stability-software-components-stable-dependency-stefano-santilli/.?
4. C. Pescio, "Systematic Object Oriented Design" https://www.eptacom.net/pubblicazioni/pub_eng/sysdes.html