Your first stop on the road to interface awareness must be an examination of the problems that interface-based programming is meant to solve. Many of these problems have to do with the relationship between a class and the clients that use it. Think about the following questions: What is the relationship between a client and a class definition? What must a client know about a class in order to benefit from using it? What dependencies are created in the client when a programmer writes code using a class's methods and properties?
In an object-oriented paradigm, a client typically instantiates an object from a class. The client usually creates the object by using the New operator followed by the class name. After creating the object, the client uses it by accessing an exposed set of properties and methods through a variable that is a class-based reference. Here's a simple example that uses a variable based on a class type to access an object's public members:
Dim Dog As CDog Set Dog = New CDog '*** Access a property. Dog.Name = "Snoopy" '*** Invoke a method. Dog.Bark
In this example, a class-based reference makes it possible to instantiate and communicate with a dog object. The communication between the client and the object takes place through a set of publicly accessible properties and methods that are known as an object's public interface. The class author must use the public interface to expose the object's functionality to the client. This is what makes an object useful. Note that the method names and property names from the public interface are hardcoded into the client. Future versions of the class must continue to supply these members in order to honor the dependencies built into the client.
One benefit of using classes is that they allow you to reuse code. Once a class has been written, you can use it in many different places in an application. Classes thus let you reduce or eliminate redundant code in an application. They also facilitate code maintenance. You can modify or remove any properties or methods that aren't publicly visible. You can also change public method implementations as long as the calling syntax of the methods isn't altered. When the implementation of a method in a class is improved, any client that uses the class will seamlessly benefit from the changes.
When you modify an existing class definition, you shouldn't change the calling syntax for accessing any public method or property because of the risk of breaking the dependencies that client code has built on the original class definition. As long as you hold the public interface constant, you can make any modifications to improve your class without breaking any client code.
To rephrase the key point of the last paragraph: Once you publish a property or a method signature in a class's public interface, you can't change or remove it. This means that you must properly design the public interface at the beginning of a project and use discipline as the class evolves. This lets you improve and extend object code without having to rewrite any client code. You can maintain a class by fixing bugs and improving method implementations.
The rules for maintaining existing members of the public interface are cut-and-dried, but what flexibility do you have when you add new functionality to a class? What can you do to safely extend an object in later versions? It's easy and safe to add new public methods and properties to a class. Old clients continue to run as before, even though they can't take advantage of the object's new functionality. However, new clients written after the class has been modified can take advantage of any members added to the public interface. This means you can improve an object safely over time in the production environment.
Problems arise in class design when you change the signature of a public method in a way that breaks an existing client. This commonly happens when you discover that the initial class design was inadequate. For instance, imagine a method that provides the behavior for a dog rolling over. The following RollOver method is defined with a 16-bit integer parameter to allow the client to request a specific number of rolls in each invocation.
' Method defined in CDog class. Public Sub RollOver(ByRef Rolls As Integer) ' Implementation code goes here. End Sub ' Client hardcodes calling syntax. Dim Dog As CDog, Rolls As Integer Set Dog = New CDog Rolls = 20000 Dog.RollOver Rolls
What if the requirements of a dog object change weren't anticipated properly in the initial design? For instance, what if the required number of rolls exceeds the highest possible value for an integer (about 32 KB)? What if a client wants to invoke the method with the value 50,000? To accommodate a larger value, you must change the parameter type to a long integer. This creates quite a design problem. The newer clients want a 32-bit integer, but older clients, such as the one shown above, already have a dependency on the 16-bit integer.
You have only two options. One is to modify the method signature and then rewrite all the client code that calls it. The other is to leave things as they are and deal with the limitations of the original design. As you can see, poor class design results in either broken clients or nonextensible objects.
The intuitive solution to this problem is to make sure that the design of the class's public interface is full-featured and finalized before you write client code against it. But this isn't always possible, even for the most experienced class designer. If a class models a real-world entity that never changes, an experienced designer can create a robust, long-lasting design. However, in many cases it's impossible to predict how external changes will affect the requirements for an object's public interface. A designer who is creating classes for an application that is expected to run for years in a rapidly changing business environment can't possibly predict what is needed. If the business model is constantly changing, your classes must change with it. Therein lies the need for extensible objects.
The use of class-based references results in a layer of dependencies between clients and classes. You can lessen the impact of these dependencies on maintainability and extensibility through disciplined design and by anticipating future requirements. Don't define a method or a property in the public interface unless you're prepared to live with it forever. Most experienced designers make all data properties private and provide access to an object's state through public methods. This prevents any client dependencies on the actual data layout of the class. Be conscientious about which methods you mark as public. Any member you mark as private can be changed or removed as the class implementation evolves. Of course, you have to make some members public, or the class will be useless. Designing with class-based references always involves these trade-offs.