The Open Closed Principle
'A class should be open for extension, but closed for modification.' - Bertrand Meyer
The idea is simple, instead of changing a class to add a new feature, you simply extend the class (via inheritance or parameterization). You can then change the behavior in the new subclass to introduce the behavior you desire without altering existing, working code.
Take the following code, for example:
struct Protocol { enum ProtocolType { IP, IPX, UDP } type;};struct IpProtocol { Protocol::ProtocolType type; //IP Protocol stuff};struct IpxProtocol { Protocol::ProtocolType type; //IPX Protocol stuff};struct UdpProtocol { Protocol::ProtocolType type; //UDP Protocol stuff};void Connect(Protocol& p, std::string connectionString) { switch(p.type) { case Protocol::IP: IpConnect((IpProtocol&)p, connectionString); break; case Protocol::IPX: IpxConnect((IpxProtocol&)p, connectionString); break; case Protocol::UDP: UdpConnect((UdpProtocol&)p, connectionString); break; }}
Now, looking over this code should, hopefully, give you a very bad taste in your mouth. Of course, this wouldn't be the only switch/if-else if-else combination in the application. Most likely it would be littered with many such examples as this one. Every time a new protocol wishes to be supported, these conditional statements would have to be updated. This code clearly violates OCP, as any new feature must alter many different pieces of code to add support for it.
OCP can be achieved through abstraction. Applying the appropriate abstractions to your code will enable it to easily be extended to add new functionality (or alter existing functionality) without requiring exiting code to change. Below you can see a simple diagram demonstrating the use of abstraction to make the code conform to the Open Closed Principle.
Now we can add new protocols by simply implementing the Protocol interface, or sub-classing one of the existing protocols. The Connect function, and any other functions dependent upon the Protocol interface, will not need to change to accommodate the new protocols as the interface abstracts the concrete implementation from the design.
Implementing this in code we end up with:
class Protocol {public: virtual void Connect(std::string const& connectionString) = 0; virtual void Disconnect() = 0; virtual void Send(std::vector<char> const& data) = 0; virtual void Receive(std::vector<char>& data) = 0; virtual ~Protocol() = 0 {}};class IpProtocol : public Protocol {public: virtual void Connect(std::string const&); virtual void Disconnect(); virtual void Send(std::vector<char> const&); virtual void Receive(std::vector<char>&); ~IpProtocol();};class IpxProtocol : public Protocol {public: virtual void Connect(std::string const&); virtual void Disconnect(); virtual void Send(std::vector<char> const&); virtual void Receive(std::vector<char>&); ~IpxProtocol();};class UdpProtocol : public Protocol {public: virtual void Connect(std::string const&); virtual void Disconnect(); virtual void Send(std::vector<char> const&); virtual void Receive(std::vector<char>&); ~UdpProtocol();};void Connect(Protocol& p, std::string const& connectionString) { p.Connect(connectionString);}
Looking over this, you are probably thinking to yourself: 'Damn, this is more code that the other way though.' However, perhaps you should implement the other code so that it provides the Disconnect, Send, and Receive functions first. At which point you will see that not only is it more code than we have currently, but it also requires a great deal more changes just to implement a single extra protocol (take SPX for instance).