According to software architects, design patterns are everywhere, you probably use them without knowing it. There is a lot of article dealing with those and their usage in very simple contexts. They are presented as small examples without connection between them. In this article, we propose 5 design patterns in a bigger picture.
Design patterns can sometimes look like voodoo magic: depending on the language you use, the implementation can be very different. However at high level, everything sounds more clear: diagrams help a lot to understand things (you cannot deny this). So, we took a practical case and we decided we could explain each design patterns you can find in it to you. Each design pattern is detailed with links to more academic definitions. Please consider this as a practical and illustrated example about the design/conception of a computer (we assumed everybody knows that).
5 design patterns in a UML class diagram
When we designed our computer, we assumed that:
- a computer is made of a processor, a memory and potentials additional cards (sound…etc),
- a processor is either a CPU either an accelerator card that will add behavior to an existing CPU,
- a processor must be init (as well as accelerator cards) and has the ability to read/write from/in memory, to load a program to an address and execute it.
- a memory can be either a RAM or a cache,
- a cache is always dealing with a RAM,
- if a cache exists it is primarily questioned by the processor,
- a computer can directly handle up to 3 additional cards,
- in order to handle new cards, a computer must have an extension board,
- many sounds cards with different behaviors exist.
The solution we came up with is the following (we remove some parts):
You can find the full uml class diagram here, clone it and modify it as you please. In this diagram, we colored the different design patterns. That way, you should be able to easily identify the five of them.
The proxy pattern
First, let’s begin with a simple one: the proxy pattern. We use it to design the RAM/Cache elements.
Find the definition of this pattern here. The main idea behind this pattern is to use a middleman, which is exactly what happens when the processor needs to read something in memory. When the processor tries to read in Memory, it only performs a
read(@) operation. If a Cache is present, the Cache looks inside the chunk of bytes it fetched. If the address is not there: it reads in RAM, fetches a new chunk of memory and gives the read byte to the processor. This flow is invisible to the Processor as the proxy gives access to the hid object when required. The proxy pattern has many usages (change few operations behavior, restrict operations visibility, lazy access to objects…) but the idea is always the same: a proxy is a kind of middleman.
This pattern is really handy when you have to define recursive typed objects. Any guess about what part of the machine needs it?
To see the full description of the pattern, go there. This time, we want to deal with composite recursion. Our computer can only manage 3 additional cards. However, it can use ExtensionBoard that will enable the use of other cards. Actually, this ExtensionBoard can contain and gather many Cards and, why not, other ExtensionBoard that will enable the computer to use other ones. Do you feel the recursive idea here? That’s what the composite pattern is made for.
Let’s stay in the Card and stuffs area of our computer to talk about SoundCards. Like I said in the introduction, many vendors design SoundCards for computers:
Having a choice about our SoundCard is nice. However, even they are on the same page about the way the card should work, they do not use a common interface and provide different operations that have almost the same semantics. In this context, the adapters are kind of socket converters that will wrap the vendor SoundCards and implement the common interface by delegating the behavior to the wrapped Card. For example, the
Vendor2Adapter implements the
end() method by delegating its execution to the
halt() method of the
We detailed how the Memory and the additional Cards are handled by our computer. Now, we will concentrate on the Processor. Our processor can be get a boost by using AcceleratorCards. How did we modeled this?
To see the full description of the pattern, this time, go there. Our Processor has always and must always have the same behavior. But we would like to add new behavior to it without perturbing its original process. The decorator pattern is thus a great candidate. It adds additional behavior for the initialization phase and the execution phase. The implementation or the FastCard shows that when the
init() method is called, the card first calls the
additionalInit() method, then it calls the
init() method of the CPU it decorates (ok, in this case, the difference between the proxy pattern and the decorator pattern is thin, by the way, some people call it ‘smart proxy’). The same thing is performed for the
Finally, the “main” pattern here, the facade pattern. This pattern is used here in the
Find this last pattern definition in this website. The main purpose of the facade pattern is to hide the complexity of a system by providing easy and comprehensible methods. In our case, the
execute() method of the Machine Element enables us to execute a dedicated program. The way the program execution is performed is quite complex. The processor must be started as well as the additional cards. Then, the program that should be executed must be loaded and located in the memory at a specific address. The
execute() method hides all this complexity by orchestrating all object instantiations/initializations and by adding all required magical values. This pattern is used almost all the time when complex systems need to be simply handled.
That’s all for these patterns, I hope you enjoyed your trip and be sure we will renew this kind of article.