Section 1.3. Principles of Component-Oriented Programming


1.3. Principles of Component-Oriented Programming

Component-oriented programming requires both systems that support the approach and programmers that adhere to its discipline and its core principles. However, it's often hard to tell the difference between a true principle and a mere feature of the component technology being used. As the supporting technologies become more powerful, no doubt software engineering will extend its understanding of what constitutes component-oriented programming and embrace new ideas, and the core principles will continue to evolve. The most important principles of component-oriented programming include:

  • Separation of interface and implementation

  • Binary compatibility

  • Language independence

  • Location transparency

  • Concurrency management

  • Version control

  • Component-based security

The following subsections discuss these seven important principles. As discussed in the next section, .NET enables complying with all of the core principles, but it does not necessarily enforces these principles.

1.3.1. Separation of Interface from Implementation

The fundamental principle of component-oriented programming is that the basic unit in an application is a binary-compatible interface. The interface provides an abstract service definition between a client and the object. This principle contrasts with the object-oriented view of the world, which places the object, rather than its interface, at the center. An interface is a logical grouping of method definitions that acts as the contract between the client and the service provider. Each provider is free to provide its own interpretation of the interfacethat is, its own implementation. The interface is implemented by a black-box binary component that completely encapsulates its interior. This principle is known as separation of interface from implementation.

To use a component, the client needs only to know the interface definition (i.e., the service contract) and to be able to access a binary component that implements that interface. This extra level of indirection between the client and the object allows one implementation of an interface to be replaced by another without affecting the client code. The client doesn't need to be recompiled to use a new version. Sometimes the client doesn't even need to be shut down to do the upgrade. Provided the interface is immutable, objects implementing the interface are free to evolve, and new versions can be introduced smoothly and easily. To implement the functionality promised by an interface inside a component, you use traditional object-oriented methodologies, but the resulting class hierarchies are usually simpler and easier to manage.

Interfaces also facilitate reuse. In object-oriented programming, the basic unit of reuse is the object. In theory, different clients should be able to use the same object. Each reuse instance saves the reusing party the amount of time and effort that would have been spent implementing the object. Reuse initiatives have the potential for significant cost reductions and reduced product-development cycle time. One reason why the industry adopted object-oriented programming so avidly was its desire to reap the benefits of reuse.

In reality, however, objects are rarely reusable. They are often specific to the problems and particular contexts for which they were developed, and unless the objects are "nuts and bolts"that is, simple and genericthey can't be reused even in very similar contexts. This is also true in many engineering disciplines, including mechanical and electrical engineering. For example, consider the computer mouse you use with your workstation. Each part of this mouse is designed and manufactured specifically for your make and model. For reasons of branding and electronics specifics, parts such as the body case can't be used in the manufacturing of any other type of mouse (even very similar ones), whether made by the same manufacturer or others. However, the interface between mouse and human hand is well defined, and any human (not just yourself) can use the mouse. Similarly, the typical USB interface between mouse and computer is well defined, and your mouse can plug into almost any computer adhering to that interface. The basic units of reuse in the computer mouse are the interfaces with which the mouse complies, not the mouse parts themselves.

In component-oriented programming, the basic unit of reuse is the interface, not a particular component. By separating interfaces from implementation in your application, and using predefined interfaces or defining new interfaces, you enable that application to reuse existing components and enable reuse of your new components in other applications.

1.3.2. Binary Compatibility Between Client and Server

Another core principle of component-oriented programming is binary compatibility between client and server. Traditional object-oriented programming requires all the parties involvedclients and serversto be part of one monolithic application. During compilation, the compiler inserts the addresses of the server entry points into the client code. Component-oriented programming, in contrast, revolves around packaging code into components (i.e., binary building blocks). Changes to the component code are contained in the binary unit hosting the component; you don't need to recompile and redeploy the clients. However, the ability to replace and plug in new binary versions of the server implies binary compatibility between the client and the server, meaning that the client's code must interact at runtime with exactly what it expects as far as the binary layout in memory of the component entry points. This binary compatibility is the basis for the contract between the component and the client. As long as the new version of the component abides by this contract, the client isn't affected. In Chapter 2, you will see how .NET provides binary compatibility.

1.3.3. Language Independence

Unlike in traditional object-oriented programming, in component-oriented programming, the server is developed independently of the client. Because the client interacts with the server only at runtime, the only thing that binds the two is binary compatibility. A corollary is that the programming languages that implement the client and server should not affect their ability to interact at runtime. Language independence means exactly that: when you develop and deploy components, your choice of programming language should be irrelevant. Language independence promotes the interchangeability of components, and their adoption and reuse. .NET achieves language independence through an architecture and implementation called the Common Language Runtime (CLR), which is discussed further in Chapter 2.

1.3.4. Location Transparency

A component-based application contains multiple binary components. These components can all exist in the same process, in different processes on the same machine, or on different machines on a network. With the advent of web services, components can also now be distributed across the Internet.

The underlying component technology is required to provide a client with location transparency, which allows the client code to be oblivious to the actual locations of the objects it uses. Location transparency means there is nothing in the client's code pertaining to where the objects execute. The same client code must be able to handle all cases of object location (see Figure 1-3), although the client should be able to insist on a specific location as well. Note that in the figure, the object can be in the same process (e.g., Process 1 on Machine A), in different processes on the same machine (e.g., Process 1 and Process 2 on Machine A), on different machines in the same local network (e.g. Machine B), or even on different machines across the Internet (e.g., Machine C).

