Often I come across a strange phenomenon when I do technical interviews. If I name a design pattern and ask the candidates to explain about it, they come up with fairly good explanation, if they know about it. But if I give a scenario and ask them to come up with a design, they often tend to ignore patterns and come up with a design which just satisfies the functionality. This shows that there is some disconnect. Disconnect between theory and practice. This is not really strange, if you realize that any article or blog on Design Patterns usually starts with describing the pattern, and then describes various classes in the pattern and then how you implement the pattern in Java or C-Sharp or C++. There might be a text book example added to it. However, for a good designer, it is not enough to know about design patterns and how to implement them. A good designer should have the ability of identifying patterns suitable for a given scenario. In fact the pattern he or she comes up with need not be something which exactly matches the text book definition of an existing pattern. Like in art, improvisations are always welcome. After all software designing is also an art in some sense!
With this brief intro, let me try to do what I said above. I want to introduce a design pattern without actually telling what it is.
Here is the requirement. You are building a Rich Text Editor. It has the usual functions of a rich text editor like - typing text, copy, paste, making some text to bold, changing color, font etc. Let us say you need to develop a functionality of Undo and Redo for these functions. How do you design your Undo/Redo module?
Here is one approach. Create a class called Undo which has one method each for each operation, like undoType, undoPaste, undoBold etc. For each operation, the details of the data modified by the operation are stored in different data objects. Let us say typed in text is stored in a TypedText object, pasted text is stored in a PastedText object, bolded text is stored in BoldedText object etc. Each method in Undo class, takes a text editor object and the corresponding data object as parameters. The logic of undoing is implemented in each of that method.
How do we invoke the method in Undo class? Create an UndoListener class which listens for undo event. Ok. How does it know what is the operation that needs to be undone? We need to store the latest operation that is done. Since we would also want to support multiple undos, we need to store all the operations along with their order. So we store the operations with their order in a data structure. Whenever user performs an operation, the corresponding listener associated with that operation will create the corresponding data object (like PastedText, BoldedText etc.) and updates the operations list. UndoListner gets the latest operation from the list, checks what is the type of operation and based on the operation type, it calls corresponding method of Undo class.
The class diagram for the above design looks like this:
So what is the problem with this design? The required functionality is surely achieved. Are there any other issues with the design? If there are some problems with this, how can we overcome them?
Give a thought about it and I’ll discuss it further in my next post.
2 comments:
Nice example...
Here are few of my observations-
1. The first problem is the "Undo" class itself. Undo is a 'verb' and so making it a class is kind of going against OO design principles. Ideally a class must be a Noun- say in our example, TextEditor can be a class, PastedText can be class etc etc
2. Since 'Undo/Redo' is an operation, it must be defined as a method of some class. The question is "Which class?". I would say, ideally, a class whose object is operated upon by the 'Undo/Redo'. For example- we can define Undo() operations in the 'PastedText', 'TypedText' classes etc, so that they can be invoked as oPastedText.Undo(), oTypedText.undo() where oPastedText and oTypedtext are objects of their corresponding classes. This looks more closer to a OO design
3. Now since the 'Undo' operation can be part of so many classes, it would be good to have an Abstract Class or an Interface that defines a basic text data object and the classes 'TypedText', 'PastedText' etc would derive from it (if Abstract class) or implement it (in case of Interface). The abstract class would also contain a Undo() method which would be over-ridden in each of the inherited classes so that the actual Undo() implementation is encapsulated in each of the respective types
Now coming to invoking the Undo() operation-
In order to loosely-couple the TextEditor class with a particular data object (say PastedText or BoldedText), we could use a Factory class that creates the instance of a particular text data object and return an abstract interface to TextEditor. This way, the TextEditor can call the Undo() method using the abstract interface without having to worry about which class the Undo() is actually belonging to. This will allow us to add more Text data types in future without having to change the textEditor class much.
More in next comment....
--Ruman Shareef
Well...my second comment- which I had posted yesterday evening- got deleted somehow.
And since I am a bit lousy I will not re-write my second comment again. But in summary, this is what it said-
1. Each of the text data objects to have certain states that would convey whether they are in a state where Undo or Redo is possible
2. Implement Observer pattern so that an Observer class observes the state of each of the text data object and when certain state change occurs, notify the other objects in the queue.
--Ruman
Post a Comment