Preface
In the past, one of my friends often said that learning anyProgramming LanguageThe most difficult part is to run "Hello World", and then everything is easy. After many years, I realized that he was right. The basic goal of learning a design pattern is to use it, especially to help those who have a solid OOP Foundation and are confused about the design pattern to apply it in the design. I will not write a comprehensive reference for different design patterns, but I hope theseArticleLet you get started. The design pattern has nothing to do with a specific language. Although I have written many examples in C #, I try to avoid some special structures in C #. Therefore, it is intended for most people, especially those who use C ++.
The modifier mode allows us to dynamically add behavior for objects. Next we will first introduce a scenario and then look for an alternative method. This helps us to recognize the true use of this model, especially in terms of flexibility.
Thinking process
Today we are not referring to a practical scenario. Sometimes it may be strange, but it will help us understand this pattern and related concepts. Our goal is to add the function of storing information from different sources to disk files. Therefore, the first step is to define an interface and implement it.ImessagewriterAndImessagereaderThe interfaces are used to write and read information respectively. Their implementation is as follows:
interface imessagewriter {string message {set;} void writemessage (string filepath);} class messagewriter: imessagewriter {private string message; public String message {set {message = value;} Public Virtual void writemessage (string filepath) {file. writealltext (filepath, message) ;}} interface imessagereader {string readmessage (string filepath);} class messagereader: imessagereader {Public Virtual string readmessage (string filepath) {If (file. exists (filepath) return file. readalltext (filepath); return NULL ;}
InformationMessageAttribute storage,MessagewriterMethodWritemessageWrite it to the specified file. Similarly,MessagereaderMethodReadmessageRead from the specified file and return it as a string. Now we assume that the customer has raised new requirements.
- Before reading and writing certain information, we need to verify the user;
- We want to encrypt and save some information to prevent others from reading it, and we need to save the encrypted information in 64-bit encoding;
- We all need these functions for some information;
It's strange. Well, first of all, we don't need to decorator to analyze different solutions. This will make our understanding of this simple design model clearer.
Traditional solutions
You decided to use inheritance in the original behavior. FromMessagewriterInheritanceEncryptedmessagewriterTo implement encryption.
Class encryptedmessagewriter: messagewriter {public override void writemessage (string filepath) {// encrypt the message // convert to a 64-bit encoded message = "base64stringyougotfromabovecode"; // store the information base. writemessage (filepath );}}
SimilarlyEncrytedmessagewriterInheritanceSecuremessagewriterTo implement user authentication.
Class securemessagewriter: encryptedmessagewriter {public override void writemessage (string filepath) {If (validateuser () base. writemessage (filepath); else console. writeline ("No Message saved, user validation failed. ");} private bool validateuser () {// verify the user. If the user fails, false return true;} is returned ;}}
Now we can write encrypted information or encrypted information after user authentication. What should we do if we need to write some simple text information that only requires user authentication instead of encryption? You canEncryptedmessagewriterWrite some ugly judgment, skip encryption when no encryption is required. If you still do this in this case, you need to change the operation order. For example, if you want to encrypt the message first and then verify it, if the verification fails, do something other than the 64-bit encrypted message. Obviously, the preceding organizational structure cannot handle this situation. Who can prevent users from putting forward more demands, such as messages requiring data signatures, large messages requiring compression or no encryption? After some information is written to the disk, you must enter the file path and timestamp in the message queue for otherProgramRead, even write to database, etc ?! Obviously not.
Let's focus on verification, ignore other details, and evaluate the complexity and severity of your situation. Currently, we implement user authentication when encrypting messages. Now we need to meet the same requirements for other functions, such:Compressedmessagewriter,Digitallysignedmessagewriter. The only thing you can do is implementSecurecompressedmessagewriter,Securedigitallysignedmessagewriter. Similarly, a large number of other combinations, such as compressing encrypted information and compressing simple information. Oh, God, you really fell into the "sub-class hell.
The second solution is to write a very largeMessagereader. As time passes, it becomes increasingly complex and becomes increasingly difficult to maintain-it is not recommended.
The third solution may be the merger of the above two solutions, which may be a temporary solution.
Introduce the decorator Mode
This is precisely the problem solved by the decoration device mode. If you carefully observe the inheritance solution above, you will realize that the root cause of the problem is the static Association brought about by inheritance. These associations are embedded between classes and cannot be changed at runtime. The decorator replaces associations with contains, and contains an object association that is flexible and updated during running.
First, let's see what the decorator mode is. The following is a class chart of the decorator mode.
Four participants:
- Component: Define an object interface to dynamically add responsibilities to the object. In our exampleImessagewriterAndImessagereader;
- Concretecomponent: Define an object that implements the component interface. This object will be decorated, but it will not contain any information about the decoration, and the decoration will not access its implementation. In our exampleMessagewriterAndMessagereader.
- Decorator: Contains a reference to a component object and defines an interface consistent with component. Therefore, it contains a reference pointing to the basic behavior and implements the same interface, so it can be accessed by component itself. ClientCodeIt is expected that component will not need to care about the differences between the decorators.
- Concretedecorator: It adds responsibilities to component. Classes inherited from the decorator can be added in the form of new methods to add some additional functions.
So far, we have two parts: component: basic behavior, that is, in our exampleImessagewriterAndImessagereaderAnd concretecomponent, that is, the read/write behavior we implement:MessagewriterAndMessagereader.
Below is what we implementSecuremessagewriterAndEncryptedmessagewriter.
Class securemessagewriter: imessagewriter {private string message; // The Private imessagewriter messagewriter; Public securemessagewriter (imessagewriter msgwriter) {This. messagewriter = msgwriter;} Public String message {set {message = value;} public void writemessage (string filepath) {If (this. validateuser () {// Add a new behavior // as you can see, we added the verification behavior messagewriter before calling the standard method of the decorator. message = This. message; messagewriter. writemessage (filepath);} else console. writeline ("");} private bool validateuser () {// verify the user code return true;} class encryptedmessagewriter: imessagewriter {private string message; // Private imessagewriter msgwriter; Public encryptedmessagewriter (imessagewriter msgwriter) {This. msgwriter = msgwriter;} Public String message {set {message = value;} public void writemessage (string filepath) {This. msgwriter. message = "encrytedmsginbase64"; // encrypted information // decorated this. msgwriter. writemessage (filepath);} private string GetPassword () {console. writeline ("Please provide security password"); Return console. readline ();}}
Is there a problem here ?????
I just said that this mode has four participants, and I have shown you component (Imessagereader,Imessagewriter), Concretecomponent (Messagereader,Messagewriter) And concretedecorator (Securemessagewriter,Encryptedmessagewriter). HoweverDecoratorWhere are you going? In our example, we only add existing behaviors without introducing new ones. We did not change other structures. In this case, we ignore the implementationDecoratorAnd follows the main hierarchy. Here I am not presenting read-related classes. They are just reverse processing.
What have we learned?
If I need a simple user-verified write operation, I will do this:
Imessagewriter msgwriter = new securemessagewriter (New messagewriter ());
I useSecuremessagewriterDecoratedMessagewriterIt will verify the user before writing information to the disk. If you need to verify the user and encrypt the information at the same time, I will do this:
Imessagewriter msgwriter = new securemessagewriter (New encryptionmessagewriter (New messagewriter ()));
- The decorator allows us to avoid constructing complex base classes and writing a large amount of code in most cases.
- The decorator allows us to make different combinations in different order, which is not easy for other methods.
- Compared to different behavior and merge implementation, we can implement separate demand behavior as needed.
Back to reality
In this section, we will see an example of the decorator mode applied in practice.
Synchronization package
Be good at using the old collection classes in. Et, suchQueue,ArraylistPeople may still remember the (SET) Synchronization functions provided by many classes. It passes in the collection instance as a parameter and returns a synchronization set. Although the collection classes are not synchronized by themselves, the returned method is actually a modifier inherited from the Collection class itself. For example, in the following code, when we callSyncrhonized (A1)We will receiveArraylistInheritedSyncarraylist.
Arraylist Al = new arraylist (); Al = arraylist. synchronized (Al );
SyncarraylistStorage pass attributes_ ListTransmittedArraylistAnd reload different instance methods in the same way.
Public override int add (object Value) {lock (this. _ root) {return this. _ list. Add (value );}}
Notes
When using this method to create a synchronization package, pay attention to the "self-deadlock" phenomenon, which means that a thread occupying the lock enters another method (or recursion ), then try to get the lock of the same object. In Windows, if you use. Net for monitoring, kernel-level naming, or anonymous mutex, all have a re-import mechanism (recursion ). Therefore, you will not encounter such problems, but when programming in other environments (such as Linux), the default mutex type is quick mutex (a non-recursive mutex ), your code may become a victim of "self-deadlock. If a message is used, even on Windows, it is not self-conscious. If you do not pay attention to it, it will bring you this problem. Of course, for a simple signal, such as n = 1, you will encounter an "self-deadlock" during the second access ".
Similarly, you can implement a read-only package for your collection class. Unlike what we have seen so far, it is better to remove some functions than to add functions to classes. For example, an exception that is not supported by the operation may be thrown in the add () method of the overload method .. NET provides readonlycollection <t>, which is used to wrap the generic list. Java provides read-only packages, such as unmodifiablecollection and unmodifiableset.
In Java, you can obtain synchronization packages for many Collection types as follows.
List list = collections. synchronizedlist (New arraylist ());
Io in Java and. net
JAVA JAVA. io package and.. Net stream class uses this mode. I won't talk about many details about their implementations .. stream is an abstract class that provides basic behaviors (such as component ), from the abstract class stream inherited classes filestream , memorystream is concretecompents , class bufferedstream , C Rytostream is concretedecorators . You can clearly understand that they also ignore decorator .
Similarly, in Java,BufferedreaderAndFilterreaderYesReader. WhileBufferedreaderFurtherLinenumberreaderDecoration.FilterreaderQuiltPushbackreaderDecoration.
What have you learned?
The decorator mode allows us to provide extension points in the implementation. You may have noticed that I have never involvedComponentClass. Therefore, even if it does not have a class, it can be decorated by dynamically adding behavior classes, or even recursion. These additional actions may be added before or after several actions, or such actions may be blocked. The modifier can provide new methods or even new attributes in the class. However, there are also some problems with the decorator.
- Programmers who use decorator must understand their intentions, otherwise they may eventually use meaningless combinations or sequences. For example, in our scenario, if a programmer defines a sequence in this way: the information is first compressed, then encrypted, and finally verified, it makes no sense. In real scenarios, some combinations or sequences may be disastrous.
- To test a decoration class, you must provide a simulated decoration class. Because we have not involved testing, we will not talk about it here.
- This mode adds some responsibilities to the basic behavior. Although adding new attributes or methods to the decorator is completely legal, this method leads to flexibility for the basic class to handle problems, because you need to use specific instances.
The last article is the same name article on codeproject <decorator pattern (a layman to laymen) > Translation and Modification The content in this article is different from that in the original text.
The article gradually exposes problems (various inheritance, sub-classes, and code are crowded together like a twist) in a step-by-step manner, and then solves the problem using the decoration mode (build each requirement into a component separately, and then complete the work like building blocks.) Finally, contact the actual user to explain the true meaning of the decoration mode.This makes the entire article well-founded, clear and easy-to-understand, and I believe it will be of great help to those who want to learn the model. Limited by personal abilities, it is inevitable that there will be errors in this article. Please give me more advice (
Especially in italics :().
Reference: Design Pattern reusable basics of Object-Oriented Software Decorator pattern (a layman to laymen)
. NET and Design Patterns
Msdn