The Open-Closed Principle


Preconditions, Postconditions, and Class Invariants

Preconditions, postconditions, and class invariants are the three cornerstones of both the LSP and DbC. Their definitions and application are discussed in this section.

Class Invariant

A class invariant is an assertion about an object property that must hold true for all valid states an object can assume. For example, suppose an airplane object has a speed property that can be set to a range of integer values between 0 and 800. This rule should be enforced for all valid states an airplane object can assume. All methods that can be invoked on an airplane object must ensure they do not set the speed property to less than 0 or greater than 800.

Precondition

A precondition is an assertion about some condition that must be true before a method can be expected to perform its operation correctly. For example, suppose the airplane object’s speed property can be incremented by some value and there exists in the set of airplane’s public interface methods one that increments the speed property anywhere from 1 to 5 depending on the value of the argument supplied to the method. For this method to perform correctly, it must check that the argument is in fact a valid increment value of 1, 2, 3, 4, or 5. If the increment value tests valid then the precondition holds true and the increment method should perform correctly.

The precondition must be true before the method is called, therefore it is the responsibility of the caller to make the precondition true, and the responsibility of the called method to enforce the truth of the precondition.

Postcondition

A postcondition is an assertion that must hold true when a method completes its operations and returns to the caller. For example, the airplane’s speed increment method should ensure that the class invariant speed property being 0 <= speed <= 800 holds true when the increment method completes its operations.

An Example

Example 24.1 gives the code for a class named Incrementer. An incrementer object can simply be incremented by the values 1, 2, 3, 4, or 5 and maintain a state value between 0 and 100. This example illustrates one approach to enforcing method preconditions and postconditions via the Java assertion mechanism.

Example 24.1: Incrementer.java

