Design Patterns in action : mix together "Observer","Proxy" and "Facade" and here is a very useful Android API to read/write on Remote Dictionary.
After so many principles, the time has finally come to talk about "Design Patterns". We will have fun with the code and we will create some very cool things.
But before starting to have fun with the code we must make a brief introduction to the subject.
Let's start with the first question, What's a Design Pattern?
Design patterns are typical solutions to commonly occurring problems in software design. They are like pre-made blueprints that we can customize to solve a recurring design problem in our code. We can’t just find a pattern and copy it into our program, the way we can with off-the-shelf functions or libraries. The pattern is not a specific piece of code, but a general concept for solving a particular problem. We can follow the pattern details and implement a solution that suits the realities of our own program.
Patterns are often confused with algorithms, because both concepts describe typical solutions to some known problems. While an algorithm always defines a clear set of actions that can achieve some goal, a pattern is a more high-level description of a solution. The code of the same pattern applied to two different programs may be different.
An analogy to an algorithm is a cooking recipe: both have clear steps to achieve a goal. Instead a pattern is more like a blueprint: we can see what the result and its features are, but the exact order of implementation is up to us.
Here is an excellent schematization of what a pattern is (I stole this image from a course presented by Carlo Pescio about fifteen years ago but still actual).
Figure 1 : pattern conceptual reppresentation.
Figure 1 shows that a Pattern is a (tested) Solution to a (recurring) Problem within a (significant) Context.
And now let's move on to the second question: Who did invent "Design Patterns"?
The concept of patterns was first described in architecture, by Christopher Alexander (Oxford University) in a book titled? "A Pattern Language: Towns, Buildings, Construction". The book describes a "language" for designing the urban environment. The units of this language are patterns. They may describe how high windows should be, how many floors a building should have, how large green areas in a neighborhood are supposed to be, and so on.
The idea was taken up by four authors: Erich Gamma, John Vlissides, Ralph Johnson, and Richard Helm. In 1994, they published "Design Patterns: Elements of Reusable Object-Oriented Software" (1.), in which they applied the concept of design patterns to programming. The book contained 23 patterns that solved various object-oriented design problems and became a bestseller very quickly. Because of its long name, people started to call it "the book by the gang of four" which was shortened again to "the GoF book". Since then, many of other object-oriented patterns have been discovered (2.), (3.), (4.).
another interesting question we need to answer is : "What is the difference between SOLID principles and design patterns".
When I explained the principles in previous articles, I followed a traditional academic approach: I explained?the principles and then I showed some application as an example. It is a highly inductive learning process. The use of patterns instead is an approach to design radically opposite to the traditional one, little emphasis on big principles, instead a lot of emphasis on problem solving. Application examples are shown. This approach is based on deduction and induction learning process.
I recall that the inductive process consists to start from individual particular cases to try to establish a universal law and is opposed to the deductive process (also defined as Aristotelic or academic) which instead proceeds from the universal (principles) to the particular (application).
And finally the last question before we start having fun with code: " Why Should I Learn Patterns? ".
We need to learn patterns for two reasons, the first is that patterns capture knowledge of experts and make it accessible to everyone (there are catalogues of models to inspire us to find solutions), the second is that patterns extend the vocabulary with which one communicates.
In fact, even if the first essential step is to learn to recognize them, choose them and use them, the greatest advantage comes when the patterns become a communication tool.
Design patterns define a common language that we and our team can use to communicate more efficiently. We can say, “Oh, just use a Singleton for that,” and everyone will understand the idea behind our suggestion. No need to explain what a singleton is if you know the pattern and its name.
And now let’s go from theory to practice, I would like to start trying to solve a real problem by using Patterns: "I want to develop an Android SDK that allows us to access in a simple and natural way to a remote key-value dictionary".
This is a problem I had about a decade ago when I was leading my team to develop our Android AOSP platform and software application for a line of cardiovascular exercise equipment.
We needed remote dictionaries of heterogeneous and thematic data (i.e., one dictionary for "user data", one for "exercise data", one for "equipment data", etc.).
External applications needed?to be able to access these remote dictionaries (created on another process) to read and update data and to be informed of the data modification without having to periodically check the data itself with polling.
So, we made an application that created these dictionaries at system startup. It was an application developed with system privileges because it had to be transparent to the "Android Low Memory Killer" module and had to be sensitive to some events that Android only notified applications signed with the platform signature.
Other applications, such as those of training programs (eg "Weight Loss" or "Constant Pulse Rate") needed to be able to read data from the “User Dictionary", such as the user's weight and age to set loads of the equipment. (e.g. speed and incline) appropriately.
The "Weight Loss" application must also be able to update some data in the “Exercise dictionary" such as calories consumed during exercise (because only this application knows the formulas and data to calculate calories), instead “GUI” application needs to read the calorie value to show it.
Also as an example, the "Weight Loss" application needed to be able to observe changes of “Exercise Duration” from the "Exercise Dictionary" to decide when to stop. The GUI application, on the other hand, had to be able to modify the duration of the exercise when the user plays on the corresponding buttons.
So the first step was to create remote dictionaries with these features.
To create remote key-value dictionaries with heterogeneous values we used the Android "ContentProvider" framework.
We had to redefine the "Query", "Insert", "Update" and "Delete" methods of the "ContentProvider" class to make them work on an internal hashmap that was the real dictionary. This is a HashMap of "Object" (or "Any" in Kotlin language) with a key string. One of the most successful choices was to use "MatrixCursor" as the return cursor of the query method. The "MatrixCursor" is a Cursor implementation that stores rows as an array of objects identified by its name (the key of the object in the dictionary). Android allows through the "Content Resolver" mechanism the read and write access to the content provider data from any external application. So here are the remote dictionaries. We called this class IMCP which stands for In Memory Content Provider, see Figure 2.
Figure 2: IMCP class, implementation of heterogeneous data dictionaries accessible from external applications.
The dictionaries must be declared in the application manifest with their class name and access authorization URI, as we can see in Figure 3.
Figure 3: Dictionary for "User" (UserIMCP) and for "Exercise" (ExerciseIMCP).
What was still not good was the way to access these dictionaries, it was the grammar of the "Content Provider" and "Content Resolver". It is a grammar too close to the world of databases and in fact it exposes concepts such as projection and selection. Instead, we were looking for a simpler, more natural and more efficient way to use these dictionaries.
We decided to use the "GoF" patterns extensively and in the end, we obtained an excellent result in terms of efficiency and simplicity. We had created a class to access the remote dictionary which we called "IMCPProxy". "IMCPProxy" stands for "In Memory Content Provider Proxy". Figure 4 shows the code.
Figure 4: IMCPProxy class, the proxy class for IMCP dictionary.
In Figure 5 we can see two concrete implementations of proxy respectively to access the "UserIMCP" and the "ExerciseIMCP" dictionaries.
Figure 5: Exercise and User are two concrete proxies to IMCP dictionaries.
Obviously the description I am going to give now about the design and patterns choices is only to show you how useful the approach of designing with patterns can be.
We have not yet listed and catalogued the patterns and we have not even seen how they are described and how they can be declined in the application context.
So, in this first article we will justify the choices made only on the basis of a very superficial description of the patterns themselves.
This will be just the appetizer of the big meal we will be making in the next articles.
The first pattern we used is the proxy pattern. The Proxy pattern motivation that we find in the "GoF" book (1.) says: "Proxy is a structural design pattern that lets you provide a substitute or placeholder for another object. A proxy controls access to the original object, allowing you to perform something either before or after the request gets through to the original object ".
The proxy pattern allowed us to increase the performance of our dictionary framework thanks to two advantages:
-) the first advantage is that we create the channel with the remote dictionary process only once. This channel is the instance of the "ContentProviderClient" that we obtain from the "ContentResolver" through the authentication Uri declared on the manifest (Figure 3) of the dictionary application. This channel must be released by calling the "tearDown" method before the object is finalized through garbage collection.
?-) the second advantage is the local cache. The local cache is an image of the remote dictionary and is kept updated by the notification mechanism we have implemented on the IMCP (the "notifyChange" called on the "contentResolver" in the "insert" function). The "IMCPProxy" class subscribes notifications to the IMCP class by calling the "registerContentObserver" method on "contentResolver". The notifications arrive at the "onChange" method of the "IMCPProxy" class. Within the "onChange" method is called the "query" method on "ContentProviderClient" to obtain, through the "MatrixCursor", the data of the remote dictionary with which to update the local cache All data read operations called on "IMCPProxy" return the value of the local cache.
The second pattern we used was the "Facade" pattern. The "Facade" pattern motivation that we find in the "GoF" book (1.) says: "Facade provides a simplified interface to a library, a framework, or any other complex set of classes".
The application of the "Facade" allowed us to make the methods (put and get) of the "IMCPProxy" class very simple and natural, hiding all the grammar of the "ContentProviders" and "ContentResolvers" such "Projction", "Selection", "SelectionArgs", etc.
The last pattern we used is the Observer. The Observer pattern motivation that we find in the "GoF" book (1.) says: "Observer is a behavioral design pattern that lets you define a subscription mechanism to notify multiple objects about any events that happen to the object they’re observing.".
This pattern allowed us to realize the listening functionality on an IMCP field through the two methods called "notifyOnChange" and "removeNotifyOnChange" of the IMCPProxy class.
The abstraction for implementing listening is the "Observer" interface.
Internally, the IMCPProxy class uses a listeners map whose keys are the fields to be observed (for the implementation a multimap was used to allow multiple observers to register on the same data).
The data change notification mechanism is already implemented in the IMCP and the selection of the registered listener (Observer) is implemented in the "refresh" function of IMCPProxy.
In Figure 6 we see the sequence diagram relating to the use-case of a user who through the buttons on the GUI modifies the "Exercise Duration". The "ExerciseIMCP" dictionary notifies the change of this data to all proxies connected to it and places in different processes. The notification arrives at the listener (observer) registered by the "Weight Loss" application on "Exercise Duration" data.
Figure 6: Sequence Diagram of use case: "a User change the Exercise Duration with the GUI button"
In Figure 7 we see the "UnitTest" for the "UserIMCP" remote dictionary where the data of this remote dictionary is read and written by a different process.
Figure 7: Unit Test for User Proxy.
In Figure 8 we see the "UnitTest" for the dictionary "ExerciseIMCP" where from an external process an observer to the data "ExerciseDuration" is registered on the Proxy. When the data is modified the registered listener (observer) is triggered.
Figure 8: Unit Test for Exercise Proxy.
I share the "IMCP Framework" repository with you. It is a mini framework to manage key-value remote dictionaries in android. I made this project with Android Studio and consists of three modules:
-)?"imcplib"?is a module which contains classes for performing write / read operations and listeners registration on remote dictionaries.
-) "app" is the application that creates the dictionary.
-)?"app_client"?is the application that, through the proxy class of the "imcplib" module, performs operations of reading, writing and listeners recording on remote dictionaries. Contains two unit-test to perform operations on the user data dictionary and the exercise data dictionary respectively.
In the next articles we will come back several times to work on this repository linked above. We will see how with small changes we can significantly increase the Remote Dictionary performance. Then we will see how to implement a transaction mechanism that allows us to write more data on the same dictionary in an atomic way. Finally we will see how using the "shared memory" of the linux kernel instead of the java or kotlin maps we will achieve amazing performances on our remote dictionary (we will get to do a very extreme programming in android and linux) !!
Well after had fun with the code let's try to answer the last question for this article: "What is the limit of Design Patterns?".
The real limit of this design approach is that it is often difficult to choose the right pattern. The choice requires excellent analytical skills, definition of the problem and of the context, and the ability to predict the stability of both. It takes a lot of knowledge and a lot of experience.
In the next articles we will analyze in detail the role of patterns in software development, and we will see the way to represent and catalogue them.
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.?Craig Larman, “Applying UML and Patterns” Prentice-Hall ( November 2004).
3.?Hassan Gomaa : "Designing Concurrent, Distributed and Real-Time Applications with UML" Addison-Wesley ( September 2000).
4.?G.Hohpe, B.Woolf : "Enterprise Integration Patterns" Addison-Wesley ( October 2003).