Component-Based Software Engineering

Nick McKinney

Department of Software Engineering

University of Wisconsin - Platteville

Platteville, Wisconsin 53818

Abstract

The industrial revolution forever changed the way goods are made by introducing the now commonly accepted idea that complex things are easier to design, construct, and repair if they are composed of standardized interchangeable parts. Component-based software engineering, or CBSE, is an attempt to bring this idea to the world of software engineering. This paper discusses the advantages of building software from components along with some problems unique to component-based software design. Several component models available in modern languages are also discussed.

Introduction

In traditional software development, large, complex software systems are custom-written in-house. There are obvious benefits to creating systems in this way: the resulting systems are optimized for the business needs they were written to address, they may contain secret algorithms that provide a competitive edge, and the organization that implemented the software is not reliant on third-parties for support [11].

On the other hand, modern systems are getting complex enough as to require years to produce and debug, and by that time the business needs they were written to address may have changed, or new technologies may have been invented but are not used by the new system because they did not exist when the system was conceptualized. Brand new systems of nontrivial complexity will, without exception, contain defects; the likelihood of defects being present in new code is simply too high to believe otherwise. The competitive advantage gained by using proprietary algorithms is then quickly nullified by the increased cost of producing the system and by the errors caused by defects in the system [11].

The alternative to producing software from scratch is to gather prefabricated software pieces called components and assemble them into a system. Assembling products out of prefabricated interchangeable pieces was the cornerstone idea that kicked off the industrial revolution, and is a practice followed by every mature engineering discipline [3].

What is a Component?

A component is an autonomous unit of software that provides specific functionality through one or more well-defined interfaces [2][6]. Components allow software systems to be broken down into large interconnected blocks that are easily understood by both system architects and customers [6]. Many of the qualities of components are shared by well-designed classes, but components do not have to be implemented using classes.

Modeling Components

Components are modeled in UML 2.0 using the notation in Figure 1 [2]. The stereotype text «component» and the presence of the component symbol, the rectangle with two smaller rectangles sitting on its left edge, both indicate that this diagram element is supposed to be a component. Only one is required; both are shown here. The component on the right lists provided and required interfaces inside the box with stereotypes denoting what the listed interfaces are. The component on the left shows the same required and provided interfaces, but does so with the new interface symbols provided in UML 2.0 [2].

The line with a circle at its end, sometimes called a lollipop symbol, denotes an interface provided by this component. A line with a half-circle at its end denotes an interface required by this component. In a complete component diagram, all the required interfaces are matched up with an interface provided by another component, as shown in Figure 2 [1]. Note that while a system must provide all required interfaces for all components, there may be unused provided interfaces.

Once a system has been designed using high-level components, those high-level components may be broken down further into lower-level components and classes, as shown in Figure 3 [2]. The components that make up higher-level components are bound together in the same way as high-level components with the provided and required interface symbols. At the boundary of the component being modeled are one or more squares that interfaces provided by or required by that component are attached to. These squares are called ports, and they represent breaches in the component’s encapsulation. All data that enters or leaves the component must do so via a port. The ports are in turn attached to components or classes that make up the component being modeled [2].

It is important to note that the definition of a component has changed between UML 1 and UML 2. In UML 1, the term “component” was used to describe a physical entity that existed outside of the system, such as a data file. This definition was in conflict with the more common definition used in industry, so in UML 2 a component is defined the same way it is in this paper. What was called a component in UML 1 is now called an artifact in UML 2 [2].

Advantages of Using Components

Components are loosely coupled, and can provide a high degree of reuse as a result. Well-designed and well-implemented components, like discrete components on a circuit board, can be used in a variety of applications without needing to modify the source code of the component itself [6]. Similarly, having geographically distributed teams working on different components of a system can be handled easily, since the only times these teams need to communicate is when working out the details of the inter-component interfaces (assuming the components themselves have been adequately specified so there is no ambiguity as to their behaviors) [6].

Components have the property of being interchangeable, allowing for components to be replaced wholesale with alternative components or groups of components that expose the same interfaces as the original component. This allows, for example, the transition from one database system to another simply by replacing the data access components that access the DBMS. Isolation of behavior and responsibility through encapsulation into components makes such changes trivially easy [3].

Since components are easily changeable, software upgrades are no longer atomic operations. A customer does not have to upgrade the entire system at once. If an improved version of a particular component used in a system is released by its author, just that one component can be upgraded, making the whole idea of a system update a little easier to swallow for a customer, since there is not as much transition that needs to happen after upgrading a small piece of the system versus upgrading the entire system to a new version [11].

Software Assembly

When software systems are designed entirely from components, it permits the resulting software to be assembled from those components. Software assembly differs from software fabrication in that no special programming skills are required in order to assemble software. Given a rich enough library of prefabricated components to choose from, a system designer could, in theory, select the right set of components and assemble them in the right way to produce the desired system. In addition, assembling software is a very quick process and requires very little effort when compared to fabricating that same software from scratch [3].

Software fabrication and software assembly have two very different focuses. Fabrication focuses on the how: how can code be written to satisfy the requirements of the system. In contrast, assembly focuses on the what: what does this system need to do [3].

