Inheritance and Interfaces in Java and UML
Welcome to the second article in a series introducing UML and object modeling from a Java programmer's perspective. In the previous article, I introduced UML class diagrams, comparing the way classes, attributes, operations, and associations are represented in the Unified Modeling Language (UML) and the Java programming language. This article considers the two Is of UML class diagrams: inheritance and interfaces.
Inheritance
In Java, we may declare that a class extends another class and implements one or more interfaces. Let's take a look at how we represent each of these ideas in UML class diagrams.
Extends
Here are the bare bones of three classes written in Java. The first is an abstract class representing a payment of some sort. The other two classes each extend the Payment class and represent two different methods of payment.
/** an abstract class representing a payment of some kind */ abstract public class Payment { public Payment() { } public Payment(BigDecimal amount) { this.amount = amount; } public BigDecimal getAmount() { return amount; } public void setAmount(BigDecimal amount) { this.amount = amount; } private BigDecimal amount; } /** a subclass that extends the Payment class to represent credit card payments */ public class CreditCardPayment extends Payment { public CreditCardPayment() { } public CreditCardPayment(BigDecimal amount) { super(amount); } public String getCardNumber() { return cardNumber; } public void setCardNumber(String cardNumber) { this.cardNumber = cardNumber; } public boolean authorize() { return false; //not yet implemented } private String cardNumber; } /** a subclass that extends the Payment class to represent cash payments */ public class CashPayment extends Payment { public CashPayment() { super(); } public CashPayment(BigDecimal amount) { super(amount); } public BigDecimal getAmountTendered() { return amountTendered; } public void setAmountTendered(BigDecimal amountTendered) { this.amountTendered = amountTendered; } private BigDecimal amountTendered; public BigDecimal calcChange() { return amountTendered.subtract(super.getAmount()); } }
Figure 1 shows the same three classes in UML. As is often the case, details such as types and parameters have been omitted from the operations and attributes so that the overall structure of the classes and their relationships shows through clearly.
Figure 1 UML generalization relationships (the equivalent of Java extends).
The extends keyword in Java declares inheritance of both interface and implementation. UML has an equivalent generalization relationship that is drawn as a solid line with a closed arrowhead from the subclass to the superclass. The additional Sale class helps illustrate the difference between the type of arrowhead used in the UML generalization relationships and those used in directed UML association relationships. Another difference is that, unlike associations, generalization relationships have no need for multiplicities or role names at the ends of the relationship.
I think you will agree that it is easier and quicker to see the inheritance relationships between the three classes from the UML class diagram in Figure 1 than from looking at three separate Java source code files. It is also far quicker to sketch the UML class diagram on a whiteboard or flipchart than to type up the Java source code when discussing the design with a customer or fellow developer.
NOTE
Some argue that they keep the class structure of a system in their head, so they need to work only with Java source code. This is, of course, nonsense for larger systemsand difficult for even small systems that have changes applied by different people over a significant period of time. It also has the drawback that there is no efficient way, short of a Vulcan mind-meld, of ensuring that the structures contained in each team member's head are consistent.
An abstract class in UML is identified by writing the class name in italics. This can be almost impossible to distinguish when sketching models on flipcharts or whiteboards. Some people recommend using a tag value of {abstract} in the bottom-right corner of the class name compartment in these circumstances.
Personally, I find the use of {abstract} too verbose when working on a whiteboard, so I tend to break from standard UML in this situation and just write a 0 for zero instances in the bottom-right corner of the class name compartment. I also use 1 in that position to indicate a singleton class (a class that only ever has one instance) and, when necessary for clarity, I use N to represent an enumeration (a class that has a fixed number of instances representing things such as the days of the week or colors of the rainbow, and so on). However, this is only my own shorthand convention for informal whiteboard/flipchart work. It is not standard UML, and is unlikely to be supported by any UML modeling tool.
Historical Note
UML was first devised by a team working at Rational Corporation, the producer of the Rose UML modeling tool. UML was unveiled at OOPSLA in 1995. The UML specification was subsequently adopted by the Object Management Group (OMG) in 1997. The OMG task force that continues to develop the UML specification quite understandably has representation from nearly all the leading UML tool vendors. It is, therefore, not surprising if some of the UML notation is inconvenient when working with whiteboards and flipcharts instead of with software tools.
The Problem With Inheritance
Inheritance of the type signified by the extends keyword in Java is a very powerful tool. It allows one class to make use of attributes and methods of another class as if they were its own. When first introduced, inheritance of this sort was seen as a wonderful mechanism for reusing existing code. Unfortunately, large inheritance trees tend to be brittle, and changes in one part of the tree can force a ripple of changes throughout the tree. This is contrary to the principle of localizing change that underpins the idea of encapsulation in object-oriented programming. Thankfully, the ease in which inheritance trees can be viewed in UML also makes it easy to apply guidelines on the use of this type of inheritance. The following guidelines are adapted from Peter Coad's Java Design book1:
For superclass A and subclass B apply the following checks:
The phrase "B is a role played by a A" does not make sense.
B never needs to transmute to be an object in some other class.
B extends rather than overrides or nullifies the behavior of A.
A is not merely a utility class (useful functionality you want to reuse).
For a problem domain (business objects): Both A and B define the same kind of object; either user transactions, roles, entities (party, place or thing), or similar categorizations of other objects.
If any of the above checks fail, then it is likely to be an inappropriate use of inheritance, and a design using associations would be more robust. For example, Figure 2 falls foul of check 1 because "Employee is a role played by a Person" makes sense as an English statement. It also falls foul of check 2 because an employee would have to change class if they want to be a customer at some point in time. A person wanting to be an employee and a customer at the same time needs to be represented by two distinct objects, duplicating the information in the Person class and introducing the risk of data integrity problems between the two copies.
Figure 2 Inappropriate use of extends.
Figure 3 shows a refactored design using associations. Now, a person can be either an employee or a customer (or both) at the same time or over time.
Figure 3 Refactored to use associations instead.