Modules that conform to OCP have two primary attributes.
It would seem that these two attributes are at odds. The normal way to extend the behavior of a module is to make changes to the source code of that module. A module that cannot be changed is normally thought to have a fixed behavior. How is it possible that the behaviors of a module can be modified without changing its source code? Without changing the module, how can we change what a module does? The answer is abstraction. In C# or any other object-oriented programming language (OOPL), it is possible to create abstractions that are fixed and yet represent an unbounded group of possible behaviors. The abstractions are abstract base classes, and the unbounded group of possible behaviors are represented by all the possible derivative classes. It is possible for a module to manipulate an abstraction. Such a module can be closed for modification, since it depends on an abstraction that is fixed. Yet the behavior of that module can be extended by creating new derivatives of the abstraction. Figure 9-1 shows a simple design that does not conform to OCP. Both the Client and Server classes are concrete. The Client class uses the Server class. If we want for a Client object to use a different server object, the Client class must be changed to name the new server class. Figure 9-1. Client is not open and closed.
Figure 9-2 shows the corresponding design that conforms to the OCP by using the STRATEGY pattern (see Chapter 22). In this case, the ClientInterface class is abstract with abstract member functions. The Client class uses this abstraction. However, objects of the Client class will be using objects of the derivative Server class. If we want Client objects to use a different server class, a new derivative of the ClientInterface class can be created. The Client class can remain unchanged. Figure 9-2. STRATEGY pattern: Client is both open and closed.
The Client has some work that it needs to get done and can describe that work in terms of the abstract interface presented by ClientInterface. Subtypes of Client-Interface can implement that interface in any manner they choose. Thus, the behavior specified in Client can be extended and modified by creating new subtypes of ClientInterface. You may wonder why I named ClientInterface the way I did. Why didn't I call it AbstractServer instead? The reason, as we will see later, is that abstract classes are more closely associated to their clients than to the classes that implement them. Figure 9-3 shows an alternate structure using the TEMPLATE METHOD pattern (see Chapter 22). The Policy class has a set of concrete public functions that implement a policy, similar to the functions of the Client in Figure 9-2. As before, those policy functions describe some work that needs to be done in terms of some abstract interfaces. However, in this case, the abstract interfaces are part of the Policy class itself. In C#, they would be abstract methods. Those functions are implemented in the subtypes of Policy. Thus, the behaviors specified within Policy can be extended or modified by creating new derivatives of the Policy class. Figure 9-3. TEMPLATE METHOD pattern: Base class is open and closed.
These two patterns are the most common ways of satisfying OCP. They represent a clear separation of generic functionality from the detailed implementation of that functionality. |