Open Closed Principle

Open Closed Principle

In the previous article I talked about two concepts strictly related to the Single Responsibility Principle: “cohesion” and “coupling”.

Now I am going to talk about the second solid principle, Open Closed Principle (OCP), this principle is strictly related to an important software quality : the Extendibility.

The Open-Closed Principle (OCP) was coined in 1988 by Bertrand Meyer (1.) It says:

A software artifact should be open for extension but closed for modification.

In other words, the behavior of a software artifact ought to be extendible, without having to modify that artifact. But what is the exact motivation behind this principle?

Let’s see an example out of the software world, the Nintendo Wii, a very popular gaming console. Look at the image that introduces the article

So when you buy the Wii console, what you get is the CPU tower and a basic remote controller. Nintendo also manufactures a number of accessories that can go with the Wii. For instance, there's Wii Zapper, which is a good accessory for playing shooter games in Wii. Setting this up is pretty simple. You just place the basic remote controller into a slot inside the zapper and you are all set. So the Zapper has now added a really useful feature to the Wii.

Another accessory is the Wii steering wheel, which is a really good accessory for playing racing games and it is also setup in the same way. You just put the remote controller into the slot and you are all set.

In order to add the zapper feature and the steering wheel feature, we did not do any visible change to the CPU or the basic controller, it was just plug and play.

Think about a situation where Wii wanted you to move a jumper on the console's motherboard, if you wanted to add a new accessory. That would have been so not cool, right?

So when the Wii console came out of the factory, it came out as 'Closed for modification'.

The engineers who designed Wii did not want their customers to go around opening their consoles on their own. But still they made it possible for customers to add accessories and thereby add extensions or new features to the Wii.

This did not happen by accident. This happened because the engineers at Nintendo wanted this product to behave this way, and did a really good job designing it.

So, to sum up, the Wii was designed in such a way, that it is closed for modification, but open for extension.

That was a simple analogy that explains the concept of open closed principle well(it was thought by Sujith George, teacher on Computer Science at Udemy)

Now let’s get back to software design.

When we say software components should be closed for modification and open for extension, what exactly do we mean?

Closed for modification means: new features getting added to the software component, should not make the programmer modify existing code.

At the same time, Open for extension means: a software component should be extendable to add a new feature or to add a new behavior to it.

There are two main reasons in the software world that make it convenient to respect the OCP.

The first is that we should be able to add new functionalities by adding new classes, without changing the existing classes in order to avoid (or to reduce) the need to re-test part of the system.

The second is to keep the code reusable wherever the new functionality is not needed. This is extremely important. It can be assumed that existing code is already tested.

Every software development team has software engineers and test engineers, which means that software engineers are implementing the classes and the modules and test engineers are testing the given modules with the help of unit tests. And of course, it is extremely frustrating to retest a class or a module already tested. We do not want to spend a lot of time on regression test.

That is, we should design the class interface (public and protected) so that extensions to the class are possible without changing the implementation, but by subclassing.

Not always that simple: it may lead to an explosion of subclasses.

Let’s consider an example: we have to print a Menu. At first, we simply want to add product names to a Menu and display it.

But then we apply a first extension: we should add an index before the name.

Later on we have to apply a second extension too: frame the names to have a nice menu

A snippet code below shows a first solution. It is a trivial solution obtained with inheritance

Non è stato fornito nessun testo alternativo per questa immagine

here is the output

Non è stato fornito nessun testo alternativo per questa immagine

This solution is not very good regarding the extendibility aspect. What we have to do?if we want to mix behaviors??For instance, we want a FramedIndexedMenu? Or if we want any other kind of menu, e.g. a RightToLeftMenu.

We should want to be able to combine its behavior with that of one or more other classes (IndexedRightToLeftMenu, FramedRightToLeftMenu, FramedIndexedRightToLeftMenu).

What we do in order to solve this problem?

Extending a class with inheritance is the first thing that comes to mind when you need to change the behavior of an object. However, inheritance has several serious problems that need to be acknowledged. Inheritance is static. We cannot change the behavior of an existing object at runtime, we can only replace the entire object with another created from a different subclass. Subclasses can have only one parent class. In most languages, inheritance does not allow a class to inherit the behaviors of multiple classes at the same time.

In some language, like C++, there is multiple inheritance and we could do an inheritance hierarchy but the risk is that the hierarchy could soon get very deep.

So what's the best solution?

One way to overcome this limitation is to use Aggregate or Composition rather than Inheritance.

A decorator pattern (2.) is a structural pattern that allows us to solve problems like this, where we need dynamically extensibility.

