Software development processes have not really changed much over the years. Most software development efforts are beleaguered by cost and time overruns, and the results are often bug-ridden and impossible to maintain. Over the years, a number of paradigms and methodologies, from flowchart creation to object orientation, have been offered and sometimes accepted for varying lengths of time as a panacea for these problems. Over time, all of these purported solutions have failed to meet expectations, and no reliable substitute has been found for what can be achieved by small groups of individuals, working on one project at a time. This is not to say that flowcharts are unhelpful or that object-oriented designs are flawed, but simply that they do not reduce software development to a formula that guarantees results. Perhaps these paradigms have been oversold, or perhaps users have expected too much of them. The time has come to realize that software development is inherently difficult and fraught with many problems that have no single solution.
Object-oriented programming is one of the more recent paradigms to enjoy a long and somewhat favorable reception by the software industry. This acceptance is reflected in the popularity of object-oriented programming languages such as Ada, Smalltalk, Java, and C++. Each of these languages carries the object-oriented flag at varying heights, depending on the original areas of expertise of their designers, the problems they set out to solve, and the limitations they faced. Smalltalk is an example of what happens when object-oriented ideas are carried to an extreme. C++, on the other hand, takes a pragmatic view of object-oriented programming, although this can be traced mostly to its roots in C and its stringent compatibility requirements with that language. And although Microsoft Visual Basic is not fully object-oriented, object-oriented ideas have permeated its design as it has evolved.
In a nutshell, the goal of object-oriented programming techniques is not to facilitate programming in a procedural manner that mimics the logic of a computer, but to allow developers to write software that deals with the way individuals think in the real world. Most software attempts to model, or "virtualize," things that we work with every day. Object-oriented programming languages allow developers to better express the existence of objects directly in the code. Disciples of the object-oriented school of programming believe that these techniques lead to more expressive code that is easier to develop and less costly to maintain.
Most object-oriented programming languages make an important distinction between an object and a class. A class is a template that defines its members. An object is an instance of a particular class and can actually do things. This relationship can be illustrated with the following example. Compare a cookie cutter and a cookie. A cookie cutter is a template (class) that defines various attributes of cookies, such as shape and size. A cookie, on the other hand, is analogous to an object because the cookie is created based on the cookie cutter.
To help programmers make better use of the object-oriented paradigm, most object-oriented programming languages provide support for the following three concepts:
Object-oriented programming became as popular as it did largely because it allowed developers to share code among entirely different projects. As mentioned, the redevelopment of similar code and algorithms occurs all the time, resulting in an incredible waste of time, effort, and money. While code sharing and reuse is considered a primary benefit of a well-implemented object-oriented design, the percentage of code actually being shared is still small. Until recently, even applications in suites such as Microsoft Office had different code to implement standard features such as toolbars, status bars, and spell checkers. Many of these standard graphical user interface (GUI) controls have been built into recent versions of Windows, allowing all applications to share them. If you think about it, an operating system is a great (but not tremendously flexible) example of code reuse.
Code reuse is one of those things that everyone assumed would happen spontaneously. This turned out to be wishful thinking, since code reuse needs to be planned for and its implications carefully thought through. If you write some code and then give it to friends so that they can use it, is that code reuse? What about code libraries that programmers link to their applications? While these are examples of code reuse, each has its problems. If you give your code to your friends and they don't like some aspects of it, they might go into your source code and make modifications. This tinkering is not in keeping with the idea of code reuse. Modifying someone else's source code is like breaking a figurine in a china store—the hapless browser becomes the proud new owner. If something doesn't work after someone changes your code, you are no longer obliged to support it. In addition, when you later update your own code and then make available the new version, your friends have to go through it and integrate their own changes anew. The code is then manifestly not reusable. If you purchase a class library and you don't like the way it works, that's too bad unless you also buy the source code so you can alter it, and that brings you back to the previous problem.
To better understand code reuse, we need a more solid definition. True code reusability means that the code must be written in a general enough manner for reuse to build something larger, while still being customizable in the way that the code works and what it does. Another problem with most types of code reuse is that they normally require the original developer and the person who wants to reuse the code to work in the same programming language. If a class library is written in C++, for example, it is basically impossible to reuse that code in an application written in any other language. By the same token, a Java class can be used only in a Java program. So although you often get more software reuse using an object-oriented programming language than if you don't, you still face limitations. How, then, can we apply code sharing and reuse to practical, real-life programming? While object-oriented programming has long been advanced as a solution to the problems at hand, it has yet to fulfill this promise.
The breakdown of a project into its logical components is the essence of object-oriented analysis and design. That's also the basis for component software, which is composed of reusable pieces of software in binary form (as opposed to source code) that can be plugged into components from other vendors with relatively little effort. It is important to realize that a component-based approach to software development does not dictate the structure of an application. Rather, it is a model that makes possible the programming, use, and independent evolution of binary software components. Components are independent of the applications that use them as well as the programming languages used to create them.
Software components can be divided into a variety of categories, including visual components, such as buttons or list boxes, and functional components, such as ones that add printing or spell checking capability. For example, a component-based architecture might enable spell checker components from multiple vendors to be plugged into a single word processing program from another vendor. This would hold many advantages for the end user. A user might love the word processor produced by company A but hate the spell checker it comes with. If the word processor was designed so that the spell checker component can be replaced, the user can purchase a spell checker from company B, which specializes in creating spell checkers. Component software enables software developers to specialize in what they do best.
A good analogy for component software can be found in the automobile industry, in which car manufacturers often buy individual car parts, such as engines and transmissions, from various manufacturers and then assemble cars from these components. With component software, the pieces can be used as they are—they don't need to be recompiled, you don't need the source code, and you aren't restricted to one programming language. The term for this process is binary reuse, because it is based on interfaces rather than on reuse at the source code level. While the software components must adhere to the agreed upon interface, their internal implementation is completely autonomous. For this reason, you can build the components using procedural languages as well as object-oriented languages.
One of the main goals of a component-based programming model is to promote interoperability. Interoperability is one of those buzzwords in the computer field that means different things to different people. In the context of component software, interoperability simply means the ability of components to work together. When you examine a project and notice areas in which software seems to be forced together unnaturally, the application should be divided into components.
Let's use the example of controls. In the good old days of Windows programming, if your program needed a toolbar, you simply wrote the toolbar code directly into the main part of the application. This approach had two inherent problems. First, your goal was probably not to create a cool toolbar but to create a great application with a toolbar. Second, after you consumed many precious hours developing a toolbar, if other developers in your company wanted to use that toolbar in their projects, they couldn't easily reuse it if the code was sprinkled throughout the program, responding to WM_CREATE, WM_PAINT, and WM_LBUTTONDOWN messages.
A component-based approach to this problem would make the toolbar into a separate component. The problem then becomes how the toolbar component and the application should interact—the whole issue of interoperability. The component software paradigm mandates that all components define an interface that exposes the functionality available in that component. As long as the component implements the interface and the client applications abide by it, interoperability results. In the toolbar example, the application developers could simply purchase the toolbar component from some other developer who specializes in toolbars, thus saving development, debugging, and maintenance time.
In the software world, there is no better example of the impact of components than that of ActiveX controls. An ActiveX control is a type of COM+ component that is typically placed on a form, where it can interact with the user. The user perceives the ActiveX control and the form as a single application, even though the two pieces of software were developed independently, perhaps even in different programming languages, and are not compiled together. The control and the form work together through interfaces. The form interrogates the control to learn what interfaces it supports, and the control does the same to the form. You can use object-oriented programming concepts in conjunction with a component-based approach to build flexible and powerful objects that can easily be reused by other developers.
One important principle of object-oriented programming, mentioned earlier, is encapsulation. Encapsulation hides the implementation of an object from users of the object. The users of an object have access only to the object's interface. Developers who use prebuilt objects in their projects are interested only in the "contract"—the promised behavior that the object supports. Component software formalizes the notion of a contract between an object and a client. Each object declares what it is capable of by implementing certain interfaces. The only way to access the services of an object is through the interfaces that it supports. Such a contract is the basis for interoperability.
An interface is actually a very simple thing—a semantically related set of methods grouped together under one name. For now, this will be our working definition of an interface. For example, the Win32 API is an interface to the functionality of the Windows operating system. With component software, not only can operating systems make an interface available, but so can software components built by us ordinary folk. An interface is a strongly typed contract between a software component and its clients; it is an articulation of an expected behavior and expected responsibilities, and it gives programmers and designers a concrete entity to use when referring to the component. Two objects that implement the same interfaces are said to be polymorphs. Although not a strict requirement of the model, you should factor interfaces in so that they can be reused in a variety of contexts.