|only for RuBoard|
An interface is a contract, and contracts are not meant to be broken. It is not hard to fathom that if you change an interface, the waters will get rough. Code that depends on an interface that has been changed will no longer work. Once an interface is published, it is imperative that it does not change. This rule applies to the public and protected interfaces of a class, as well as any interfaces that were implemented by a class.
In the tradition of the open -closed principle, when you need to extend an interface, simply create a new one. Usually, a number is appended to the original interface name . For example, the second version of IPaintable would be IPaintable2 .
Working with interfaces is much easier than it used to be. When creating a new version of an interface, you can inherit from the old version to maintain integrity and add the new functionality to the derived interface. Overloading is also allowed:
Imports System Imports System.Drawing Public Interface IPaintable2 : Inherits IPaintable TransparentFill(color As System.Drawing.Color) Overloads Paint(color1 As Color, color2 As Color) End Interface
Old clients can still obtain an IPaintable reference from IPaintable2 , while new clients can use the new interface directly.
The versioning story of interfaces is debatably better than a base class. It is definitely more explicit. When you need something new, you define a new interface. It's as straightforward as that. Old clients can use the old interface, while new clients can take advantage of more recent offerings. Simply adding methods to a base class and sorting things out at runtime is not very safe, nor is it forward thinking. It opens you up to all kinds of failures.
|only for RuBoard|
|only for RuBoard|
5.3 Interfaces Versus Abstract Base Classes
Interfaces provide all the benefits of polymorphism. You can code against an interface much like you can code an abstract base class:
Dim superSport As New Car( ) . . . PaintSomeStuff(superSport) . . . Public Sub PaintSomeStuff(someThing As IPaintable) someThing.Paint( ) End Sub
So what is the difference between an interface and an abstract base class? When should you use one over the other?
If you are describing an is-a relationship, you can use an abstract base class. An interface, on the other hand, usually describes a service or facility that can be used across a wide variety of objects. For instance, House , Car , and Fence are three very different classes, but each could implement IPaintable and handle the painting process in a manner specific to its needs.
This describes a typical scenario and is a pretty good guideline to follow, but it is by no means set in stone. Just because an is-a relationship is described doesn't mean you can't use an interface in place of an abstract base class. If there is no implementation in the base class, there is no reason not to use an interface instead. The Payment class you have already seen could easily be rewritten as an interface:
Public Interface IPayment Function Authorize(amount As Decimal) As Boolean Function Bill( ) As Boolean Function Credit( ) As Boolean End Interface
One advantage of using interfaces is that the number a class can implement is unlimited (in theory). You can implement as many interfaces as your class requires, but you are only allowed to derive from one base class. You will often hear of interfaces described primarily as a means of circumventing this limitation. It's true, but this description minimizes the importance of interface-based programming. Interfaces do have a place beyond this role.
Programming against an interface gives you all the benefits of coding against an abstract base class, at least in a polymorphic sense. It also gives you more flexibility in your design, at least in terms of component-to-component dependency.
This book has discussed the use of abstract base classes and inheritance to minimize dependencies in a system. You have seen how polymorphism allows the construction of flexible systems. However, you should consider several issues relating to polymorphism and the tendency of an application to grow over time.
It is reasonable to expect the requirements of a base class or a derived class to change over time. Changing a base class, however, could wreak havoc with classes that are derived from it. This well-known problem is called the fragile base class .
As inheritance trees grow, modifying classes at the top can impact classes below significantly. This is especially true of classes that provide protected access to implementation details. Once a derived class calls a protected method, a dependency is created. You can no longer change the base class implementation without the possibility of breaking a derived class.
When overridable virtual functions are involved, you introduce the possibility that the client or base class will call functions overridden by a derived class. When these functions are called, it is no longer completely safe to change a base class implementation of an overridable method. You can't add functionality that is required by a derived class beyond this point or provide new functionality.
If you expect a class to be extended, it is just as important to encapsulate implementation details from a derived class as it is to encapsulate them from the client. You can minimize these dependencies by limiting or restricting protected access to a component altogether. You can also force derived classes to provide their own implementation through the use of the MustOverride modifier. Then you can use references to the abstract base class throughout your object model. However, even after considering this scenario, there are still a few issues at hand.
Although inheritance is good, it is best used in small doses, in situations that you control. Inheritance is considered white box reuse . This term implies that inheriting from a class requires knowledge or awareness of that class' internals.
If you use references to abstract base classes, you still create a layer of dependence, which may or may not be a problem for you. In fact, within a component, this issue is probably not very important. But step back for a moment.
Most systems are built using object composition . Components are built that contain various layers of the application, such as data access, business rules, and so forth. Then you use these components to build an application. In contrast to inheritance, object composition is known as black box reuse .
Now think about the "edges" of your systemthe hinges , so to speak. If one component uses class-based references from another component, what does that mean? The two components are now dependent on each other. You are tied to an implementation, which is fine if you are satisfied with an implementation or you don't expect an implementation to change.
However, if the base class being referenced were to change, you might have some real problems on your hands. You've heard about the fragile base class problem. If you are solely responsible for an inheritance hierarchy, changes can be painful, but not detrimental. You can make your changes, recompile, and everything will be back in order. This solution is not ideal, but situations like this occur frequently outside of academia.
However, if you expose a class-based reference through a public interface, you could cause problems that would leak outside of your domain of influence. Another component that uses yours may no longer work. For this reason, many people believe that using an object as a parameter to a method should be outright illegal.
To reiterate, the use of interfaces and abstract base classes does not have to be mutually exclusive. You can use both. Example 5-3 shows a possible way to code the Payment class.
Example 5-3. Using interfaces and ABCs
Public Interface IPayment Function Authorize(ByVal amount As Decimal) As Boolean Function Bill( ) As Boolean Function Credit( ) As Boolean End Interface Public MustInherit Class Payment Implements IPayment Public MustOverride Function Authorize(ByVal amount As Double) _ As Boolean Implements IPayment.Authorize Public MustOverride Function Bill( ) As Boolean _ Implements IPayment.Bill Public MustOverride Function Credit( ) As Boolean _ Implements IPayment.Credit End Class
Rather than show two pages of database access code, you can assume that the Payment class contains base functionality that pulls payment information from a relational store. Classes that need this base functionality, like types of payments (e.g., credit cards or gift certificates), can inherit from the Payment class. Code that uses the hierarchy could refer to the interface instead of the class.
|only for RuBoard|