When it is time to upgrade or replace a chip in a computer, the old chip is simply popped out of the motherboard, and the new chip is plugged in. It doesn't matter if the new chip and old chip have the same manufacturer or the same internal circuitry, as long as they both "look" the same to the motherboard and the other chips in the computer. The same is true for car, television, and sewing machine components. Open architecture systems and "Pluggable" components allow customers to shop around for cheap, third-party generic components, or expensive, third-party high-performance components.
An interface is a collection of operator specifications. An operator specification may include a name, return type, parameter list, exception list, pre-conditions, and post-conditions. A class implements an interface if it implements the specified operators. A software component is an object that is known to its clients only through the interfaces it implements. Often, the client of a component is called a container. If software components are analogous to pluggable computer chips, then containers are analogous to the motherboards that contain and connect these chips. For example, an electronic commerce server might be designed as a container that contains and connects pluggable inventory, billing, and shipping components. A control panel might be implemented as a container that contains and connects pluggable calculator, calendar, and address book components. Java Beans and ActiveX controls are familiar examples of software components.
Modelers can represent interfaces in UML class diagrams using class icons stereotyped as interfaces. The relationship between an interface and a class that realizes or implements it is indicated by a dashed generalization arrow:
Notice that the container doesn't know the type of components it uses. It only knows that its components realize or implement the IComponent interface.
For example, imagine that a pilot flies an aircraft by remote control from inside of a windowless hangar. The pilot holds a controller with three controls labeled: TAKEOFF, FLY, and LAND, but he has no idea what type of aircraft the controller controls. It could be an airplane, a blimp, a helicopter, perhaps it's a space ship. Although this scenario may sound implausible, the pilot's situation is analogous to the situation any container faces: it controls components blindly through interfaces, without knowing the types of the components. Here is the corresponding class diagram:
Notice that all three realizations of the Aircraft interface support additional operations: airplanes can bank, helicopters can hover, and blimps can deflate. However, the pilot doesn't get to call these functions. The pilot only knows about the operations that are specifically declared in the Aircraft interface.
We can create new interfaces from existing interfaces using generalization. For example, the Airliner interface specializes the Aircraft and (Passenger) Carrier interfaces. The PassengerPlane class implements the Airliner interface, which means that it must implement the operations specified in the Aircraft and Carrier interfaces as well. Fortunately, it inherits implementations of the Aircraft interface from its Airplane super-class:
An interface is closely related to the idea of an abstract data type (ADT). In addition to the operator prototypes, an ADT might also specify the pre- and post-conditions of these operators. For example, the pre-condition for the Aircraft interface's takeoff() operator might be that the aircraft's altitude and airspeed are zero, and the post-condition might be that the aircraft's altitude and airspeed are greater than zero.
Java allows programmers to explicitly declare interfaces:
interface Aircraft {
public void takeoff();
public void fly();
public void land();
}
Notice that the interface declaration lacks private and protected members. There are no attributes, and no implementation information is provided.
A Pilot uses an Aircraft reference to control various types of aircraft:
class Pilot {
private Aircraft myAircraft;
public void fly() {
myAircraft.takeoff();
myAircraft.fly();
myAircraft.land();
}
public void setAircraft(Aircraft a) {
myAircraft = a;
}
// etc.
}
Java also allows programmers to explicitly declare that a class implements an interface:
class Airplane implements Aircraft {
public void takeoff() { /* Airplane takeoff
algorithm */ }
public void fly() { /* Airplane fly
algorithm */ }
public void land() { /* Airplane land
algorithm */ }
public void bank(int degrees) { /* only
airplanes can do this */ }
// etc.
}
The following code shows how a pilot flies a blimp and a helicopter:
Pilot p = new Pilot("Charlie");
p.setAircraft(new Blimp());
p.fly(); // Charlie flies a blimp!
p.setAircraft(new Helicopter());
p.fly(); // now Charlie flies a helicopter!
It is important to realize that Aircraft is an interface, not a class. As such, it cannot be instantiated:
p.setAircraft(new Aircraft()); // error!
Java also allows programmers to create new interfaces from existing interfaces by extension:
interface Airliner extends Aircraft, Carrier {
public void serveCocktails();
}
Although a Java class can only extend at most one class (multiple inheritance is forbidden in Java), a Java interface can extend multiple interfaces and a Java class can implement multiple interfaces. A Java class can even extend and implement at the same time:
class PassengerPlane extends Airplane implements Airliner {
public void add(Passenger p) { ... }
public void rem(Passenger p) { ... }
public void serveCocktails() { ... }
// etc.
}
Names of abstract classes and virtual functions are italicized in UML:
Consider the relationship: "Person P owns shares in company C." We might represent this relationship as a simple association between the Person class and the Company class:
Now consider the relationship: "Person P owns N shares in company C." Where should the number of shares, N, be recorded? It wouldn't make sense to store this information in the Person class, because a person might own different numbers of shares of different companies. Similarly, it wouldn't make sense to store this information in the Company class, because a company might have many share holders, each owning different numbers of shares.
The number of shares owned is clearly a property of the link between a person and a company. Unfortunately, until now we have been representing links by C++ pointers or Java references, and neither of these can bear attributes. It's time to take a more object-oriented view of links. Instead of pointers or references, we can represent links as objects and associations as classes.
For example, suppose Smith owns 50 shares of IBM. Thus, there is a link between Smith and IBM, which is an instance of the owns association. We can represent this link as an object that instantiates the Owns association class, is linked to both Smith and IBM, and has a quantity attribute with value 50:
Notice that Smith and IBM are no longer directly linked, but are instead linked through the Owns object.
An object representing a link is called a link object. A link object is an instance of an association class. In UML we depict association classes using a class icon connected to an association with a dashed line:
class Person
{
set<Owns*> holdings;
// etc.
};
class Company { ... };
class Owns
{
Person* owner;
Company* holding;
int quantity;
void addShares(int quant) { quantity +=
quant; }
// etc.
};
Of course we must take special care to avoid inconsistencies in the C++ implementation. This might be accomplished by making Owns a friend of the Person class. A single public constructor is provided:
Owns::Owns(Person* p, Company* c, int quant)
{
quantity = quant;
holding = c;
owner = p;
(p->holdings).insert(this);
}
class Person {
private Set holdings = new TreeSet();
public addHolding(Owns h) {
holdings.add(h);
}
// etc.
}
class Company { ... }
class Owns {
private Person owner;
private Company holding;
private int quantity;
public void addShares(int quant) {
quantity += quant; }
public Owns(Person p, Company c, int
quant) {
quantity = quant;
owner = p;
holding = c;
p.addHolding(this);
}
// etc.
}
Now consider the relationship "Person P purchases N shares of Company C." Again we might represent this relationship as a simple association between the Person class and the Company class. Again we are confronted with the problem of where to store the quantity attribute, N. Clearly, purchases should be represented as instances of a Purchase class with a quantity attribute. In fact, the implementation of the Purchase class would be almost identical to the implementation of the Owns class shown above.
However, the Purchase class is not considered to be an association class. This is because Smith might purchase 20 shares in IBM today and 30 shares tomorrow. Each purchase is an instance of the Purchase class that links Smith and IBM. Of course there will only be a single instance of Owns that links Smith and IBM:
The point is that the identity of a link object is completely determined by the objects it links together, not its attributes. There will only be one instance of the Owns class that links Smith with IBM, while there can be many instances of the Purchase class, each distinguished by the quantity purchased (and perhaps other attributes such as date of purchase and price per share paid).
To put it another way, the owns N shares relationship is a set or links (repeat members disallowed), while the purchases N shares relationship is a bag or multi-set of links (repeat members allowed). This difference might be reflected in the implementation of the Person and Company classes. Here's the C++ version:
class Person
{
set<Owns*> holdings;
multi_set<Purchase*> purchases;
// etc.
};
and here's a possible Java version:
class Person {
private Set holdings = new TreeSet();
priavte List purchases = new Vector();
// etc.
}
No special notation is needed to represent the Purchase class in a class diagram:
Although Purchase isn't an association class, it is an example of a reified class. Reification means objectification: the tendency to view abstractions (such as relationships, events, aggregations, processes, and concepts) as concrete objects. Instances of the Purchase class represent stock purchasing events. The ontological status of a stock purchasing event is a bit more nebulous than a company or a person. A person is a physical object in the real world, a purchasing event is not. (The ontological concreteness of a company is somewhere between the person and event.) Reification is an important modeling tool that is used extensively in science and mathematics. (Quantum mechanics is a good example of reification.)
Instances of a reified class represent abstractions in the application domain. Both the Owns and the Purchase classes are reified classes.
class Registry {
private Map<String, Server> table
= new Hashtable<String,
Server>();
public void register(String name,
Server s) {
table.add(name, s);
}
public void unregister(String name) {
table.remove(name);
}
public Server find(String name) {
table.get(name);
}
}
A power type is a special kind of meta class in which instances represent subclasses of a particular class. For example, there are many types of aircraft: airplanes, blimps. helicopters, space ships, etc. We could represent this situation by introducing blimp, helicopter, and airplane as subclasses of an aircraft base class:
Alternatively, we could define a single Aircraft class and represent the different types of aircraft by different instances of an AircraftType power type. In this case it's common for instances of the power type to act as a factories that create instances of the type of objects they represent, the same way a class would provide a constructor for creating instances. It is also common for products created by a power type object to carry a type pointer back to the factory that created them:
In UML we can also represent a power type with a dashed line connecting it to the hierarchy of subclasses it represents:
Instances of power types are often factories and instances of the subclasses represented by a power type instance are the products this factory creates. In C++ we can force clients to create objects by calling power type factory methods by making constructors private and by declaring the power type to be a friend class:
class Aircraft
{
public:
void takeoff() { ... }
void fly() { ... }
void land() { ... }
AircraftType* getType() { return
myType; }
private:
AircraftType* myType; // = blimp,
plane, saucer, etc.
Aircraft() {}
Aircraft(AircraftType* t) { myType = t;
}
Aircraft(const Aircraft& a) {}
friend class AircraftType;
};
class AircraftType
{
public:
AircraftType(string n) { name = n; }
string getName() { return name; }
Aircraft* makeAircraft() { return new
Aircraft(this); }
private:
string name; // = blimp, plane, saucer,
etc.
};