Several programs have been written to aid in assembling software from components. A commonly used one is Microsoft’s Visual Studio .NET, which includes a user interface designer that allows the developer to graphically choose which user interface components to use and to bind them to the application simply by dragging them onto a user interface canvas. Sun’s Bean Builder works in a similar fashion [10].

A more powerful component assembly tool is Apple’s Interface Builder, a technology that they inherited when they bought NeXT Software, Inc. in 1996. Interface Builder has functionality similar to Visual Studio .NET in that the developer can drag user interface components from a toolbox onto a user interface canvas. Developers can also connect components together from within Interface Builder by control-clicking on a component that requires an interface and dragging the pointer to a component that provides that interface. Interface Builder will write code that performs the requested binding automatically when the application is executed. Neither the component requiring an interface nor the provider of that interface need to be user interface components; there’s also a panel off to the side that contains a list of all the non-UI components that Interface Builder knows about, and they may act as interface requirers or providers [7].


Component Models

In order for components to interact with each other, there needs to exist a standardized way for those components to communicate with each other and to discover each other. These mechanisms are provided with component models [9]. At a minimum, a component model defines what constitutes a component and how the components are bound together. Modern component models also provide several basic services to the components and to the system as a whole. These services include class introspection, persistence, support for visual layout and component assembly, and distributed computing support [9].

Introspection is the ability to take an arbitrary object and discover its properties without any prior knowledge of the object. This is probably the most important function provided by a component model, since without it, all component binding must be done at compile time, which significantly reduces the flexibility of component-based software. Both Java and C# support introspection through their object frameworks independent of the component model features [10][8].

Persistence allows the state of an object to be stored and later retrieved; it allows components to persist even if the system is shut down. Components that support persistence are often called serializable components. The storage of a component that supports persistence is called serialization, and the recreation of a component from the stored state is called deserialization [10][8]. Serialized objects may be stored to disk for later use; for example, saving a document could be accomplished by serializing the application’s document editor component to disk. Serialized objects may also be sent across a network connection to another instance of the program, which may be used for distributed computing applications or simply to send data to another user of a system [10][8].

Visual layout and support for component assembly are mostly important for components that are visual in nature, such as graphical user interface components, but may also be useful for non-graphical components. Visual layout simply means that the component is capable of manifesting itself on a graphical user interface for the purpose of laying out that interface (as opposed to being on the interface for normal program operation) [8]. Rudimentary support for visual layout and component assembly can be provided by introspection, but rich support for such functionality must be provided by the components themselves.

Lastly, modern component models support distributed computing by allowing components in a system to reside on different physical machines. Connections are made between components, and the components communicate with each other as if they existed on the same machine. This feature is critical for large systems that span several servers.

Sun JavaBeans

JavaBeans is Sun Microsystem’s component model for components written in Java; components implemented for use within this model are appropriately called beans. JavaBeans supports many features of a modern component model. Introspection is provided through classes in the java.lang.reflect namespace. All objects used in beans are required to provide an implementation of the Serializable interface, used to support object persistence. Individual user interface beans may be assembled using a tool called Bean Builder. Beans are placed on a user interface canvas and can be wired together to support some simple functionality. Java RMI, or remote method invocation, is provided to support distributed applications. Any interface that extends java.rmi.Remote can have its methods invoked remotely [10].

Microsoft .NET

Microsoft’s .NET framework provides all of the component model features that JavaBeans provides. The System.Reflection namespace contains classes that provide object introspection capabilities. Events are first-class citizens in the object model, and the delegate pattern is used to specify receivers for events. All classes that inherit from the ISerializable interface and have the SerializableAttribute attribute applied may be serialized to disk or across a communications channel such as a network socket. Components that are part of the Windows Forms API have classes in the System.Windows.Forms.Design namespace that support laying out the components using the Visual Studio .NET Forms Designer, but the .NET framework does not provide any interfaces for assembling non-graphical components. The .NET framework has a very easy-to-use mechanism for distributed computing called .NET Remoting; any object that inherits from System.MarshalByRefObject is capable of having its methods invoked remotely as if they were being invoked locally; the component model handles all the details of the remote method call [8].

UNIX Shell

While not usually thought of as a component model, the UNIX environment provides an environment that allows command-line tools to interact with each other, and that is the pipes/filters mechanism. Each UNIX shell utility, or component, is an autonomous software entity, has a definite purpose, and has two interfaces for interacting with the environment: standard input and standard output (there’s also standard error, signals, etc., but we’ll ignore them for now for the sake of simplicity). Standard input can be thought of as a required interface — the user is required to provide input — and standard output can be thought of as a provided interface.

The mechanism for assembling these components together is the command line. Two commands on a command line with a pipe (|) between them instructs the shell to take the first program’s standard output, or provided interface, and attach it to the second program’s standard input, or required interface. Likewise, adding another pipe and command will attach the second program’s output to the third program’s input, etc. In this way, smaller programs with a singular purpose can be assembled together in a multitude of ways to accomplish a number of tasks.

For example, the cat, sort, uniq, and head components can be assembled into a system that takes a list of words, removes the duplicates, and prints the five words closest to the beginning of an alphabetical listing of the words. Figure 4 shows the assembled command on the command line and an example of a result using a file containing random words as input.