The UML Profile for Framework Architectures
4.3 Terminology and concept excursion: abstract classes, abstract coupling, Java interfaces
Classes that define common behavior for a set of subclasses usually do not represent instantiable classes but abstractions thereof they are called abstract classes. It does not make sense to generate instances of abstract classes since some methods are abstract and have empty or no implementations. The general idea behind abstract classes is clear and straightforward.
Properties (that is, attributes and methods) of similar classes are defined in a common superclass.
Some methods of the resulting abstract class can be implemented, while only empty or no implementations can be provided for others, which are termed abstract methods. The RoundingPolicy class is so simple that it only contains one abstract method, round(). Though abstract methods cannot be implemented, their names and parameters create a standard class signature for all descendants, since descendants cannot change the method interface. Instances of all descendants of an abstract class understand at least all messages that are defined in the abstract class. (A descendant class inherits from another class, but not necessarily as a direct subclass.)
Sometimes the term protocol is used for this standardized signature instances of descendants of a class A support the same signature as supported by instances of A. Conceptually, a signature and a Java interface are the same. The differences between an abstract class and a Java interface are explained below.
4.3.1 Abstract classes and abstract coupling
The purpose of an abstract class A is to provide a signature[4] so that other pieces of software, such as a component B, can be implemented based on the signature of A. B relies on the signature supported by the abstract class that is, B is abstractly coupled with the abstract class A. In an adapted framework an implementation for the signature of the abstract class A is provided through a concrete subclass A1, which the component B does not know of. Most importantly, these components interact properly, that is without change and recompilation, with instances of all future extensions of the abstract classes.
[4] In addition to that, abstract classes can also implement methods and define instance variables. Java interfaces can only provide a signature.
In general, the key is to find useful abstractions so that software components can be implemented without knowing the specific details of the concrete objects they rely on. Framework development projects corroborate that difficulty, as it sometimes takes years to refine abstractions.
Usually an instance variable maintains a coupling relation between two classes. Other possibilities are global variables or temporary relations by passing object references via method parameters. As the actual coupling between abstractly coupled classes is a rather irrelevant implementation detail, this issue is not discussed in further detail. The same is true for the implementation of a UML association between two classes.
Note that the Separation principle is closely related to abstract coupling. Both terms can be used interchangeably in most contexts. A small difference is that the Separation principle does not require the class with the hook to be an abstract one. For example, the rounding policy case study could abandon the abstract class RoundingPolicy. Instead, the CurrencyConverter could be coupled with the concrete class DefaultRPol. RPol10, RPol100, and SpecialRPol would then be subclasses of DefaultRPol.
4.3.2 Java interfaces
Java supports a separation of interface and class definitions. In other typed object-oriented languages, such as C++, the type hierarchy is only defined by (and is identical to) the class hierarchy only descendants of a class A are type compatible to A. Java breaks this up by offering the interface language construct. C# also provides the interface construct. Thus the following discussion applies to C# as well.
Interface and class definitions both represent type definitions. Therefore the class hierarchy in Java acts as type hierarchy. But classes can additionally implement one or more interfaces. So additional types can be defined independently of the class hierarchy.
In Java, interfaces are defined similarly to classes with the keyword interface instead of class. Analogous with abstract methods, only the signature of a method is defined, not its implementation. So neither method implementations nor any state variables (instance variables or attributes in UML terminology) are present in an interface. Example 4.7 shows the definition of an interface and a class that implements this interface. If a class definition says it implements an interface, it has to implement all methods specified in the particular interface. Classes can implement several interfaces by separating the list of interfaces with commas.
Example 4.7 Sample interface definition and implementation
public interface SampleInterface { public void method1(); public void method2() ; } public class A extends AnotherClass implements SampleInterface { public void method1() { // implementation of // method1 } public void method2() { // implementation of // method2 } }
Variables of static type[5] SampleInterface can refer to instances of classes that implement the interface (see Example 4.8). There is another difference between class and interface hierarchies. In Java, a class can only inherit from one other class (single inheritance), while an interface can inherit from any number of interfaces (multiple inheritance). As interfaces only define method signatures, multiple inheritance of interfaces causes no problems as no method implementations are inherited.
[5] In statically typed languages, such as Java, C#, and C++, a variable has exactly one static type as specified in the variable declaration in the (static) program text. On the other hand, a variable can have any number of dynamic types. The dynamic type of a variable is the type of the object the variable refers to. In the context of interfaces, a variable wih the static type of an interface can refer to any object that is an instance of a class that implements that interface.
Example 4.8 Type compatibility
SampleInterface si; si= new A();
From the perspective of construction principles, Java interfaces are equivalent to abstract classes. Thus, it does not matter whether a class is coupled with an abstract class or an interface. In case of an abstract class, subclasses define the specific behavior in the overridden methods. In case of an interface, any class in the class hierarchy can implement the interface, no matter whose subclass it is. The implementation of interface methods corresponds to the overriding of abstract methods in abstract classes.
The abstract class RoundingPolicy in the earlier case study could, and probably should, become an interface. A rule of thumb is to prefer interfaces, if the resulting entity does not have a minimum number of method implementations that justifies an abstract class definition. The consequence of defining RoundingPolicy as an interface would simply be that the classes DefaultRPol, RPol10, RPol100, and SpecialRPol would not be subclasses of RoundingPolicy, but would implement its round() method. Figure 4.20 shows the corresponding UML diagram. Any class that offers an appropriate round() method could implement the RoundingPolicy interface.
Figure 4.20. RoundingPolicy as an interface
Example 4.9 summarizes the modifications in the source code. There are no changes necessary in class CurrencyConverter.
Example 4.9 Hook as Java interface
public class CurrencyConverter { RoundingPolicy rPol= new DefaultRPol(); public void convert(...) { double result, value; switch (...) { case ... : result= rPol.round(value); break; case ... : result= rPol.round(value); } } public void defineRoundingPolicy (RoundingPolicy rp) { rPol= rp; } } public interface RoundingPolicy { public double round(double val); } public class DefaultRPol implements RoundingPolicy { public double round(double val) { ... // do a four digit after comma rounding // of 'val' } }