image from book
 1     public class Incrementer { 2       /********************************************** 3         Class invariant: 0 <= Incrementer.val <= 100 4        **********************************************/ 5        private int val = 0; 6 7        /******************************************** 8          Constructor Method: Incrementer(int i) 9           precondition: ((0 <= i) &&    (i <= 100)) 10         postcondition:  0 <= Incrementer.val <= 100 11       ********************************************/ 12       public Incrementer(int i){ 13         assert((0 <= i) && (i <= 100)); 14         val = i; 15         System.out.println("Incrementer object created with initial value of: " + val); 16         checkInvariant();  // enforce class invariant 17       } 18 19       /********************************************** 20               Method: void increment(int i) 21         precondition: 0 < i <= 5 22        postcondition: 0 <= Incrementer.val <= 100 23       *********************************************/ 24       public void increment(int i){ 25         assert((0 < i) && (i <= 5)); // enforce precondition 26 27         if((val+i) <= 100){ 28           val += i; 29         }else{ 30           int temp = val; 31           temp += i; 32           val = (temp - 100); 33          } 34 35          checkInvariant();           // enforce class invariant 36 37          System.out.println("Incremeter value is: " + val); 38       } 39 40      /********************************************* 41        Method: void checkInvariant() - called 42        immediately after any change to class 43        invariant to ensure invariant condition 44        is satisfied. 45       *******************************************/ 46       private void checkInvariant(){ 47         assert((0 <= val) && (val <= 100)); 48       } 49    }// end Incrementer class definition
image from book

Referring to example 24.1 — the Incrementer class has a private instance field of type int named val. The class invariant is specified in comments above the field declaration and indicates that the valid range of values val can assume is 0 through 100. This invariant is enforced with the Java assertion mechanism in the body of the constructor when an instance of Incrementer is created. The invariant is also validated via the checkInvariant() method.

The increment() method’s pre- and postconditions are stated in the comment above the method. It indicates that the valid range of increment values the parameter i can assume include 1 through 5, and that when the method completes the class invariant state must be valid. The increment() method’s precondition is checked by the assert statement on line 25. The class invariant is checked by calling the checkInvariant() method on line 35.

To compile this class using Java version 1.4 you must inform the Java compiler to generate Java 1.4 compatible source code using the -source switch in the following manner:

 javac -source 1.4 Incrementer.java

Example 24.2 gives a short program putting the Incrementer class through its paces.

Example 24.2: MainTestApp.java

image from book
 1     public class MainTestApp { 2       public static void main(String[] args){ 3         Incrementer i1 = new Incrementer(0); 4 5         i1.increment(1); 6         i1.increment(2); 7         i1.increment(3); 8         i1.increment(4); 9         i1.increment(5); 10        i1.increment(6); // throws an AssertionError exception as expected 11 12     } // end main() method 13    }// end MainTestApp clas definition
image from book

Referring to example 24.2 — an Incrementer reference named i1 is declared and initialized on line 3. On lines 5 through 10 the increment method is called via i1 with different increment values 1, 2, 3, 4, 5, and 6. If assertions have been enabled in the JVM when this example is executed then it will throw an AssertionError exception when line 10 executes. To enable assertions in the JVM start the MainTestApp program using the enable assertions ( -ea ) switch in the following manner:

 java -ea MainTestApp

Figure 24-1 shows the results of running this program.

image from book
Figure 24-1: Results of Running Example 24.2

A Note On Using Assertions To Enforce Pre- and Postconditions

As was just demonstrated, the assert keyword is ignored in Java version 1.4 unless the -source switch is used during compilation. Also, assertions are not enabled in the JVM unless it is started with the -ea switch. The use of the assertion mechanism to enforce method pre- and postconditions, and the state of class invariants, is best used during implementation and testing. Remember, it is the responsibility of the calling program to adhere to a method’s documented precondition.

Consider for a moment the MainTestApp program shown in example 24.1. When a programmer runs this code and gets the error produced by trying to call the increment method with an invalid precondition, he would then be obliged to fix his code to eliminate the error. From this point forward the assertion mechanism can be safely disabled and the code will run fine.

Using Incrementer As A Base Class

A programmer using the Incrementer class learns from reading its class invariant, precondition, and postcondition specifications how those objects can be used in a program and how they should behave. And that is all they should have to know, even when an Incrementer reference points to an object belonging to a class that is derived from Incrementer.

There are several issues that demand the attention of the programmer who plans to extend the functionality of Incrementer. First, he must be aware of the point of view of the client program that will use the derived object. That code expects certain behavior from Incrementer objects. For example, a client program calling the increment() method on Incrementer objects can rely on proper behavior if the arguments to the method satisfy the precondition of being greater than zero or less than or equal to five. If an object derived from Incrementer is substituted at runtime for an Incrementer object, the derived object must not break the client code by behaving in a manner not anticipated by the client program.

Second, with the expectations of the client code in mind, what rules should a programmer follow when extending the functionality of a base class to ensure the derived object continues to live up to or meet the expectations of the client code? This section explores these issues further.

Example 24.3 gives the code for a class named DerivedIncrementer that extends the functionality of the Incrementer class.

Example 24.3: DerivedIncrementer.java

image from book
 1     public class DerivedIncrementer extends Incrementer { 2       /******************************************** 3         Class invariant: 0 <= val <= 50 4       ******************************************/ 5       private int val = 0; 6 7       /********************************************** 8         Constructor Method: DerivedIncrementer(int i) 9               precondition: ((0 <= i) && (i <= 50)) 10             postcondition: 0 <= val <= 50 11       **********************************************/ 12      public DerivedIncrementer(int i){ 13        super(i); 14        assert((0 <= i) && (i <= 50));  // enforce precondition 15        val = i; 16        System.out.println("DerivedIncrementer object created with value: " + val); 17        checkInvariant(); 18      } 19 20      /********************************************* 21               Method: void increment(int i) 22         precondition: ((0 < i) && (i <= 5)) 23        postcondition:  0 <= val <= 50 24      ********************************************/ 25      public void increment(int i){ 26        assert((0 < i) && (i <= 5)); // enforce precondition 27        super.increment(i); 28        if((val+i) <= 50){ 29           val += i; 30        }else{ 31          int temp = val; 32          temp += i; 33          val = (temp - 50); 34         } 35         checkInvariant();  // check invariant 36         System.out.println("DerivedIncrementer value is: " + val); 37      } 38 39      private void checkInvariant(){ 40        assert((0 <= val) && (val <= 50)); 41      } 42    } // end DerivedIncrementer class definition
image from book

Referring to example 24.3 — the DerivedIncrementer class extends Incrementer and overrides its increment() method. DerivedIncrementer has its own val field which has a different class invariant from that of Incrementer’s val. (But this is perfectly OK!) The DerivedIncrementer’s version of the increment() method subscribes to the same precondition as that of the base class version of the method it is overriding, namely, that the values of the integer parameter i can be anything from 0 through 5. Therefore, an object of type DerivedIncrementer will behave the same as objects of type Incrementer. Example 24.4 shows the DerivedIncrementer class in action.

Example 24.4: MainTestApp.java (mod 1)

image from book
 1     public class MainTestApp { 2       public static void main(String[] args){ 3         Incrementer i1 = new Incrementer(0); 4         Incrementer i2 = new DerivedIncrementer(20); 5 6         i1.increment(1); 7         i1.increment(2); 8         i1.increment(3); 9         i1.increment(4); 10        i1.increment(5); 11        System.out.println("-----------------------------"); 12        i2.increment(4); 13        i2.increment(5); 14        i2.increment(6); // throws AssertionError exception here as expected 15 16     } // end main() method 17    }// end MainTestApp clas definition
image from book

Referring to example 24.4 — an Incrementer type reference named i2 is declared on line 4 and initialized to point to an object of type DerivedIncrementer. From lines 12 through 14 the increment() method is called on the DerivedIncrementer object via i2. When the invalid precondition value of 6 is used in the increment() method call the AssertionError is thrown as expected. Figure 24-2 shows the results of running this program.

image from book
Figure 24-2: Results of Running Example 24.4

Changing the Preconditions of Derived Class Methods

The version of the increment() method in class DerivedIncrementer discussed above implemented the same precondition as the Incrementer class version, namely, that the integer argument passed to the method was in the range 1 through 5. However, it is possible to specify a different precondition for a derived class version of increment().

In regards to derived class method preconditions you can go three ways: 1) adopt the same precondition(s), as was illustrated in the previous section, 2) weaken the precondition(s), or 3) strengthen the precondition(s).

Adopting the Same Preconditions

Derived class methods can adopt the same preconditions as the base class methods they override. The increment() method in class DerivedIncrementer, shown in the previous section, adopted the same precondition as the Incrementer class’s version of increment(). When a derived class method adopts the same preconditions as its base class counterpart its behavior is predictable from the point of view of any client program using a base class reference to a derived class object. In other words, you can safely reason about the behavior of a derived class object whose overriding methods adopt the same preconditions as their base class counterparts.

Weakening Preconditions

Derived class methods can weaken the preconditions specified in the base class methods they override. Weakening can also be thought of as a loosening or relaxing of a specified precondition. The increment() method in class DerivedIncrementer could have weakened the precondition specified in the base class version of increment() by allowing a wider range of increment values to be called as arguments. An example of this is shown in the class named WeakenedDerivedIncrementer whose code is given in example 24.5.

Example 24.5: WeakenedDerivedIncrementer.java

image from book
 1     public class WeakenedDerivedIncrementer extends Incrementer { 2       /******************************************** 3         Class invariant: 0 <= val <= 50 4       ******************************************/ 5       private int val = 0; 6 7       /********************************************** 8         Constructor Method: DerivedIncrementer(int i) 9               precondition: ((0 <= i) && (i <= 50)) 10             postcondition: 0 <= val <= 50 11       **********************************************/ 12      public WeakenedDerivedIncrementer(int i){ 13        super(i); 14        assert((0 <= i) && (i <= 50));  // enforce precondition 15        val = i; 16        System.out.println("WeakenedDerivedIncrementer object created with value: " + val); 17        checkInvariant(); 18      } 19 20      /*********************************************** 21               Method: void increment(int i) 22         precondition: ((0 < i) && (i <= 10)) 23        postcondition: 0 <= val <= 50 24      ***********************************************/ 25      public void increment(int i){ 26        assert((0 < i) && (i <= 10)); // enforce precondition 27 28        if((0 <= i) && (i <= 5)){   // remember, it's our job to use the base class correctly! 29             super.increment(i); 30        } 31 32        if((val+i) <= 50){ 33           val += i; 34        }else{ 35          int temp = val; 36          temp += i; 37          val = (temp - 50); 38         } 39         checkInvariant();  // check invariant 40         System.out.println("WeakenedDerivedIncrementer value is: " + val); 41       } 42 43       private void checkInvariant(){ 44         assert((0 <= val) && (val <= 50)); 45       } 46 47     } // end WeakenedDerivedIncrementer class definition
image from book

Referring to example 24.5 — the WeakenedDerivedIncrementer class looks a lot like the DerivedIncrementer class with two notable exceptions. First, the precondition on the increment() method has been relaxed to allow a wider range of increment values. Second, the if statement that appears within the body of the increment() method starting on line 28 ensures the value of i used in the call to the base class version of increment() obeys its precondition. Example 24.6 shows the WeakenedDerivedIncrementer class being put through its paces in a modified version of the MainTestApp program.

Example 24.6: MainTestApp.java (mod 2)

image from book
 1     public class MainTestApp { 2       public static void main(String[] args){ 3         Incrementer i1 = new Incrementer(0); 4         Incrementer i2 = new DerivedIncrementer(20); 5         Incrementer i3 = new WeakenedDerivedIncrementer(10); 6 7         i1.increment(1); 8         i1.increment(2); 9         i1.increment(3); 10        i1.increment(4); 11        i1.increment(5); 12        System.out.println("-----------------------------"); 13        i2.increment(4); 14        i2.increment(5); 15        System.out.println("-----------------------------"); 16        i3.increment(5); 17        i3.increment(6);  // It does not throw exception here... 18        i3.increment(7);  // nor here... 19        i3.increment(8);  // nor here... 20        i3.increment(9);  // nor here... 21        i3.increment(10); // nor here... 22        i3.increment(11); // ...but here it does! 23 24      } // end main() method 25     }// end MainTestApp clas definition
image from book

Referring to example 24.6 — a new Incrementer type reference named i3 is declared and initialized to point to an object of type WeakenedDerivedIncrementer. Lines 16 through 22 call the increment() method via i3. As you can see, WeakenedDerivedIncrementer’s version of increment() allows a wider range of increment values. If steps weren’t taken within the body of its increment() method to obey the contract of Incrementer.increment() then an exception would have been thrown on line 17.

However, from the point of view of a programmer whose expecting derived class objects to fulfill the contract of the base class increment() method, WeakenedDerivedIncrementer objects work just fine, as they allow the valid increment ranges of 1 through 5 which is what Incrementer.increment() methods expect. Figure 24-3 shows the results of running this modified version of MainTestApp.

image from book
Figure 24-3: Results of Running Example 24.6

Strengthening Preconditions

So far you have seen how a derived class object can be substituted for an Incrementer class object when the derived class’s increment() method adopts the same precondition or weakens the precondition of the Incrementer class’s increment() method. When preconditions are kept the same or weakened in the overriding methods of a derived class, objects of the derived class type can be substituted for base class objects with little problem. However, if you happen to strengthen the precondition of an overriding derived class method you will break the code that relies on the original preconditions specified for the base class method.

A strengthening precondition in a derived class method places limits on or restricts the original precondition specified in the base class method it is overriding. In the case of Incrementer and its possible derived classes, the preconditions on a derived version of increment() can be strengthened to limit the range of authorized increment values to, say, 1 through 3. This would effectively break any code that relies on the Incrementer’s version of the increment() method that allows the increment values 1 through 5.

Example 24.7 gives the code for a class named StrengthenedDerivedIncrementer whose increment() method overrides the base class version and strengthens the precondition.

Example 24.7: StrengthenedDerivedIncrementer.java

image from book
 1     public class StrengthenedDerivedIncrementer extends Incrementer { 2       /******************************************** 3         Class invariant: 0 <= val <= 50 4       ******************************************/ 5       private int val = 0; 6 7       /********************************************** 8         Constructor Method: DerivedIncrementer(int i) 9               precondition: ((0 <= i) && (i <= 50)) 10             postcondition: 0 <= val <= 50 11       **********************************************/ 12      public StrengthenedDerivedIncrementer(int i){ 13        super(i); 14        assert((0 <= i) && (i <= 50));  // enforce precondition 15        val = i; 16        System.out.println("StrengthenedDerivedIncrementer object created with value: " + val); 17        checkInvariant(); 18      } 19 20      /***************************************************** 21               Method: void increment(int i) 22         precondition: ((0 < i) && (i <= 3)) 23        postcondition: 0 <= val <= 50 24       *****************************************************/ 25      public void increment(int i){ 26        assert((0 < i) && (i <= 3)); // enforce precondition 27        super.increment(i); 28        if((val+i) <= 50){ 29           val += i; 30        }else{ 31          int temp = val; 32          temp += i; 33          val = (temp - 50); 34         } 35         checkInvariant();  // check invariant 36         System.out.println("StrengthenedDerivedIncrementer value is: " + val); 37      } 3839       private void checkInvariant(){ 40        assert((0 <= val) && (val <= 50)); 41      } 42 43    } // end StrengthenedDerivedIncrementer class definition
image from book

Referring to example 24.7 — the StrengthenedDerivedIncrementer class places a restriction on the original increment() method precondition by limiting the authorized increment values to 1 through 3. Example 24.8 shows the StrengthenedDerivedIncrementer class in action. Figure 24-4 shows the results of running this program.

Example 24.8: MainTestApp.java (mod 3)

image from book
 1     public class MainTestApp { 2       public static void main(String[] args){ 3         Incrementer i1 = new Incrementer(0); 4         Incrementer i2 = new DerivedIncrementer(20); 5         Incrementer i3 = new WeakenedDerivedIncrementer(10); 6         Incrementer i4 = new StrengthenedDerivedIncrementer(10); 7 8         i1.increment(1); 9         i1.increment(2); 10        i1.increment(3); 11        i1.increment(4); 12        i1.increment(5); 13        System.out.println("-----------------------------"); 14        i2.increment(4); 15        i2.increment(5); 16        System.out.println("-----------------------------"); 17        i3.increment(5); 18        System.out.println("-----------------------------"); 19        i4.increment(2); // OK so far... 20        i4.increment(3); // OK here too... 21        i4.increment(4); // Wait a minute...this should work! 22 23     } // end main() method 24    }// end MainTestApp clas definition
image from book

image from book
Figure 24-4: Results of Running Example 24.8

Changing the Postconditions of Derived Class Methods

Derived class method postconditions can be adopted, weakened, or strengthened just like preconditions. However, unlike preconditions, where a weakening condition is preferred to a strengthening condition, the opposite is true for postconditions: A derived class method should specify and implement a stronger, rather than weaker, postcondition.

The Incrementer and its derived class examples shown previously each had their own private attribute that was part if each class’s invariant. (Incrementer.val, DerivedIncrementer.val, etc.) Each class’s increment() method had a separate postcondition to preserve each class’s invariant. The two postconditions did not conflict or contradict and were therefore compatible.

If, on the other hand, Incrementer.val had been declared protected, and was inherited and used by its derived classes, then derived versions of the increment() method would need a postcondition that either maintained the class invariant specified by the Incrementer class (adopting postcondition) or a postcondition that strengthened Incrementer’s class invariant (strengthening postcondition).

A weakening postcondition will cause problems. Consider for a moment what would happen if a derived class version of increment() allowed inherited Incrementer.val to assume values outside the range of those allowed by Incrementer’s class invariant specification. Disaster would strike the code sooner than later. (Assuming some code somewhere depended upon Incrementer objects being within their specified, valid states.)

Special Cases of Preconditions and Postconditions

Method preconditions can specify and enforce more than just the values of method parameters, and postconditions can specify and enforce more than just class invariant states.

A method precondition can, for example, specify that the class invariant must hold true or that a combination of conditions hold true before it can do its job properly. A method postcondition can, in addition to enforcing the class invariant, specify the state of the object or reference the method returns (if any), or it can specify any number of conditions hold true upon completion of the method call. The conditions or combination of conditions imposed by derived class overriding method preconditions and postconditions can be weakening or strengthening.

The weakening and strengthening effects of preconditions and postconditions can apply to more than just simple conditions. Method parameter types and return types all play a part and are discussed below.

Method Argument Types

Derived class method preconditions can be weakened or strengthened by their method parameter types. An overriding method must agree with the method it overrides in the type, number, and order of its method parameters. Method parameter types can belong to a type hierarchy. This means that a method parameter might be related to another class via a subtype or supertype relationship.

A derived class method that declares a parameter whose type is a base class to the matching parameter declared by the base class’s version of the method, then the derived class method is an overriding method. If, however, the derived class method declares a parameter that is a subclass of the parameter type declared by the base class method then the derived class method hides the base class’s version of the method. This is due to the transitive nature of subtypes. (i.e., Given two types, Base and Derived, if Derived extends or implements Base, then Derived is a Base but a Base is not a Derived.)

In other words, an overriding method can only provide a weakening precondition with regards to parameter types because to strengthen the parameter type required would result in the declaration of a new method, (i.e., method overloading) (requiring a new type from the point of view of the base class version of the method) not the overriding of the base class method. To illustrate this point assume there exists the class inheritance hierarchy shown in figure 24-5.

image from book
Figure 24-5: Strong vs. Weak Types

Each method f() in each class A and C requires a reference to an object of type A. Method f() in class B specifies a reference to an object of type B. Therefore, method B.f() is an overloading method while method C.f() is an overriding method. Examples 24.9 through 24.11 give the code for classes A, B, and C. Example 24.12 puts these classes through their paces and figure 24-6 shows the results of running this program.

Example 24.9: A.java

image from book
 1     public class A { 2       public A(){ 3          System.out.println("A object created!"); 4       } 5 6       public void f(A a){ 7         System.out.println("A.f() called!"); 8       } 9     }
image from book

Example 24.10: B.java

image from book
 1     public class B extends A { 2       public B(){ 3          System.out.println("B object created!"); 4       } 5 6       public void f(B b){ 7         System.out.println("B.f() called!"); 8       } 9     }
image from book

Example 24.11: C.java

image from book
 1     public class C extends B { 2       public C(){ 3          System.out.println("C object created!"); 4       } 5 6       public void f(A a){ 7         System.out.println("C.f() called!"); 8       } 9     }
image from book

Example 24.12: MainTestApp.java

image from book
 1     public class MainTestApp { 2       public static void main(String[] args){ 3         A a1 = new A(); 4         a1.f(new A()); // A's method called 5 6         System.out.println("------------------"); 7 8         A a2 = new B(); 9         a2.f(new A()); // A's method called 10        a2.f(new B()); // A's method called 11 12        System.out.println("------------------"); 13 14        B b1 = new C(); 15        b1.f(new A()); // C's overriding method called 16        b1.f(new B()); // B's overloaded method called 17        b1.f(new C()); // B's overloaded method called 18 19        System.out.println("------------------"); 20 21        A a3 = new C(); 22        a3.f(new A()); // C's overriding method called 23        a3.f(new B()); // C's overriding method called 24        a3.f(new C()); // C's overriding method called 25 26      } // end main() method 27    } // end MainTestApp program
image from book

image from book
Figure 24-6: Results of Running Example 24.12

Method Return Types

Method return types are considered special cases of postconditions. A reference to an object may be returned from a method as a result of its execution. Refer again to the inheritance hierarchy illustrated in figure 24-5. If a snippet of client code expects a return type from a method to be of a certain type, the method can strengthen that condition and return a subtype of the type expected. This strengthening of return types is in line with the strengthening usually required of postconditions.

Three Rules of the Substitution Principle

In their book Program Development in Java: Abstraction, Specification, and Object-Oriented Design, Barbara Liskov and John Guttag say that the substitution principle must support three properties: the signature rule, the methods rule, and the properties rule. Each of these rules are discussed below.

Signature Rule

The signature rule deals with the methods published or made public by a type specification. In Java these methods would have public accessibility. For a subtype to obey the signature rule it must support all the methods published by its base class and that each overriding method is compatible with the method it overrides. Java enforces this type compatibility.

Methods Rule

The methods rule says that calls to overriding methods should behave like the base class methods they override. A type may be substitutable from a strictly type perspective but the behavior may be all wrong. Correct behavior of overriding methods is the aim of LSP and DbC.

Properties Rule

The properties rule is concerned with the preservation of provable base class properties by subtype behavior. A subtype should preserve the base class invariant. If a subtype’s behavior violates a base class invariant then it is breaking the properties rule.

Quick Review

The preconditions of a derived class method should either adopt the same or weaker preconditions as the base class method it is overriding. A derived class method should never strengthen the preconditions specified in a base class version of the method. Derived class methods that strengthen base class method preconditions will render it impossible for programmers to reason about the behavior of subtype objects and lead to broken code should the ill-behaved derived class object be substituted for a base class object.

Method parameter types are considered special cases of preconditions. Preconditions should be weakened in the overriding Method, therefore, parameter types should be the same or weaker than the parameter types of the method being overridden. A base class is considered a weaker type than one of its subclasses.

Method return types are considered special cases of postconditions. The return type of an overriding method should be stronger than the type expected by the client code. A subclass is considered a stronger type than its base class.




Java For Artists(c) The Art, Philosophy, and Science of Object-Oriented Programming
Java For Artists: The Art, Philosophy, And Science Of Object-Oriented Programming
ISBN: 1932504052
EAN: 2147483647
Year: 2007
Pages: 452

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net