If every .NET class is a component, and if classes and components share so many qualities, then what is the difference between traditional object-oriented programming and component-oriented programming? In a nutshell, object-oriented programming focuses on the relationships between classes that are combined into one large binary executable, while component-oriented programming focuses on interchangeable code modules that work independently and don’t require you to be familiar with their inner workings to use them.
The fundamental difference between the two methodologies is the way in which they view the final application. In the traditional object-oriented world, even though you may factor the business logic into many fine-grained classes, once those classes are compiled, the result is monolithic binary code. All the classes share the same physical deployment unit (typically an EXE), process, address space, security privileges, and so on. If multiple developers work on the same code base, they have to share source files. In such an application, a change made to one class can trigger a massive re-linking of the entire application and necessitate retesting and redeployment of all the other classes.
On the other hand, a component-oriented application comprises a collection of interacting binary application modules —that is, its components and the calls that bind them (see Figure 1-2).
A particular binary component may not do much on its own. Some may be general- purpose components, such as communication wrappers or file-access components. Others may be highly specialized and developed specifically for the application. An application implements and executes its required business logic by gluing together the functionality offered by the individual components. Component-enabling technologies such as COM, J2EE, CORBA, and .NET provide the “plumbing” or infrastructure needed to connect binary components in a seamless manner, and the main distinction between these technologies is the ease with which they allow you to connect those components.
The motivation for breaking down a monolithic application into multiple binary components is analogous to that for placing the code for different classes into different files. By placing the code for each class in an application into its own file, you loosen the coupling between the classes and the developers responsible for them. If you make a change to one class, although you’ll have to re-link the entire application, you’ll only need to recompile the source file for that class.
However, there is more to component-oriented programming than simple software project management. Because a component-based application is a collection of binary building blocks, you can treat its components like LEGO bricks, adding and removing them as you see fit. If you need to modify a component implementation, changes are contained to that component only. No existing client of the component requires recompilation or redeployment. Components can even be updated while a client application is running, as long as the components aren’t currently being used. Improvements, enhancements, and fixes made to a component will immediately be available to all applications that use that component, whether on the same machine or across a network.
A component-oriented application is easier to extend, as well. When you have new requirements to implement, you can provide them in new components, without having to touch existing components not affected by the new requirements.
These factors enable component-oriented programming to reduce the cost of long-term maintenance, a factor essential to almost any business, which helps explain the widespread adoption of component technologies.
Component-oriented applications usually have a faster time to market, because you can select from a range of available components, either from in-house collections or from third-party component vendors, and thus avoid repeatedly reinventing the wheel. For example, consider the rapid development enjoyed by many Visual Basic projects, which rely on libraries of ActiveX controls for almost every aspect of the application.
Another important difference between object-oriented and component-oriented applications is the emphasis the two models place on inheritance and reuse models.
In object-oriented analysis and design, applications are often modeled as complex hierarchies of classes, which are designed to approximate as closely as possible the business problem being solved. You reuse existing code by inheriting it from an existing base class and specializing its behavior. The problem is that inheritance is a poor way to achieve reuse. When you derive a subclass from a base class, you must be intimately aware of the implementation details of the base class. For example, what is the side effect of changing the value of a member variable? How does it affect the code in the base class? Will overriding a base class method and providing a different behavior break the code of clients that expect the base behavior?
This form of reuse is commonly known as white-box reuse, because you are required to be familiar with the details of the base class implementation. White-box reuse simply doesn’t allow for economy of scale in large organizations’ reuse programs or easy adoption of third-party frameworks.
Component-oriented programming promotes black-box reuse instead, which allows you to use an existing component without caring about its internals, as long as the component complies with some predefined set of operations or interfaces. Instead of investing in designing complex class hierarchies, component-oriented developers spend most of their time factoring out the interfaces used as contracts between components and clients.
.NET does allow components to use inheritance of implementation, and you can certainly use this technique to develop complex class hierarchies. However, you should keep your class hierarchies as simple and as flat as possible, and focus instead on factoring interfaces. Doing so promotes black-box reuse of your component instead of white-box reuse via inheritance.
Finally, object-oriented programming provides few tools or design patterns for dealing with the runtime aspects of the application, such as multithreading and concurrency management, security, distributed applications, deployment, or version control. Object-oriented developers are more or less left to their own devices when it comes to providing infrastructure for handling these common requirements. As you will see throughout this book, .NET supports you by providing a superb component-development infrastructure. Using .NET, you can focus on the business problem at hand instead of the software infrastructure needed to build the solution.