Figure 1-3. With location transparency, the client is oblivious to the actual object location


Location transparency is crucial to component-oriented programming for a number of reasons. First, it lets you develop the client and components locally (which leads to easier and more productive debugging), yet deploy the same code base in distributed scenarios. Second, the choice of whether to use the same process for all components or multiple processes for multiple machines has a significant impact on performance and ease of management versus scalability, availability, robustness, throughput, and security. Organizations have different priorities and preferences for these trade-offs, yet the same set of components from a particular vendor or team should be able to handle all scenarios. Third, the locations of the components tend to change as an application's requirements evolve over time. If the locations are transparent to the client, this movement does not cause problems.

To minimize the cost of long-term maintenance and extensibility, you should avoid having client code make any assumptions regarding the locations of the objects it uses and avoid making explicit calls across processes or across machines. .NET remoting is the name of the technology that enables remote calls in .NET. Chapter 10 is dedicated to .NET remoting and discusses .NET support for location transparency.

1.3.5. Concurrency Management

A component developer can't possibly know in advance all the possible ways in which a component will be used, and particularly whether multiple threads will access it concurrently. The safest course is to assume that the component will be used in concurrent situations and to prepare for this eventuality. One option is to provide some mechanism inside the component for synchronizing access. However, this approach has two flaws. First, it may lead to deadlocks; if every component in the application has its own synchronization lock, a deadlock can occur if two components on different threads try to access each other. Second, it's an inefficient use of system resources for all components in the application to be accessed by the same thread.

To get around these problems, the underlying component technology must provide a concurrency management servicethat is, a way for components to participate in some application-wide synchronization mechanism, even when the components are developed separately. In addition, the underlying component technology should allow components and clients to provide their own synchronization solutions for fine-grained control and optimized performance. .NET concurrency management support is discussed in Chapter 8 as part of developing multithreaded .NET applications.

1.3.6. Versioning Support

Component-oriented programming must allow clients and components to evolve separately. Component developers should be able to deploy new versions (or just fixes) of existing components without affecting existing client applications. Client developers should be able to deploy new versions of the client application and expect them to work with older versions of components. The underlying component technology should support versioning, which allows a component to evolve along different paths and allows different versions of the same component to be deployed on the same machine, or even in the same client process, side by side. The component technology should also detect incompatibility as soon as possible and alert the client. .NET's solution to version control is discussed in Chapter 5.

1.3.7. Component-Based Security

In component-oriented programming, components are developed separately from the client applications that use them. Component developers have no way of knowing how a client application or end user will try to use their work, so a benign component could well be used maliciously to corrupt data or transfer funds between accounts without proper authorization or authentication. Similarly, a client application has no way to know whether it's interacting with a malicious component that will abuse the credentials the client provides. In addition, even if both the client and the component have no ill intent, the end application user can still try to hack into the system or do some other damage (even by mistake).

To lessen the danger, a component technology must provide a security infrastructure to deal with these scenarios, without coupling components and client applications to each other. Security requirements, policies, and events (such as new users) are among the most volatile aspects of the application lifecycle, and security policies vary between applications and customers. A productive component technology should allow for the components to have as few security policies as possible and for there to be as little security awareness as possible in the code itself. It should also allow system administrators to customize and manage the application security policy without requiring changes to the code. .NET's rich security infrastructure is the subject of Chapter 12.

Version Control and DLL Hell

Historically, the versioning problem has been the source of much aggravation. Early attempts at component technology using DLL and DLL-exported functions created the predicament known as DLL Hell. A typical DLL Hell scenario involves two client applications, say A1.0 and B1.0, each using version C1.0 of a component in the mydll.dll file. Both A1.0 and B1.0 install a copy of mydll.dll in some global location, such as the System directory. When version A1.1 is installed, it also installs version C1.1 of the component, providing new functionality in addition to the functionality defined in C1.0. Note that mydll.dll can contain C1.1 and still serve both old and new client application versions, because the old clients aren't aware of the new functionality, and the old functionality is still supported. Binary compatibility is maintained via strict management of ordinal numbers for the exported functions (a source for another set of problems associated with DLL Hell). The problem starts when Application B1.0 is reinstalled. As part of installing B1.0, version C1.0 is reinstalled, overriding C1.1. As a result, A1.1 can't execute.

Interestingly enough, addressing the issue of DLL Hell was one of the driving forces behind COM. Even though COM makes wide use of objects in DLLs, COM can completely eliminate DLL Hell. However, COM is difficult to learn and apply and consequently can be misused or abused, resulting in problems similar to DLL Hell.

Like COM, .NET was designed with DLL Hell in mind. .NET doesn't eliminate all chances of DLL Hell, but does reduce its likelihood substantially. While it's true that the default versioning and deployment policies of .NET don't allow for DLL Hell, .NET is, after all, an extensible platform. You can choose to override its default behavior to meet some advanced need or to provide your own custom version control policy, but in doing so you risk creating DLL Hell.




Programming. NET Components
Programming .NET Components, 2nd Edition
ISBN: 0596102070
EAN: 2147483647
Year: 2003
Pages: 145
Authors: Juval Lowy

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net