?The snippet code below is the solution to Product Menu problem with decorator, as you can see now it is very simple create a FramedIndexedMenu.

Non è stato fornito nessun testo alternativo per questa immagine

here is the output

Non è stato fornito nessun testo alternativo per questa immagine

however, we must also be careful to this solution. The decorator pattern can have a side effect : “lose” the object identity very easily in comparison with an inheritance-based solution (known as “the this problem”).

?Let’s see another example.

Consider we have two modules which we will simply refer to as “Client” and “Server”. The two modules communicate with each other via a socket.

Now we want to change the type of communication channel (e.g. use a raw serial port, or anything else?). What we have to do? We have to make changes to the two classes “Client” and “Server”, now they no longer have to manage a socket but a different communication channel, like a serial one for example.

How can we avoid changing “Client” and “Server” class when we change the nature of the communication channel? Look at the snippet of UML below.

Non è stato fornito nessun testo alternativo per questa immagine

?

In the solution on the right we are modeling only the “physical” transport channel, then a logical protocol should be kept separate. It must be similar at an extension point. Now the Open Closed Principle is respected.

Now I want to present one last interesting example. It is about a real case that was recently happened to me.

I work in a company of fitness equipment.

The hardware platforms inside our products usually consist of two main components:

-) a main processor with an AOSP Android Platform where the user entertainment and fitness apps run.

-) a microcontroller with a real-time operating system (FreeRtos) where runs the firmware that manage the actuators (motors and electromagnetic brakes), the sensors (heart-rate band receiver, NFC reader, biofeedback sensor, etc..) and the GPIO for the management of STOP button, ?joysticks for speed, grade and power, etc..

Microprocessor and microcontroller communicate through a USB connection where commands to the actuators and data from sensors are exchanged.

But due to the shortage of the electronic components, the microcontroller was no longer available.

We could have replaced it with another one (and we risked to have problems of availability with this one too) or have eliminated it completely.

The director gave us two months to solve this problem, and so we decided to take the solution of eliminating the component and porting its software to the main processor.

Fortunately we had developed the microcontroller software following the SOLID principles.

The architecture of this software is excellent, it is completely decoupled from the operating system (FreeRtos). It had been designed as a collection of self-created subsystems (drive, electromagnetic brake, heartbeat receiver, NFC management, physical button, joysticks, etc), the aim was to have all memory allocation at the beginning, which is optimal in a context like ours of small size for RAM (microcontroller PIC32 with 256K RAM).

The communication with the microprocessor had been realized with a “Postman” pattern (soon I want to make an article about it), which acquired from the communication channel and passed the envelope with the content to the receiver subsystem, same thing for the outgoing data.

Thanks to this high degree of decoupling of the software from the Operating System and from the Communication Channel, it was fast to move it from the microcontroller to the microprocessor as a native linux process. We just had to create a class for managing the localsocket by which we communicated from the Android JVM world to the Linux world (by implementing a Channel interface) and a new class for thread that specialized the abstract Thread class with the Linux pthread implementation).

Obviously we had to rewrite the drivers for the serial communication port (actuators and sensors) and the driver for managing the GPIO (STOP key, joystick, etc..) and move them to kernel-space through kernel drivers.

All the rest of the software instead has been inserted into a Linux process and runs in user-space.

Thanks to the skill of Carlo Pescio (our super consultant) and Gianni Sartoni (the most senior embedded system architect of my team) we were able to finish the activity earlier than expected, to the great satisfaction of the company.

Below is the class diagram of the software from which you can guess the decoupling from Operating System and from communication channel (you can see the Channel extension point colored brown)

Non è stato fornito nessun testo alternativo per questa immagine

?In the end, after having analyzed all these examples, we may conclude our analysis on the second principle of SOLID and saying that the OCP is one of the driving forces behind the architecture of systems. The goal is to make the system easy to extend without incurring a high impact of change. This goal is accomplished by partitioning the system into components, and arranging those components into a dependency hierarchy that protects higher-level components from changes in lower-level components.

?In the next episode I will talk about Liskow Substitution Principle, perhaps the most technical?among the SOLID principles, see you soon.

?

?I remind you my previous article:


thanks for reading my article, and I hope you have found the topic useful,

?

Feel free to leave any feedback

your feedback is appreciated

?

Stefano

?

1.????Bertrand Meyer. Object Oriented Software Construction, Prentice Hall, 1988, p. 23.

2.????Gamma, E., Helm, R., Johnson, R. e Vlissides, J., Design Patterns -Addison-Wesley p.175

要查看或添加评论,请登录

Stefano Santilli的更多文章

社区洞察

其他会员也浏览了