Skip to main content
Engineering LibreTexts

8.1: Section 1-

  • Page ID
    15105
  • \( \newcommand{\vecs}[1]{\overset { \scriptstyle \rightharpoonup} {\mathbf{#1}} } \) \( \newcommand{\vecd}[1]{\overset{-\!-\!\rightharpoonup}{\vphantom{a}\smash {#1}}} \)\(\newcommand{\id}{\mathrm{id}}\) \( \newcommand{\Span}{\mathrm{span}}\) \( \newcommand{\kernel}{\mathrm{null}\,}\) \( \newcommand{\range}{\mathrm{range}\,}\) \( \newcommand{\RealPart}{\mathrm{Re}}\) \( \newcommand{\ImaginaryPart}{\mathrm{Im}}\) \( \newcommand{\Argument}{\mathrm{Arg}}\) \( \newcommand{\norm}[1]{\| #1 \|}\) \( \newcommand{\inner}[2]{\langle #1, #2 \rangle}\) \( \newcommand{\Span}{\mathrm{span}}\) \(\newcommand{\id}{\mathrm{id}}\) \( \newcommand{\Span}{\mathrm{span}}\) \( \newcommand{\kernel}{\mathrm{null}\,}\) \( \newcommand{\range}{\mathrm{range}\,}\) \( \newcommand{\RealPart}{\mathrm{Re}}\) \( \newcommand{\ImaginaryPart}{\mathrm{Im}}\) \( \newcommand{\Argument}{\mathrm{Arg}}\) \( \newcommand{\norm}[1]{\| #1 \|}\) \( \newcommand{\inner}[2]{\langle #1, #2 \rangle}\) \( \newcommand{\Span}{\mathrm{span}}\)\(\newcommand{\AA}{\unicode[.8,0]{x212B}}\)

    Inheritance and Polymorphism

    After studying this chapter, you will

    Understand the concepts of inheritance and polymorphism.

    Know how Java’s dynamic binding mechanism works.

    Be able to design and use abstract methods and classes.

    Be able to design and use polymorphic methods.

    Gain a better understanding of object-oriented design.

    Introduction

    Java’s Inheritance Mechanism

    Abstract Classes, Interfaces, and Polymorphism

    Example: A Toggle Button

    Special Topic: Historical Cryptography

    Example: The Cipher Class Hierarchy

    Case Study: A Two Player Game Hierarchy

    Principles of Object-Oriented Design

    Chapter Summary

    Solutions to Self-Study Exercises

    Exercises

    Introduction

    Among the most important concepts in object oriented programming are the concepts of inheritance and polymorphism. We first introduced the idea of inheritance in Chapter 0. There we compared inheritance to the natural form of inheritance, in which horses and cows share certain inherited characteristics, such as being warm-blooded, by virtue of their being mammals. We also gave the example of a hierarchy of chess pieces and showed how different kinds of chess pieces, such as Pawn and Bishop, inherited certain shared characteristics from their ChessPiece superclass.

    We took a more technical look at inheritance in Chapter 3, where we talked about the toString() method and how it is inherited from the Object class. We illustrated there how subclasses of Object could override the inherited toString() method in order to customize it for their purposes. We also introduced the idea of polymorphism, in which a method call, such as obj.toString(), can have different behaviors depending on the type of object, obj, on which it is called.

    In Chapter 4, we continued introducing inheritance and polymorphism, when we learned about Java’s Abstract Windowing Toolkit (AWT) and Swing hierarchies, the class hierarchies that are used to create Graphical User Interfaces (GUIs). We also learned how to extend a class to create our own subclass, and we made limited use of inheritance in the design of the SimpleGUI class. We were also introduced to the concept of a Java interface, and we learned how to use the ActionListener interface to enable a SimpleGUI to handle action events while the GUI is running.

    In this chapter we will take a much closer look at these important object-oriented concepts. We will learn how Java’s dynamic binding mechanism works and how it makes polymorphism possible. Most importantly, we will see why inheritance and polymorphism are important elements of object-oriented design, and we will learn how to use these important tools to design several different programs. In keeping with our running games example, we will develop a TwoPlayerGame hierarchy and show how it can simplify the implementation of OneRowNim and other two-player games.

    Java’s Inheritance Mechanism

    As we described in Chapter 0, class inheritance is the mechanism whereby a class acquires (inherits) the methods and variables of its superclasses. To remind you of the basic concept, let’s repeat an earlier example: Just as horses inherit the attributes and behaviors associated with mammals and vertebrates, a Java subclass inherits the attributes and behaviors of its superclasses.

    Figure [fig-horsehier] uses a UML diagram to illustrate the relationships among horses, mammals, vertebrates, and animals. As the root of the hierarchy, which is always shown at the top, the Animal class contains the most general attributes, such as being alive and being able to move. All animals share these attributes. The class of vertebrates is a somewhat more specialized type of animal, in that vertebrates have backbones. Similarly, the class of mammals is a further specialization over the vertebrates in that mammals are warm-blooded and nurse their young. Finally, the class of horses is a further specialization over the class of mammals, in that all horses have four legs. Some mammals, such as humans and penguins, do not have four legs. Thus, by virtue of its class’s position in this hierarchy, we can infer that a horse is a living, moving, four-legged vertebrate, which is warm blooded and nurses its young.

    We have deliberately used an example from the natural world to show that the concept of inheritance in Java is inspired by its counterpart in the natural world. But how exactly does the concept of inheritance apply to Java (and to other object-oriented languages)? And, more importantly, how do we use the inheritance mechanism in object-oriented design?

    Using an Inherited Method

    In Java, the public and protected instance methods and instance variables of a superclass are inherited by all of its subclasses. This means that objects belonging to the subclasses can use the inherited variables and methods as their own.

    We have already seen some examples of this in earlier chapters. For example, recall that by default all Java classes are subclasses of the Object class, which is the most general class in Java’s class hierarchy. One public method that is defined in the Object class is the toString() method. Because every class in the Java hierarchy is a subclass of Object, every class inherits the toString() method. Therefore, toString() can be used with any Java object.

    To illustrate this, suppose we define a Student class as follows:

    public class Student {
        protected String name;
        public Student(String s) {
            name = s;
        }
        public String getName() {
            return name;
        }
    }

    Figure [fig-student1] shows the relationship between this class and the Object class. As a subclass of Object, the Student class inherits the toString() method. Therefore, for a given Student object, we can call its toString() as follows:

    Student stu = new Student("Stu");
    System.out.println(stu.toString());

    How does this work? That is, how does Java know where to find the toString() method, which, after all, is not defined in the Student class? The answer to this question is crucial to understanding how Java’s inheritance mechanism works.

    Note in this example that the variable stu is declared to be of type Student and is assigned an instance of the Student class. When the expression stu.toString() is executed, Java will first look in the Student class for a definition of the toString() method. Not finding one there, it will then search up the Student class hierarchy (Fig. 8.2) until it finds a public or protected definition of the toString() method. In this case, it finds a toString() method in the Object class and it executes that implementation of toString(). As you know from Chapter 3, this would result in the expression stu.toString() returning something like:

    Student@cde100

    The default implementation of toString() returns the name of the object’s class and the address (cde100) where the object is stored in memory. However, this type of result is much too general and not particularly useful.

    Overriding an Inherited Method

    In Chapter 3 we pointed out that the toString() method is designed to be overridden—that is, to be redefined in subclasses of Object. Overriding toString() in a subclass provides a customized string representation of the objects in that subclass. We showed that by redefining toString() in our OneRowNim class, we customized its actions so that it returned useful information about the current state of a OneRowNim game.

    To override toString() for the Student class, let’s add the following method definition to the Student class:

    public String toString() {
      return "My name is " + name +  " and I am a Student.";
    }

    Given this change, the revised Student class hierarchy is shown in Figure [fig-student2]. Note that both Object and Student contain implementations of toString(). Now when the expression stu.toString() is invoked, the following, more informative, output is generated:

        My name is Stu and I am a Student.

    In this case, when Java encounters the method call stu.toString(), it invokes the toString() method that it finds in the Student class (Fig. 8.3).

    These examples illustrate two important object-oriented concepts: inheritance and method overriding.

    Static Binding, Dynamic Binding
    and Polymorphism

    The mechanism that Java uses in these examples is known as dynamic binding, in which the association between a method call and the correct method implementation is made at run time. In dynamic binding a method call is bound to the correct implementation of the method at run time by the Java Virtual Machine (JVM).

    Dynamic binding is contrasted with static binding, the mechanism by which the Java compiler resolves the association between a method call and the correct method implementation when the program is compiled. In order for dynamic binding to work, the JVM needs to maintain some kind of representation of the Java class hierarchy, including classes defined by the programmer. When the JVM encounters a method call, it uses information about the class hierarchy to bind the method call to the correct implementation of that method.

    In Java, all method calls use dynamic binding except methods that are declared final or private. Final methods cannot be overridden, so declaring a method as final means that the Java compiler can bind it to the correct implementation. Similarly, private methods are not inherited and therefore cannot be overridden in a subclass. In effect, private methods are final methods and the compiler can perform the binding at compile time.

    Java’s dynamic-binding mechanism, which is also called late binding or run-time binding, leads to what is know as polymorphism. Polymorphism is a feature of object-oriented languages whereby the same method call can lead to different behaviors depending on the type of object on which the method call is made. The term polymorphism means, literally, having many (poly) shapes (morphs). Here’s a simple example:

     Object obj;                        // Static type: Object
     obj = new Student("Stu");          // Actual type: Student
     System.out.println(obj.toString());// Prints "My name is Stu..."
     obj = new OneRowNim(11);           // Actual type: OneRowNim
     System.out.println(obj.toString());// Prints "nSticks = 11, player = 1"

    The variable obj is declared to be of type Object. This is its static or declared type. A variable’s static type never changes. However, a variable also has an actual or dynamic type. This is the actual type of the object that has been assigned to the variable. As you know, an Object variable can be assigned objects from any Object subclass. In the second statement, obj is assigned a Student object. Thus, at this point in the program, the actual type of the variable obj is Student. When obj.toString() is invoked in the third line, Java begins its search for the toString() method at the Student class, because that is the variable’s actual type.

    In the fourth line, we assign a OneRowNim object to obj, thereby changing its actual type to OneRowNim. Thus, when obj.toString() is invoked in the last line, the toString() method is bound to the implementation found in the OneRowNim class.

    Thus, we see that the same expression, obj.toString(), is bound alternatively to two different toString() implementations, based on the actual type of the object, obj, on which it is invoked. This is polymorphism and we will sometimes say that the toString() method is a polymorphic method. A polymorphic method is a method signature that behaves differently when it is invoked on different objects. An overridden method, such as the toString() method, is an example of a polymorphic method, because its use can lead to different behaviors depending upon the object on which it is invoked.

    The previous example is admittedly somewhat contrived. In some object-oriented languages, a code segment such as that above would use static binding rather than dynamic binding. In other words, the compiler would be able to figure out the bindings. So let’s take an example where static binding, also called early binding, is not possible. Consider the following method definition:

    public void polyMethod(Object obj) {
      System.out.println(obj.toString()); // Polymorphic
    }

    The method call in this method, obj.toString(), can’t be bound to the correct implementation of toString() until the method is actually invoked—that is, at run time. For example, suppose we make the following method calls in a program:

     Student stu = new Student("Stu");
     polyMethod(stu);
     OneRowNim nim = new OneRowNim();
     polyMethod(nim);  

    The first time polyMethod() is called, the obj.toString() is invoked on a Student object. Java will use its dynamic binding mechanism to associate this method call with the toString() implementation in Student and output “My name is Stu and I am a Student.” The second time polyMethod() is called, the obj.toString() expression is invoked on a OneRowNim object. In this case, Java will bind the method call to the implementation in the OneRowNim class. The output generated in this case will report how many sticks are left in the game.

    The important point here is that polymorphism occurs when an overridden method is called on a superclass variable, obj. In such a case, the actual method implementation that is invoked is determined at run time. The determination depends on the type of object that was assigned to the variable. Thus, we say that the method call obj.toString() is polymorphic because it is bound to different implementations of toString() depending on the actual type of the object that is bound to obj.

    Polymorphism and Object-Oriented Design

    Now that we understand how inheritance and polymorphism work in Java, it will be useful to consider an example that illustrates how these mechanisms can be useful in designing classes and methods. We have been using the various System.out.print() and System.out.println() methods since Chapter 1. The print() and println() methods are examples of overloaded methods—that is, methods that have the same name but different parameter lists. Remember that a method’s signature involves its name, plus the type, number, and order of its parameters. Methods that have the same name but different parameters are said to be overloaded.

    Here are the signatures of some of the different print() and println() methods:

    print(char c);           println(char c);
    print(int i);            println(int i);
    print(double d);         println(double d);
    print(float f);          println(float f);
    print(String s);         println(String s);
    print(Object o);         println(Object o);

    Basically, there is a print() and println() method for every type of primitive data, plus methods for printing any type of object. When Java encounters an expression involving print() or println() it chooses which particular print() or println() method to call. To determine the correct method, Java relies on the differences in the signatures of the various print() methods. For example, because its argument is an int, the expression print(5) is associated with the method whose signature is print(int i) be cause its parameter is an int.

    Note that there is only one set of print() and println() methods for printing Objects. The reason is that polymorphism is used by the print(Object o) and println(Object o) methods to print any type of object. While we do not have access to the source code for these methods, we can make an educated guess that their implementations utilize the polymorphic toString() method, as follows:

     public void print(Object o) {
         System.out.print(o.toString());
     }
     
     public void println(Object o) {
         System.out.println(o.toString());
     }    

    Here again we have a case where an expression, o.toString(), is bound dynamically to the correct implementation of toString() based on the type of Object that the variable o is bound to. If we call System.out.print(stu), where stu is a Student, then the Student.toString() method is invoked. On the other hand, if we call System.out.print(game), where game is a OneRowNim, then the OneRowNim.toString() method is invoked.

    The beauty of using polymorphism in this way is the flexibility and extensibility that it allows. The print() and println() methods can print any type of object, even new types of objects that did not exist when these library methods were written.

    To confirm that the print() and println() methods are implemented along the lines that we suggest here, compile and run the TestPrint program shown here. Describe how it confirms our claim.

    public class TestPrint {
        public static void main(String args[]) {
            System.out.println(new Double(56));
            System.out.println(new TestPrint());
        }
    }

    Override the toString() method in the TestPrint class and rerun the experiment. Describe how this adds further confirmation to our claim.

    Using super to Refer to the Superclass

    One question that might occur to you is: Once you override the default toString() method, is it then impossible to invoke the default method on a Student object? The default toString() method (and any method from an object’s superclass) can be invoked using the super keyword. For example, suppose that within the Student class, you wanted to concatenate the result of both the default and the new toString() methods. The following expression would accomplish that:

    super.toString() + toString()

    The super keyword specifies that the first toString() is the one implemented in the superclass. The second toString() refers simply to the version implemented within the class. We will see additional examples of using the super keyword in the following sections.

    Consider the following class definitions and determine the output that would be generated by the code segment.

     public class A {
         public void method() { System.out.println("A"); }
     }
     public class B extends A {
         public void method() { System.out.println("B"); }
     }
    
     // Determine the output from this code segment
     A a = new A();
     a.method();
     a = new B();
     a.method();
     B b = new B();
     b.method();

    For the class B defined in the previous exercise, modify its method() so that it invokes A’s version of method() before printing out B.

    Given the definitions of the classes A and B, which of the following statements are valid? Explain.

        A a = new B();
        a = new A();
        B b = new A();
        b = new B();

    Inheritance and Constructors

    Java’s inheritance mechanism applies to a class’s public and protected instance variables and methods. It does not apply to a class’s constructors. To illustrate some of the implications of this language feature, let’s define a subclass of Student called CollegeStudent:

    public class CollegeStudent extends Student {
        public CollegeStudent() { }
        public CollegeStudent(String s) {
            super(s);
        }
        public String toString() {
           return "My name is " + name +  
                   " and I am a CollegeStudent.";
        }
    }

    Because CollegeStudent is a subclass of Student, it inherits the public and protected instance methods and variables from Student. So, a CollegeStudent has an instance variable for name and it has a public getName() method. Recall that a protected element, such as the name variable in the Student class, is accessible only within the class and its subclasses. Unlike public elements, it is not accessible to other classes.

    Note that CollegeStudent overrides the toString() method, giving it a more customized implementation. The hierarchical relationship between CollegeStudent and Student is shown in Figure [fig-collstudent]. A CollegeStudent is a Student and both are Objects.

    Note how we have implemented the CollegeStudent(String s) constructor. Because the superclass’s constructors are not inherited, we have to implement this constructor in the subclass if we want to be able to assign a CollegeStudent’s name during object construction. The method call, super(s), is used to invoke the superclass constructor and pass it s, the student’s name. The superclass constructor will then assign s to the name variable.

    As we have noted, a subclass does not inherit constructors from its superclasses. However, if the subclass constructor does not explicitly invoke a superclass constructor, Java will automatically invoke the default superclass constructor—in this case, super(). By “default superclass constructor” we mean the constructor that has no parameters. For a subclass that is several layers down in the hierarchy, this automatic invoking of the super() constructor will be repeated upwards through the entire class hierarchy. Thus when a CollegeStudent is constructed, Java will automatically call Student() and Object(). Note that if one of the superclasses does not contain a default constructor, this will result in a syntax error.

    If you think about this, it makes good sense. How else will the inherited elements of the object be created? For example, in order for a CollegeStudent to have a name variable, a Student object, where name is declared, must be created. The CollegeStudent constructor then extends the definition of the Student class. Similarly, in order for a Student object to have the attributes common to all objects, an Object instance must be created and then extended into a Student.

    Thus, unless a constructor explicitly calls a superclass constructor, Java will automatically invoke the default superclass constructors. It does this before executing the code in its own constructor. For example, if you had two classes, A and B, where B is a subclass of A, then whenever you create an instance of B, Java will first invoke A’s constructor before executing the code in B’s constructor. Thus, Java’s default behavior during construction of B is equivalent to the following implementation of B’s constructor:

    public B() {
        A();   // Call the superconstructor 
        // Now continue with this constructor's code
    }

    Calls to the default constructors are made all the way up the class hierarchy, and the superclass constructor is always called before the code in the class’s constructor is executed.

    Consider the following class definitions and describe what would be output by the code segment.

     public class A {
         public A() { System.out.println("A"); }
     }
     public class B extends A {
         public B() { System.out.println("B"); }
     }
     public class C extends B {
         public C() { System.out.println("C"); }
     }
    
     // Determine the output.
     A a = new A();
     B b = new B();
     C c = new C();

    Abstract Classes, Interfaces,
    and Polymorphism

    In Java, there are three kinds of polymorphism:

    • Overriding an inherited method.
    • Implementing an abstract method.
    • Implementing a Java interface.

    In the previous section we saw examples of the first type of polymorphism. All forms of polymorphism are based on Java’s dynamic binding mechanism. In this section we will develop an example that illustrates the other two types of polymorphism and discuss some of the design implications involved in choosing one or the other approach.

    Implementing an Abstract Method

    An important feature of polymorphism is the ability to invoke a polymorphic method that has been defined only abstractly in the superclass. To illustrate this feature, we will develop a hierarchy of simulated animals that make characteristic animal sounds, an example that is widely used to illustrate polymorphism.

    As we all know from our childhood, animals have distinctive ways of speaking. A cow goes “moo”; a pig goes “oink”; and so on. Let’s design a hierarchy of animals that simulates this characteristic by printing the characteristic sounds that these animals make. We want to design our classes so that any given animal will return something like “I am a cow and I go moo,” when we invoke the toString() method. Moreover, we want to design this collection of classes so that it is extensible—that is, so that we can continue to add new animals to our menagerie without having to change any of the code in the other classes.

    Figure [fig-animals] provides a summary of the design we will implement. The Animal class is an abstract class. That’s why its name is italicized in the UML diagram. The reason that this class is abstract is because its speak() method is an abstract method, which is a method definition that does not contain an implementation. That is, the method definition contains just the method’s signature, not its body. Any class that contains an abstract method, must itself be declared abstract. Here is the definition of the Animal class:

    public abstract class Animal {
        protected String kind; // Cow, pig, cat, etc.
             
        public Animal()  {  }
        public String toString() {
            return "I am a " + kind + " and I go " + speak();
        }
        public abstract String speak();   // Abstract method
    }

    Note how we declare the abstract method (speak()) and the abstract class. Because one or more of its methods is not implemented, an abstract class cannot be instantiated. That is, you cannot say:

    Animal animal = new Animal(); // Error: Animal is abstract

    Even though it is not necessary, we give the Animal class a constructor. If we had left this off, Java would have supplied a default constructor that would be invoked when Animal subclasses are created.

    Java has the following rules on using abstract methods and classes.

    • Any class containing an abstract method must be declared an class.
    • An abstract class cannot be instantiated. It must be subclassed.
    • A subclass of an abstract class may be instantiated only if it implements all of the superclass’s abstract methods. A subclass that implements only some of the abstract methods must itself be declared abstract.
    • A class may be declared abstract even it contains no abstract methods. It could, for example, contain instance variables that are common to all its subclasses.

    Even though an abstract method is not implemented in the superclass, it can be called in the superclass. Indeed, note how the toString() method calls the abstract speak() method. The reason that this works in Java is due to the dynamic binding mechanism. The polymorphic speak() method will be defined in the various Animal subclasses. When the Animal.toString() method is called, Java will decide which actual speak() method to call based on what subclass of Animal is involved.

    Definitions for two such subclasses are shown in Figure [fig-animalsubs].

    public class Cat extends Animal {
        public Cat() {
            kind = "cat";
        }
        public String speak() {
            return "meow";
        }
    }
    
    public class Cow extends Animal {
        public Cow() {
            kind = "cow";
        }
        public String speak() {
            return "moo";
        }
    }

    In each case the subclass extends the Animal class and provides its own constructor and its own implementation of the speak() method. Note that in their respective constructors, we can refer to the kind instance variable, which is inherited from the Animal class. By declaring kind as a protected variable, it is inherited by all Animal subclasses but hidden from all other classes. On the other hand, if kind had been declared public, it would be inherited by Animal subclasses, but it would also be accessible to every other class, which would violate the information hiding principle.

    Given these definitions, we can now demonstrate the power and flexibility of inheritance and polymorphism. Consider the following code segment:

     Animal animal = new Cow();
     System.out.println(animal.toString()); // A cow goes moo
     animal = new Cat();
     System.out.println(animal.toString()); // A cat goes meow

    We first create a Cow object and then invoke its (inherited) toString() method. It returns, “I am a cow and I go moo.” We then create a Cat object and invoke its (inherited) toString() method, which returns, “I am a cat and I go meow.” In other words, Java is able to determine the appropriate implementation of speak() at run time in each case. The invocation of the abstract speak() method in the Animal.toString() method is a second form of polymorphism.

    What is the advantage of polymorphism here? The main advantage is the extensibility that it affords our Animal hierarchy. We can define and use completely new Animal subclasses without redefining or recompiling the rest of the classes in the hierarchy. Note that the toString() method in the Animal class does not need to know what type of Animal subclass will be executing its speak() method. The toString() method will work correctly for any subclass of Animal because every non-abstract subclass of Animal must implement the speak() method.

    To get a better appreciation of the flexibility and extensibility of this design, it might be helpful to consider an alternative design that does not use polymorphism. One such alternative would be to define each Animal subclass with its own speaking method. A Cow would have a moo() method; a Cat would have a meow() method; and so forth. Given this design, we could use a switch statement to select the appropriate method call. For example, consider the following method definition:

    public String talk(Animal a) {
      if (a instanceof Cow)
         return "I am a " + kind + " and I go " + a.moo();
      else if (a instanceof Cat)
         return "I am a " + kind + " and I go " + a.meow();
      else
        return "I don't know what I am";
    }

    In this example, we introduce the instanceof operator, which is a built-in boolean operator. It returns true if the object on its left-hand side is an instance of the class on its right-hand side.

    The talk() method would produce more or less the same result. If you call talk(new Cow()), it will return “I am a cow and I go moo.” However, with this design, it is not possible to extend the Animal hierarchy without rewriting and recompiling the talk() method.

    Thus, one of the chief advantages of using polymorphism is the great flexibility and extensibility it affords. We can define new Animal subclasses and define their speak() methods. These will all work with the toString() method in the Animal class, without any need to revise that method.

    Another advantage of using abstract methods is the control that it gives the designer of the Animal hierarchy. By making it an abstract class with an abstract speak() method, any non-abstract Animal subclass must implement the speak() method. This lends a great degree of predictability to the subclasses in the hierarchy, making it easier to use them in applications.

    Following the examples in this section, define an Animal subclass named Pig, which goes “oink.”

    Show how you would have to modify the talk() method defined above to incorporate the Pig class.

    Implementing a Java Interface

    A third form of polymorphism results through the implementation of Java interfaces, which are like classes but contain only abstract method definitions and constants (final) variables. An interface cannot contain instance variables. We have already seen interfaces, such as when we encountered the ActionListener interface in Chapter 4.

    The designer of an interface specifies what methods will be implemented by classes that implement the interface. This is similar to what we did when we implemented the abstract speak() method in the animal example. The difference between implementing a method from an interface and from an abstract superclass is that a subclass extends an abstract superclass but it implements an interface.

    Java’s interface mechanism gives us another way to design polymorphic methods. To see how this works, we will provide an alternative design for our animal hierarchy. Rather than defining speak() as an abstract method within the Animal superclass, we will define it as an abstract method in the Speakable interface (Fig. [fig-speakable]).

    public interface Speakable {
        public String speak();
    }
    public class Animal {
        protected String kind; // Cow, pig, cat, etc.
        public Animal()  {  }
        public String toString() {
            return "I am a " + kind + " and I go " + 
                   ((Speakable)this).speak();
        }
    }

    Note the differences between this definition of Animal and the previous definition. This version no longer contains the abstract speak() method. Therefore, the class itself is not an abstract class. However, because the speak() method is not declared in this class, we cannot call the speak() method in the toString() method, unless we cast this object into a Speakable object.

    We encountered the cast operation in Chapter 5, where we used it with primitive types such as (int) and (char). Here, we use it to specify the actual type of some object. In this toString() example, this object is some type of Animal subclass, such as a Cat. The cast operation, (Speakable), changes the object’s actual type to Speakable, which syntactically allows its speak() method to be called.

    Given these definitions, Animal subclasses will now extend the Animal class and implement the Speakable interface:

    public class Cat extends Animal implements Speakable {
        public Cat() { kind = "cat"; }
        public String speak() { return "meow";  }
    }
    public class Cow extends Animal implements Speakable {
        public Cow() { kind = "cow";  }
        public String speak() { return "moo";  }
    }

    To implement a Java interface, one must provide a method implementation for each of the abstract methods in the interface. In this case there is only one abstract method, the speak() method.

    Note, again, the expression from the Animal.toString() class

    ((Speakable)this).speak();

    which casts this object into a Speakable object. The reason that this cast is required is because an Animal does not necessarily have a speak() method. A speak() method is not defined in the Animal class. However, the Cat subclass of Animal does implement a sleep() method as part of its Speakable interface. Therefore, in order to invoke speak() on an object from one of the Animal subclasses, the object must actually be a Speakable and we must perform the cast as shown here.

    This illustrates, by the way, that a Cat, by virtue of extending the Animal class and implementing the Speakable interface, is both an Animal and a Speakable. In general, a class that implements an interface, has that interface as one of its types. Interface implementation is itself a form of inheritance. A Java class can be a direct subclass of only one superclass. But it can implement any number of interfaces.

    Given these definitions of the Cow and Cat subclasses, the following code segment will produce the same results as in the previous section.

     Animal animal = new Cow();
     System.out.println(animal.toString()); // A cow goes moo
     animal = new Cat();
     System.out.println(animal.toString()); // A cat goes meow

    Although the design is different, both approaches produce the same result. We will put off, for now, the question of how one decides whether to use an abstract method or a Java interface. We will get to this question when we design the TwoPlayerGame class hierarchy later in this chapter.

    Example: A Toggle Button

    The ability to extend an existing class is one of the most powerful features of object-oriented programming. It allows objects to reuse code defined in the superclasses without having to redefine or recompile the code. As we saw in Chapter 4, a programmer-defined JFrame, such as GreeterGUI, uses the public methods defined for JFrames, Frames, Windows, Containers, Components, and Objects simply because it is a subclass of JFrame (Fig. 4.11). By the same token, it can use all of the public and protected instance variables and constants defined in these classes by simply referring to them in its own code.

    In this section, we present an example of how inheritance can be used to extend and customize the functionality of a Java library class. As we saw in Chapter 4, a JButton is a GUI component that can be associated with a particular action by implementing the ActionListener interface. For example, we used a JButton in the GreeterGUI to generate a greeting to the user.

    In this section, we will design a more sophisticated button. We will call it a ToggleButton and define it as a JButton subclass that toggles its label whenever it is clicked, in addition to carrying out some kind of associated action.

    A light switch behaves similarly to a ToggleButton in this sense. Whenever you flick a light switch, it changes its label from “on” to “off,” but it also turns the lights on or off. Although different switches are associated with different lights, every light switch toggles its label each time it is clicked. So let’s design a ToggleButton that behaves like a light switch.

    The main idea in our design is that a ToggleButton is a JButton that has two labels. By default, a JButton has just a single label. Thus, because of the type of behavior we want to elicit, we need to define ToggleButton as a subclass of JButton with two String variables that will serve as its alternate labels (Fig. [fig-toggleuml]). Note that we give it a constructor method that will allow us to provide the initial value of its two label strings. Another important feature of a ToggleButton is that it should act as its own ActionListener so that it can toggle its label whenever it is clicked. Therefore, it must also implement the ActionListener interface.

    The complete definition of ToggleButton is given in Figure [fig-togglebutton]. Note how we have defined its constructor. Recall that the JButton class has a constructor method with the signature JButton(String), which allows us to set a JButton’s label during instantiation. We need to do the same thing with one of ToggleButton’s two labels. That is, when we create a ToggleButton, we want to initialize its label to one of its two alternative labels (here, “On” or “Off”).

    import java.awt.*;
    import java.awt.event.*;
    import javax.swing.*;
    
    public class ToggleButton extends JButton 
                            implements ActionListener {   
      private String label1;   // Toggle between two labels
      private String label2;
       
      public ToggleButton(String l1, String l2) {// Constructor
        super(l1);          // Use l1 as the default label
        label1 = l1; 
        label2 = l2;
        addActionListener(this);
      } 
      public void actionPerformed(ActionEvent e) {
        String tempS = label1;  // Swap the labels
        label1 = label2;
        label2 = tempS;
        setText(label1);
      } // actionPerformed()
    } // ToggleButton

    Because constructor methods are not inherited by the subclass, we want to invoke the superclass’s constructor in the ToggleButton() constructor using the super keyword. This must be done as the first statement in the ToggleButton() constructor. By passing l1 to the super constructor we are making the first string that the user gives us the default label for our ToggleButton. This will be the label that appears on the button when it is first displayed in a Component.

    Notice also in the ToggleButton() constructor that the ToggleButton is designated as its own ActionListener, so whenever it is clicked, its method will be invoked. The actionPerformed() method exchanges the button’s current label for its other label. Swapping two values in memory is a standard programming practice used in lots of different algorithms. In order to do it properly, you must use a third variable to temporarily store one of the two values you are swapping. The comments in actionPerformed() provide a step-by-step trace of the values of the three variables involved.

    The first statement in actionPerformed() creates a temporary variable named tempS and assigns it the value of label1. Recall that was the button’s initial label. To make this example easier to follow, let’s suppose that initially label1 is “off” and that label2 is “on.” After line 1 is executed, both tempS and label1 contain “off” as their value. Line 2 then assigns label2’s value to label1. Now both label1 and label2 store “on” as their values. In line 3 we assign tempS’s value to label2. Now label2 stores “off” and label1 stores “on,” and we have effectively swapped their original values.

    The next time we invoke actionPerformed(), label1 and label2 will have their opposite values initially. Swapping them a second time will assign them their initial values again. We can continue toggling their values in this way indefinitely. To complete the method, the last statement in actionPerformed() assigns label1’s current value as the new ToggleButton’s label.

    Now that we have seen that a ToggleButton toggles its label between two values, what about performing an associated action? To do this, we need a design involving multiple event handlers, one to handle the toggling of the button’s label and the other to handle its associated action (Fig [fig-p204]).

    In this design, lightSwitch has two listeners that respond to its events: the lightSwitch itself, as a result of the actionPerformed() method in its class, and the ToggleFrame, as a result of actionPerformed() method in this class.

    The implementation of this design is given by ToggleFrame, a program that uses a ToggleButton (Fig. [fig-toggletest]). Like the GUI we designed in Chapter 4, this program extends the JFrame class and implements the ActionListener interface. In this example we use a ToggleButton to simulate a light switch. Note that we assign the program itself as an ActionListener for the lightSwitch, so that

    import java.awt.*;
    import java.awt.event.*;
    import javax.swing.*;
    
    public class ToggleFrame extends JFrame
                                implements ActionListener {
      private ToggleButton lightSwitch;  
    
      public ToggleFrame() {
        lightSwitch = new ToggleButton ("off","on");
        getContentPane().add(lightSwitch);
        lightSwitch.addActionListener(this);
      } // init()
    
      public void actionPerformed(ActionEvent e)  {
        setTitle("The light is " + lightSwitch.getText());
      } // actionPerformed()
    
       public static void main(String args[]) 
       {  
          JFrame f = new ToggleFrame();
          f.setSize(200,200);
          f.setVisible(true);
       }
    } // ToggleFrame

    When lightSwitch is clicked, the program displays the message, “The light is on,” or “The light is off,” in the program’s title bar (Fig. 8.12). This is a somewhat trivial action but it illustrates that a ToggleButton both toggles its own label and carries out some associated action.

    The ToggleButton design satisfies several key design principles of object-oriented programming. First and foremost, it uses inheritance to extend the functionality of the predefined JButton class—the extensibility principle. Secondly, it encapsulates a ToggleButton’s essential behavior within the ToggleButton class itself—the modularity principle. Finally, it hides the mechanism by which a ToggleButton manages its labels—the information-hiding principle.

    Write a code segment (not a whole method) to swap two boolean variables, b1 and b2.

    Suppose you are designing an GUI that plays a card game, and you want a single button that can be used both to deal the cards and to collect the cards. Write a code segment that creates this type of button, adds it to the JFrame, and designates the JFrame as its ActionListener.

    Special Topic: Historical Cryptography

    Cryptography, the study of secret writing, has had a long and interesting history. Modern-day cryptographic techniques employ sophisticated mathematics to encrypt and decrypt messages. Today’s most encryption schemes are safe from attack by even the most powerful computers. Given our widespread dependence on computers and the Internet, secure encryption has become an important application area within computer science. While the cryptographic techniques used up through World War II are too simple to serve as the basis for modern-day encryption schemes, they can provide an interesting and accessible introduction to this important area of computer science.
    One of the earliest and simplest ciphers is the Caesar cipher, used by Julius Caesar during the Gallic wars. According to this scheme, letters of the alphabet are shifted by three letters, wrapping around at the end of the alphabet:

    PlainText:     abcdefghijklmnopqrstuvwxyz
    CaesarShifted: defghijklmnopqrstuvwxyzabc

    When encrypting a message, you take each letter of the message and replace it with its corresponding letter from the shifted alphabet. To decrypt a secret message, you perform the operation in reverse—that is, you take the letter from the shifted alphabet and replace it with the corresponding letter from the plaintext alphabet. Thus, “hello” would be Caesar encrypted as “khoor.”
    The Caesar cipher is a substitution cipher, because each letter in the plaintext message is replaced with a substitute letter from the ciphertext alphabet. A more general form of a substitution cipher uses a keyword to create a ciphertext alphabet:

    PlainText:  abcdefghijklmnopqrstuvwxyz
    Ciphertext: xylophneabcdfgijkmqrstuvwz

    In this example, the keyword “xylophone,” (with the second o removed) is used to set up a substitution alphabet. According to this cipher, the word “hello” would be encrypted as “epddi.” Substitution ciphers of this form are found frequently in cryptogram puzzles in the newspapers.
    Another type of cipher is known as a transposition cipher. In this type of cipher, the letters in the original message are rearranged in some methodical way. A simple example would be if we reversed the letters in each word so that “hello” became “olleh.”

    Example: The Cipher Class Hierarchy

    Suppose we wish to design a collection of cipher classes, including a Caesar cipher and a transposition cipher. Because the basic operations used in all forms of encryption are the same, both the class and the Transpose class will have methods to encrypt() and decrypt() messages, where each message is assumed to be a string of words separated by spaces. These methods will take a String of words and translate each word using the encoding method that is appropriate for that cipher. Therefore, in addition to encrypt() and decrypt(), each cipher class will need polymorphic encode() and decode() methods, which take a single word and encode or decode it according to the rules of that particular cipher.

    From a design perspective the encrypt() and decrypt() methods will be the same for every class: They simply break the message into words and encode or decode each word. However, the encode() and decode() methods will be different for each different cipher. The Caesar.encode() method should replace each letter of a word with its substitute, whereas the Transpose.encode() method should rearrange the letters of the word. Given these considerations, how should we design this set of classes?

    Because all of the various ciphers will have the same methods, it will be helpful to define a common Cipher superclass (Fig. [fig-cipherhier]). Cipher will encapsulate those features that the individual cipher classes have in common—the encrypt(), decrypt(), encode(), and decode() methods.

    Some of these methods can be implemented in the Cipher class itself. For example, the encrypt() method should take a message in a String parameter, encode each word in the message, and return a String result. The following method definition will work for any cipher:

    public String encrypt(String s) {
      StringBuffer result = new StringBuffer("");          
      StringTokenizer words = new StringTokenizer(s);// Tokenize
      while (words.hasMoreTokens()) {        // Encode each word 
        result.append(encode(words.nextToken()) + " "); 
      }
      return result.toString();        // Return result
    } // encrypt()

    This method creates a local StringBuffer variable, result, and uses StringTokenizer to break the original String into its component words. It uses the encode() method to encode the word, appending the result into result. The result is converted back into a String and returned as the encrypted translation of s, the original message.

    If we define encrypt() in the superclass, it will be inherited by all of Cipher’s subclasses. Thus, if we define Caesar and Transpose as

    public class Caesar extends Cipher { ... }
    public class Transpose extends Cipher { ... }

    instances of these classes will be able to use the encrypt() method.

    On the other hand, the polymorphic encode() method cannot be implemented within Cipher. This is because unlike the encrypt() method, which is the same for every Cipher subclass, the encode() method will be different for every subclass. However, by declaring the encode() method as abstract, we can leave its implementation up to the Cipher subclasses. Thus, within the Cipher class, we would define encode() and decode() as follows:

               // Abstract methods
    public abstract String encode(String word); 
    public abstract String decode(String word);

    These declarations within the Cipher class tell the compiler that these methods will be implemented in Cipher’s subclasses. By defining it as abstract, encode() can be used in the Cipher class, as it is within the encrypt() method.

    Figure [fig-cipher] provides the full definition of the Cipher class. The and decode() methods are declared abstract. They are intended to be implemented by Cipher’s subclasses.

    import java.util.*;
    
    public abstract class Cipher {
      public String encrypt(String s) {
        StringBuffer result = new StringBuffer("");         // Use a StringBuffer
        StringTokenizer words = new StringTokenizer(s); // Break s into its words
        while (words.hasMoreTokens()) {                     // For each word in s
          result.append(encode(words.nextToken()) + " ");   //  Encode it
        }
        return result.toString();                            // Return the result
      } // encrypt()
    
      public String decrypt(String s) {
        StringBuffer result = new StringBuffer("");        // Use a StringBuffer
        StringTokenizer words = new StringTokenizer(s);    // Break s into words
        while (words.hasMoreTokens()) {                    // For each word in s
          result.append(decode(words.nextToken()) + " ");  //  Decode it
        }
        return result.toString();                       // Return the decryption
      } // decrypt()
    
      public abstract String encode(String word);            // Abstract methods
      public abstract String decode(String word);
    } // Cipher

    Note again that encrypt() and decrypt(), which are implemented in , invoke encode() and decode(), respectively, which are declared in Cipher but implemented in Cipher’s subclasses. Java’s dynamic binding mechanism will take care of invoking the appropriate implementation of encode() or decode(), depending on what type of object is involved. For example, if caesar and transpose are Caesar and Transpose objects, respectively, then the following calls to encrypt() will cause their respective encode() methods to be invoked:

    // Invokes caesar.encode()
    caesar.encrypt("hello world");     
    // Invokes transpose.encode()
    transpose.encrypt("hello world");  

    When caesar.encrypt() is called, it will in turn invoke caesar.encode()—that is, it will call the encode() method implemented in the Caesar class. When transpose.encrypt() is invoked, it will in turn invoke transpose.encode(). In this way, each object can perform the encoding algorithm appropriate for its type of cipher.

    Algorithm Design: Shifting Characters

    The Caesar class is defined as an extension of Cipher (Fig. [fig-caesar]). The only methods implemented in Caesar are encode() and decode(). The encode() method takes a String parameter and returns a String result. It takes each character of its parameter (word.charAt(k)) and performs a Caesar shift on the character. Note how the shift is done:

    ch = (char)('a' + (ch -'a'+ 3) % 26);// Caesar shift

    Recall from Chapter 5 that char data in Java are represented as 16-bit integers. This enables us to manipulate characters as numbers. Thus, to shift a character by 3, we simply add 3 to its integer representation.

    public class Caesar extends Cipher {
      public String encode(String word) {
        StringBuffer result = new StringBuffer(); // Initialize a string buffer
        for (int k = 0; k < word.length(); k++) { // For each character in word
          char ch = word.charAt(k);               //  Get the character
          ch = (char)('a' + (ch -'a'+ 3) % 26);   //  Perform caesar shift
          result.append(ch);                   //  Append it to new string
        }
        return result.toString();              // Return the result as a string
      } // encode()
    
      public String decode(String word) {
        StringBuffer result = new StringBuffer(); // Initialize a string buffer
        for (int k = 0; k < word.length(); k++) { // For each character in word
        char ch = word.charAt(k);                 //  Get the character
           ch = (char)('a' + (ch - 'a' + 23) % 26); //  Perform reverse shift
           result.append(ch);                     //  Append it to new string
        }
        return result.toString();            // Return the result as a string
      } // decode()
    } // Caesar

    For example, suppose that the character (ch) is h, which has an ASCII code of 104 (see Table 5.13). We want to shift it by 3, giving k, which has a code of 107. In this case, we could simply add 3 to 104 to get the desired result. However, suppose that ch was the character y, which has an ASCII code of 121. If we simply add 3 in this case, we get 124, a code that corresponds to the symbol “|,” which is not our desired result. Instead, we want the shift in this case to “wrap around” to the beginning of the alphabet, so that y gets shifted into b. In order to accomplish this we need to do some modular arithmetic.

    Let’s suppose the 26 characters a to z were numbered 0 through 25, so that a corresponds to 0, b to 1, and so on up to z as 25. If we take any number N and divide it (modulo 26), we would get a number between 0 and 25. Suppose, for example, y were numbered 24. Shifting it by 3 would give us 27, and 27 % 26 would give us 1, which corresponds to b. So, if the a to z were numbered 0 through 25, then we can shift any character within that range by using the following formula:

    (ch + 3) % 26         // Shift by 3 with wraparound

    To map a character in the range a to z onto the integers 0 to 25, we can simply subtract a from it:

    'a' - 'a' = 0
    'b' - 'a' = 1
    'c' - 'a' = 2
    ...
    'z' - 'a' = 25

    Finally, we simply map the numbers 0 through 25 back to the characters a to z to complete the shift operation:

    (char)('a' + 0) = 'a'
    (char)('a' + 1) = 'b'
    (char)('a' + 2) = 'c'
    ...
    (char)('a' + 25) = 'z'

    Note the use here of the cast operator (char) to covert an integer into a char.

    To summarize, we can shift any character by 3 if we map it into the range 0 to 25, then add 3 to it mod 26, then map that result back into the range a to z. Thus, shifting y would go as follows:

    (char)('a' + (ch -'a'+ 3) % 26)   //  Perform Caesar shift
    (char)('a' + ('y' - 'a' +3) % 26) //   on 'y'
    (char)(97 + (121 - 97 + 3) % 26)      //  Map 'y' to 0..25
    (char)(97 + (27 % 26))     //  Shift by 3, wrapping around
    (char)(97 + 1)           //  Map result back to 'a' to 'z'
    (char)(98)                    //  Convert from int to char
    'b'

    Note that in decode() a reverse Caesar shift is done by shifting by 23, which is \(26-3\). If the original shift is 3, we can reverse that by shifting an additional 23. Together this gives a shift of 26, which will give us back our original string.

    The Transpose class (Fig. [fig-transpose]) is structured the same as the Caesar class. It implements both the encode() and decode() methods. The key element here is the transpose operation, which in this case is a simple reversal of the letters in the word. Thus, “hello” becomes “olleh”. This is very easy to do when using the StringBuffer.reverse() method. The decode() method is even simpler, because all you need to do in this case is call encode(). Reversing the reverse of a string gives you back the original string.

    public class Transpose extends Cipher {
      // encode() reverses and returns a word
      public String encode(String word) {
        StringBuffer result = new StringBuffer(word);
        return result.reverse().toString(); 
      } // encode()
    
      public String decode(String word) {
        return encode(word);         // Just call encode
      } // decode
    } // Transpose

    Testing and Debugging

    Figure [fig-testcipher] provides a simple test program for testing Cipher and its subclasses. It creates a Caesar cipher and

    public class TestEncrypt {
      public static void main(String argv[]) {
        Caesar caesar = new Caesar();
        String plain = "this is the secret message";   // Here's the message
        String secret = caesar.encrypt(plain);         // Encrypt the message
        System.out.println(" ********* Caesar Cipher Encryption *********");
        System.out.println("PlainText: " + plain);    // Display the results
        System.out.println("Encrypted: " + secret);
        System.out.println("Decrypted: " + caesar.decrypt(secret));// Decrypt
    
        Transpose transpose = new Transpose();
        secret = transpose.encrypt(plain);
        System.out.println("\n ********* Transpose Cipher Encryption *********");
        System.out.println("PlainText: " + plain);    // Display the results
        System.out.println("Encrypted: " + secret);
        System.out.println("Decrypted: " + transpose.decrypt(secret));// Decrypt
      } // main()
    } // end TestEncrypt

    a Transpose cipher and then encrypts and decrypts the same sentence using each cipher. If you run this program, it will produce the following output:

         ********* Caesar Cipher Encryption *********
        PlainText: this is the secret message
        Encrypted: wklv lv wkh vhfuhw phvvdjh
        Decrypted: this is the secret message
    
         ********* Transpose Cipher Encryption *********
        PlainText: this is the secret message
        Encrypted: siht si eht terces egassem
        Decrypted: this is the secret message

    Modify the Caesar class so that it will allow various sized shifts to be used. (Hint: Use an instance variable to represent the shift.)

    Modify Transpose.encode() so that it uses a rotation instead of a reversal. That is, a word like “hello” should be encoded as “ohell” with a rotation of one character.

    Case Study: A Two Player Game Hierarchy

    In this section we will redesign our OneRowNim game to fit within a hierarchy of classes of two-player games, which are games that involve two players. Many games that this characteristic: checkers, chess, tic-tac-toe, guessing games, and so forth. However, there are also many games that involve just 1 player: blackjack, solitaire, and others. There are also games that involve two or more players, such as many card games. Thus, our redesign of OneRowNim as part of a two-player game hierarchy will not be our last effort to design a hierarchy of game-playing classes. We will certainly re-design things as we learn new Java language constructs and as we try to extend our game library to other kinds of games.

    This case study will illustrate how we can apply inheritance and polymorphism, as well as other object-oriented design principles. The justification for revising OneRowNim at this point is to make it easier to design and develop other two-player games. As we have seen, one characteristic of class hierarchies is that more general attributes and methods are defined in top-level classes. As one proceeds down the hierarchy, the methods and attributes become more specialized. Creating a subclass is a matter of specializing a given class.

    Design Goals

    One of our design goals is to revise the OneRowNim game so that it fits into a hierarchy of two-player games. One way to do this is to generalize the OneRowNim game by creating a superclass that contains those attributes and methods that are common to all two-player games. The superclass will define the most general and generic elements of two-player games. All two-player games, including OneRowNim, will be defined as subclasses of this top-level superclass and will inherit and possibly override its public and protected variables and methods. Also, our top-level class will contain certain abstract methods, whose implementations will be given in OneRowNim and other subclasses.

    A second goal is to design a class hierarchy that makes it possible for computers to play the game, as well as human users. Thus, for a given two-player game, it should be possible for two humans to play each other, or for two computers to play each other, or for a human to play against a computer. This design goal will require that our design exhibit a certain amount of flexibility. As we shall see, this is a situation in which Java interfaces will come in handy.

    Another important goal is to design a two-player game hierarchy that can easily be used with a variety of different user interfaces, including command-line interfaces and GUIs. To handle this feature, we will develop Java interfaces to serve as interfaces between our two-player games and various user interfaces.

    Designing the TwoPlayerGame Class

    To begin revising the design of the OneRowNim game, we first need to design a top-level class, which we will call the TwoPlayerGame class. What variables and methods belong in this class? One way to answer this question is to generalize our current version of OneRowNim by moving any variables and methods that apply to all two-player games up to the TwoPlayerGame class. All subclasses of TwoPlayerGame—which includes the OneRowNim class—would inherit these elements. Figure [fig-onerownim] shows the current design of OneRowNim.

    What variables and methods should we move up to the TwoPlayerGame class? Clearly, the class constants, PLAYER_ONE and PLAYER_TWO, apply to all two-player games. These should be moved up. On the other hand, the MAX_PICKUP and MAX_STICKS constants apply just to the OneRowNim game. They should remain in the OneRowNim class.

    The nSticks instance variable is a variable that only applies to the OneRowNim game, but not to other two-player games. It should stay in the OneRowNim class. On the other hand, the onePlaysNext variable applies to all two-player games, so we will move it up to the TwoPlayerGame class.

    Because constructors are not inherited, all of the constructor methods will remain in the OneRowNim class. The instance methods, takeSticks() and getSticks(), are particular to OneRowNim, so they should remain there. However, the other methods, getPlayer(), gameOver(), getWinner(), and reportGameState(), are methods that would be useful to all two-player games. Therefore these methods should be moved up to the superclass. Of course, while these methods can be defined in the superclass, some of the methods can only be implemented in subclasses. For example, the reportGameState() method reports the current state of the game, so it has to be implemented in OneRowNim. Similarly, the getWinner() method defines how the winner of the game is determined, a definition that can only occur in the subclass. Every two-player game needs methods such as these. Therefore, we will define these methods as abstract methods in the superclass. The intention is that TwoPlayerGame subclasses will provide game-specific implementations for these methods.

    Given these considerations, we come up with the design shown in Figure [fig-twoplayergame]. The design shown in this figure is much more complex than designs we have used in earlier chapters. However, the complexity comes from combining ideas already discussed in previous sections of this chapter, so don’t be put off by it.

    To begin with, notice that we have introduced two Java interfaces into our design in addition to the TwoPlayerGame superclass. As we will show, these interfaces lead to a more flexible design and one that can easily be extended to incorporate new two-player games. Let’s take each element of this design separately.

    The TwoPlayerGame Superclass

    As we have stated, the purpose of the TwoPlayerGame class is to serve as the superclass for all two-player games. Therefore, it should define those variables and methods that are shared by two-player games.

    The PLAYER_ONE, PLAYER_TWO, and onePlaysNext variables and the getPlayer(), setPlayer(), and changePlayer() methods have been moved up from the OneRowNim class. Clearly, these variables and methods apply to all two-player games. Note that we have also added three new variables: nComputers, computer1, computer2 and their corresponding methods, getNComputers() and addComputerPlayer(). We will use these elements to give our games the ability to be played by computer programs. Because we want all of our two-player games to have this capability, we define these variables and methods in the superclass rather than in OneRowNim and subclasses of TwoPlayerGame.

    Note that the computer1 and computer2 variables are declared to be of type IPlayer. IPlayer is an interface, which contains a single method declaration, the makeAMove() method:

     public interface IPlayer {
         public String makeAMove(String prompt);
     }

    Why do we use an interface here rather than some type of game-playing object? This is a good design question. Using an interface here makes our design more flexible and extensible because it frees us from having to know the names of the classes that implement the makeAMove() method. The variables computer1 and computer2 will be assigned objects that implement IPlayer via the addComputerPlayer() method.

    The algorithms used in the various implementations of makeAMove() are game-dependent—they depend on the particular game being played. It would be impossible to define a game-playing object that would suffice for all two-player games. Instead, if we want an object that plays OneRowNim, we would define a OneRowNimPlayer and have it implement the IPlayer interface. Similarly, if we want an object that plays checkers, we would define a CheckersPlayer and have it implement the IPlayer interface. By using an interface here, our TwoPlayerGame hierarchy can deal with a wide range of differently named objects that play games, as long as they implement the IPlayer interface. So, using the IPlayer interface adds flexibility to our game hierarchy and makes it easier to extend it to new, yet undefined, classes. We will discuss the details of how to design a game player in one of the following sections.

    Turning now to the methods defined in TwoPlayerGame, we have already seen implementations of getPlayer(), setPlayer(), and changePlayer() in the OneRowNim class. We will just move those implementations up to the superclass. The getNComputers() method is the accessor method for the nComputers variable, and its implementation is routine. The addComputerPlayer() method adds a computer player to the game. Its implementation is as follows:

    public void addComputerPlayer(IPlayer player) {
       if (nComputers == 0)
          computer2 = player;
       else if (nComputers == 1)
          computer1 = player;
       else 
          return; // No more than 2 players
       ++nComputers;
    }

    As we noted earlier, the classes that play the various TwoPlayerGames must implement the IPlayer interface. The parameter for this method is of type IPlayer. The algorithm we use checks the current value of nComputers. If it is 0, which means that this is the first IPlayer added to the game, the player is assigned to computer2. This allows the human user to be associated with PLAYERONE, if this is a game between a computer and a human user.

    If nComputers equals 1, which means that we are adding a second IPlayer to the game, we assign that player to computer1. In either of these cases, we increment nComputers. Note what happens if nComputers is neither 1 nor 2. In that case, we simply return without adding the IPlayer to the game and without incrementing nComputers. This, in effect, limits the number of IPlayers to two. (A more sophisticated design would throw an exception to report an error. but we will leave that for a subsequent chapter.)

    The addComputerPlayer() method is used to initialize a game after it is first created. If this method is not called, the default assumption is that nComputers equals zero and that computer1 and computer2 are both null. Here’s an example of how it could be used:

     OneRowNim nim = new OneRowNim(11); // 11 sticks
     nim.add(new NimPlayer(nim));       // 2 computer players
     nim.add(new NimPlayerBad(nim));

    Note that the NimPlayer() constructor takes a reference to the game as its argument. Clearly, our design should not assume that the names of the IPlayer objects would be known to the TwoPlayerGame superclass. This method allows the objects to be passed in at run time. We will discuss the details of NimPlayerBad in a subsequent section.

    The getRules() method is a new method whose purpose is to return a string that describes the rules of the particular game. This method is implemented in the TwoPlayerGame class with the intention that it will be overridden in the various subclasses. For example, its implementation in TwoPlayerGame is:

     public String getRules() {
         return "The rules of this game are: ";
     }

    and its redefinition in OneRowNim is:

    public String getRules() {
      return "\n*** The Rules of One Row Nim ***\n" +
      "(1) A number of sticks between 7 and " + MAX_STICKS + 
              " is chosen.\n" + 
      "(2) Two players alternate making moves.\n" + 
      "(3) A move consists of subtracting between 1 and\n\t" + 
      MAX_PICKUP + 
      " sticks from the current number of sticks.\n" + 
      "(4) A player who cannot leave a positive\n\t" + 
      " number of sticks for the other player loses.\n";
    }

    The idea is that each TwoPlayerGame subclass will take responsibility for specifying its own set of rules in a form that can be displayed to the user.

    You might recognize that defining getRules() in the superclass and allowing it to be overridden in the subclasses is a form of polymorphism. It follows the design of the toString() method, which we discussed earlier. This design will allow us to use code that takes the following form:

      TwoPlayerGame game = new OneRowNim();
      System.out.println(game.getRules());

    In this example the call to getRules() is polymorphic. The dynamic binding mechanism is used to invoke the getRules() method that is defined in the OneRowNim class.

    The remaining methods in TwoPlayerGame are defined abstractly. The gameOver() and getWinner() methods are both methods that are game dependent. That is, the details of their implementations depend on the particular TwoPlayerGame subclass in which they are implemented.

    This is good example of how abstract methods should be used in designing a class hierarchy. We give abstract definitions in the superclass and leave the detailed implementations up to the individual subclasses. This allows the different subclasses to tailor the implementations to their particular needs, while allowing all subclasses to share a common signature for these tasks. This allows us to use polymorphism to create flexible, extensible class hierarchies.

    Figure [fig-twoplayercode] shows the complete implementation of the abstract TwoPlayerGame class. We have already discussed the most important details of its implementation.

    public abstract class TwoPlayerGame {   
      public static final int PLAYER_ONE = 1;
      public static final int PLAYER_TWO = 2;
    
      protected boolean onePlaysNext = true;
      protected int nComputers = 0;     // How many computers
                                    // Computers are IPlayers
      protected IPlayer computer1, computer2; 
     
      public void setPlayer(int starter) {   
        if (starter == PLAYER_TWO)
          onePlaysNext = false;
        else onePlaysNext = true;
      } //setPlayer()
      public int getPlayer() {   
        if (onePlaysNext) 
          return PLAYER_ONE;
        else return PLAYER_TWO;
      } //getPlayer()
      public void changePlayer() {   
        onePlaysNext = !onePlaysNext;
      } //changePlayer()
      public int getNComputers() {
        return nComputers;
      }
      public String getRules() {
        return "The rules of this game are: ";
      }
      public void addComputerPlayer(IPlayer player) {
        if (nComputers == 0)
          computer2 = player;
        else if (nComputers == 1)
          computer1 = player;
        else 
          return;  // No more than 2 players
        ++nComputers;
      }
        public abstract boolean gameOver();// Abstract Methods
        public abstract String getWinner();
    } //TwoPlayerGame

    The CLUIPlayableGame Interface

    Let’s turn now to the two interfaces shown in Figure [fig-twoplayergame]. Taken together, the purpose of these interfaces is to create a connection between any two-player game and a command-line user interface (CLUI). The interfaces provide method signatures for the methods that will implement the details of the interaction between a TwoPlayerGame and a UserInterface. Because the details of this interaction vary from game to game, it is best to leave the implementation of these methods to the games themselves.

    Note that CLUIPlayableGame extends the IGame interface. The IGame interface contains two methods that are used to define a standard form of communication between the CLUI and the game. The getGamePrompt() method defines the prompt that is used to signal the user for some kind of move—for example, “How many sticks do you take (1, 2, or 3)?” And the reportGameState() method defines how that particular game will report its current state—for example, “There are 11 sticks remaining.” CLUIPlayableGame adds the play() method to these two methods. As we will see shortly, the play() method will contain the code that will control the playing of the game.

    The source code for these interfaces is very simple:

    public interface CLUIPlayableGame extends IGame {
        public abstract void play(UserInterface ui);
    }
    public interface IGame {
        public String getGamePrompt();
        public String reportGameState();
    } //IGame

    Notice that the CLUIPlayableGame interface extends the IGame interface. A CLUIPlayableGame is a game that can be played through a CLUI. The purpose of its play() method is to contain the game dependent control loop that determines how the game is played via some kind of user interface (UI). In pseudocode, a typical control loop for a game would look something like the following:

    Initialize the game.
    While the game is not over 
      Report the current state of the game via the UI.
      Prompt the user (or the computer) to make a move via the UI.
      Get the user's move via the UI.
      Make the move.
      Change to the other player.

    The play loop sets up an interaction between the game and the UI. The UserInterface parameter allows the game to connect directly to a particular UI. To allow us to play our games through a variety of UIs, we define UserInterface as the following Java interface:

     public interface UserInterface {
         public String getUserInput();
         public void report(String s);
         public void prompt(String s);
     }

    Any object that implements these three methods can serve as a UI for one of our TwoPlayerGames. This is another example of the flexibility of using interfaces in object-oriented design.

    To illustrate how we use UserInterface, let’s attach it to our KeyboardReader class, thereby letting a KeyboardReader serve as a CLUI for TwoPlayerGames. We do this simply by implementing this interface in the KeyboardReader class, as follows:

     public class KeyboardReader implements UserInterface

    As it turns out, the three methods listed in UserInterface match three of the methods in the current version of KeyboardReader. This is no accident. The design of UserInterface was arrived at by identifying the minimal number of methods in KeyboardReader that were needed to interact with a TwoPlayerGame.

    The benefit of defining the parameter more generally as a , instead of as a KeyboardReader, is that we will eventually want to allow our games to be played via other kinds of command-line interfaces. For example, we might later define an Internet-based CLUI that could be used to play OneRowNim among users on the Internet. This kind of extensibility—the ability to create new kinds of UIs and use them with TwoPlayerGames— is another important design feature of Java interfaces.

    As Figure [fig-twoplayergame] shows, OneRowNim implements the CLUIPlayableGame interface, which means it must supply implementations of all three abstract methods: play(), getGamePrompt(), and reportGameState().

    Object Oriented Design: Interfaces or Abstract Classes

    Why are these methods defined in interfaces? Couldn’t we just as easily define them in the TwoPlayerGame class and use inheritance to extend them to the various game subclasses? After all, isn’t the net result the same, namely, that OneRowNim must implement all three methods.

    These are very good design questions, exactly the kinds of questions one should ask when designing a class hierarchy of any sort. As we pointed out in the Animal example earlier in the chapter, you can get the same functionality from a abstract interface and from an abstract superclass method. When should we put the abstract method in the superclass and when does it belong in an interface? A very good discussion of these and related object-oriented design issues is available in Java Design, 2nd Edition, by Peter Coad and Mark Mayfield (Yourdan Press, 1999). Our discussion of these issues follows many of the guidelines suggested by Coad and Mayfield.

    We have already seen that using Java interfaces increases the flexibility and extensibility of a design. Methods defined in an interface exist independently of a particular class hierarchy. By their very nature, interfaces can be attached to any class, which makes them very flexible to use.

    Another useful guideline for answering this question is that the superclass should contain the basic common attributes and methods that define a certain type of object. It should not necessarily contain methods that define certain roles that the object plays. For example, the gameOver() and getWinner() methods are fundamental parts of the definition of a TwoPlayerGame. One cannot define a game without defining these methods. By contrast, methods such as play(), getGamePrompt(), and reportGameState() are important for playing the game but they do not contribute in the same way to the game’s definition. Thus these methods are best put into an interface. So, one important design guideline is:

    The Revised OneRowNim Class

    Figure [fig-revisednim] provides a listing of the revised OneRowNim class, one that fits into the TwoPlayerGame class hierarchy. Our discussion in this section will focus on just those features of the game that are new or revised.

    public class OneRowNim extends TwoPlayerGame 
                                     implements CLUIPlayableGame {   
      public static final int MAX_PICKUP = 3;
      public static final int MAX_STICKS = 11;
      private int nSticks = MAX_STICKS;
    
      public OneRowNim() { }        // Constructors
      public OneRowNim(int sticks) {   
        nSticks = sticks;
      }  // OneRowNim()
      public OneRowNim(int sticks, int starter) {   
        nSticks = sticks;
        setPlayer(starter);
      } // OneRowNim()
      public boolean takeSticks(int num) {   
        if (num < 1 || num > MAX_PICKUP || num > nSticks) 
          return false;                // Error
        else                             // Valid move
        {   nSticks = nSticks - num;
            return true;
        } //else
      } // takeSticks()
      public int getSticks() {   
        return nSticks;
      } // getSticks()
      public String getRules() {
        return "\n*** The Rules of One Row Nim ***\n" +
        "(1) A number of sticks between 7 and " + MAX_STICKS + 
             " is chosen.\n" + 
        "(2) Two players alternate making moves.\n" + 
        "(3) A move consists of subtracting between 1 and\n\t" + 
            MAX_PICKUP + " sticks from the current number of sticks.\n" + 
        "(4) A player who cannot leave a positive\n\t" + 
        " number of sticks for the other player loses.\n";
      } // getRules()
      public boolean gameOver() {   /** From TwoPlayerGame */
        return (nSticks <= 0);
      }  // gameOver()
      public String getWinner()  {       /** From TwoPlayerGame */
        if (gameOver()) //{
          return "" + getPlayer() + " Nice game.";
        return "The game is not over yet.";   // Game is not over
      } // getWinner()

    -6pc

      /** From CLUIPlayableGame */
      public String getGamePrompt() {
        return "\nYou can pick up between 1 and " + 
                        Math.min(MAX_PICKUP,nSticks) + " : ";
      } // getGamePrompt()
      public String reportGameState() {   
        if (!gameOver())
           return ("\nSticks left: " + getSticks() + 
              " Who's turn: Player " + getPlayer());
        else
           return ("\nSticks left: " + getSticks() + 
            " Game over! Winner is Player "  + getWinner() +"\n");
      }   // reportGameState()
      public void play(UserInterface ui) {// From CLUIPlayableGame interface
        int sticks = 0;
        ui.report(getRules());
        if (computer1 != null) 
          ui.report("\nPlayer 1 is a " + computer1.toString());
        if (computer2 != null)
          ui.report("\nPlayer 2 is a " + computer2.toString());
        while(!gameOver()) {  
          IPlayer computer = null;     // Assume no computers
          ui.report(reportGameState());
          switch(getPlayer()) {
          case PLAYER_ONE:             // Player 1's turn
            computer = computer1;
            break;
          case PLAYER_TWO:             // Player 2's turn
            computer = computer2;
            break;
          } // cases
          if (computer != null) {      // If computer's turn
            sticks = Integer.parseInt(computer.makeAMove(""));
            ui.report(computer.toString() + " takes " + 
                                       sticks + " sticks.\n");
          } else {                  // otherwise, user's turn
            ui.prompt(getGamePrompt());
            sticks = 
              Integer.parseInt(ui.getUserInput()); // Get user's move 
          }
          if (takeSticks(sticks))      // If a legal move
            changePlayer();
        } // while
        ui.report(reportGameState());  // The game is now over
      } // play()
    } // OneRowNim class

    The gameOver() and getWinner() methods, which are now inherited from the TwoPlayerGame superclass, are virtually the same as in the previous version. One small change is that getWinner() now returns a String instead of an int. This makes that method more generally useful as a way of identifying the winner for all TwoPlayerGames.

    Similarly, the getGamePrompt() and reportGameState() methods merely encapsulate functionality that was present in the earlier version of the game. In our earlier version the prompts to the user were generated directly by the main program. By encapsulating this information in an inherited method, we make it more generally useful to all TwoPlayerGames.

    The major change to OneRowNim comes in the play() method, which controls the playing of the OneRowNim (Fig. [fig-revisednim2]). Because this version of the game incorporates computer players, the play loop is a bit more complex than in earlier versions of the game. The basic idea is still the same: The method loops until the game is over. On each iteration of the loop, one or the other of the two players, PLAYER_ONE or PLAYER_TWO, takes a turn making a move—that is, deciding how many sticks to pick up. If the move is a legal move, then it becomes the other player’s turn.

    Let’s look now at how the code distinguishes between whether it is a computer’s turn to move or a human player’s turn. Note that at the beginning of the while loop, it sets the computer variable to null. It then assigns computer a value of either computer1 or computer2, depending on whose turn it is. But recall that one or both of these variables may be null, depending on how many computers are playing the game. If there are no computers playing the game, then both variables will be null. If only one computer is playing, then computer1 will be null. This is determined during initialization of the game, when the addComputerPlayer() is called. (See above.)

    In the code following the switch statement, if computer is not null, then we call computer.makeAMove(). As we know, the makeAMove() method is part of the IPlayer interface. The makeAMove() method takes a String parameter that is meant to serve as a prompt, and returns a String that is meant to represent the IPlayer’s move:

     public interface IPlayer {
         public String makeAMove(String prompt);
     }

    In OneRowNim the “move” is an integer, representing the number of sticks the player picks. Therefore, in play() OneRowNim has to convert the String into an int, which represents the number of sticks the IPlayer picks up.

    On the other hand, if computer is null, this means that it is a human user’s turn to play. In this case, play() calls ui.getUserInput(), employing the user interface to input a value from the keyboard. The user’s input must also be converted from String to int. Once the value of sticks is set, either from the user or from the IPlayer, the play() method calls takeSticks(). If the move is legal, then it changes whose turn it is, and the loop repeats.

    There are a couple of important points to notice about the design of the play() method. First, the play() method has to know what to do with the input it receives from the user or the IPlayer. This is game-dependent knowledge. The user is inputting the number of sticks to take in OneRowNim. For a tic-tac-toe game, the “move” might represent a square on the tic-tac-toe board. This suggests that play() is a method that should be implemented in OneRowNim, as it is here, because OneRowNim encapsulates the knowledge of how to play the One Row Nim game.

    A second point is to notice that the method call computer.makeAMove() is another example of polymorphism at work. The play() method does not know what type of object the computer is, other than that it is an IPlayer—that is, an object that implements the IPlayer interface. As we will show in the next section, the OneRowNim game can be played by two different IPlayers: one named NimPlayer and another named NimPlayerBad. Each has its own game-playing strategy, as implemented by their own versions of the makeAMove() method. Java uses dynamic binding to decide which version of makeAMove() to invoke depending on the type of IPlayer whose turn it is. Thus, by defining different IPlayers with different makeAMove() methods, this use of polymorphism makes it possible to test different game-playing strategies against each other.

    The IPlayer Interface

    The last element of our design is the IPlayer interface, which, as we just saw, consists of the makeAMove() method. To see how we use this interface, let’s design a class to play the game of OneRowNim. We will call the class NimPlayerBad and give it a very weak playing strategy. For each move it will pick a random number between 1 and 3, or between 1 and the total number of sticks left, if there are fewer than 3 sticks. (We will leave the task of defining NimPlayer, a good player, as an exercise.)

    As an implementer of the IPlayer interface, NimPlayerBad will implement the makeAMove() method. This method will contain NimPlayerBad’s strategy (algorithm) for playing the game. The result of this strategy will be the number of sticks that the player will pick up.

    What other elements (variables and methods) will a NimPlayerBad need? Clearly, in order to play OneRowNim, the player must know the rules and the current state of the game. The best way to achieve this is to give the Nim player a reference to the OneRowNim game. Then it can call getSticks() to determine how many sticks are left, and it can use other public elements of the OneRowNim game. Thus, we will have a variable of type OneRowNim, and we will assign it a value in a constructor method.

    Figure 8.23 shows the design of NimPlayerBad. Note that we have added an implementation of the toString() method. This will be used to give a string representation of the NimPlayerBad. Also, note that we have added a private helper method named randomMove(), which will simply generate an appropriate random number of sticks as the player’s move.

    public class NimPlayerBad implements IPlayer {   
      private OneRowNim game;
      public NimPlayerBad (OneRowNim game) {  
        this.game = game;
      } // NimPlayerBad()
      public String makeAMove(String prompt) {   
        return "" + randomMove();
      } // makeAMove()
      private int randomMove() {   
        int sticksLeft = game.getSticks();
        return 1 + (int)(Math.random() * 
               Math.min(sticksLeft, game.MAX_PICKUP));
      } // randomMove()
      public String toString() { 
        String className = 
            this.getClass().toString(); // Gets 'class NimPlayerBad'
        return className.substring(5);  // Cut off the word 'class'
      } // toString()
    } // NimPlayerBad

    The implementation of NimPlayerBad is shown in Figure [fig-nimbad]. The makeAMove() method converts the randomMove() to a String and returns it, leaving it up to OneRowNim, the calling object, to convert that move back into an int. Recall the statement in OneRowNim where makeAMove() is invoked:

     sticks = Integer.parseInt(computer.makeAMove(""));

    In this context, the computer variable, which is of type IPlayer, is bound to a NimPlayerBad object. In order for this interaction between the game and a player to work, the OneRowNim object must know what type of data is being returned by NimPlayerBad. This is a perfect use for a Java interface, which specifies the signature of makeAMove() without committing to any particular implementation of the method. Thus, the association between OneRowNim and IPlayer provides a flexible and effective model for this type of interaction.

    Finally, note the details of the randomMove() and toString() methods. The only new thing here is the use of the getClass() method in toString(). This is a method that is defined in the Object class and inherited by all Java objects. It returns a String of the form “class X” where X is the name of that object’s class. Note here that we are removing the word “class” from this string before returning the class name. This allows our IPlayer objects to report what type of players they are, as in the following statement from OneRowNim:

    ui.report("\nPlayer 1 is a " + computer1.toString());

    If computer1 is a NimPlayerBad, it would report “Player1 is a NimPlayerBad.”

    Define a class NimPlayer that plays the optimal strategy for OneRowNim. This strategy was described in Chapter 5.

    Playing OneRowNim

    Let’s now write a main() method to play OneRowNim:

    public static void main(String args[]) {
      KeyboardReader kb = new KeyboardReader();
      OneRowNim game = new OneRowNim();
      kb.prompt("How many computers are playing, 0, 1, or 2? ");
      int m = kb.getKeyboardInteger();
      for (int k = 0; k < m; k++) {
        kb.prompt("What type of player, " + 
                      "NimPlayerBad = 1, or NimPlayer = 2 ? ");
        int choice = kb.getKeyboardInteger();
        if (choice == 1) {
          IPlayer computer = new NimPlayerBad(game);
          game.addComputerPlayer(computer);
        } else {
          IPlayer computer = new NimPlayer(game);
          game.addComputerPlayer(computer);
        }
      }
      game.play(kb);
     } // main()

    After creating a KeyboardReader and then creating an instance of OneRowNim, we prompt the user to determine how many computers are playing. We then repeatedly prompt the user to identify the names of the IPlayer and use the addComputerPlayer() method to initialize the game. Finally, we get the game started by invoking the play() method, passing it a reference to the KeyboardReader, our UserInterface.

    Note that in this example we have declared a OneRowNim variable to represent the game. This is not the only way to do things. For example, suppose we wanted to write a main() method that could be used to play a variety of different TwoPlayerGames. Can we make this code more general? That is, can we rewrite it to work with any TwoPlayerGame?

    A OneRowNim object is also a TwoPlayerGame, by virtue of inheritance, and it is also a CLUIPlayableGame, by virtue of implementing that interface. Therefore, we can use either of these types to represent the game. Thus, one alternative way of coding this is as follows:

        TwoPlayerGame game = new OneRowNim();
        ...
        IPlayer computer = new NimPlayer((OneRowNim)game);
        ...
        ((CLUIPlayableGame)game).play(kb);

    Here we use a TwoPlayerGame variable to represent the game. However, note that we now have to use a cast expression, (CLUIPlayableGame), in order to call the play() method. If we don’t cast game in this way, Java will generate the following syntax error:

     OneRowNim.java:126: cannot resolve symbol
     symbol  : method play (KeyboardReader)
     location: class TwoPlayerGame
            game.play(kb);
               ^

    The reason for this error is that play() is not a method in the TwoPlayerGame class, so the compiler cannot find the play() method. By using the cast expression, we are telling the compiler to consider game to be a CLUIPlayableGame. That way it will find the play() method. Of course, the object assigned to nim must actually implement the CLUIPlayableGame interface in order for this to work at run time. We also need a cast operation in the NimPlayer() constructor in order to make the argument (computer) compatible with that method’s parameter.

    Another alternative for the main() method would be the following:

     CLUIPlayableGame game = new OneRowNim();
     ...
     IPlayer computer = new NimPlayer((OneRowNim)game);
     ((TwoPlayerGame)game).addComputerPlayer(computer);
     ...
     game.play(kb);
      nim.play(kb);

    By representing the game as a CLUIPlayableGame variable, we don’t need the cast expression to call play(), but we do need a different cast expression, (TwoPlayerGame), to invoke addComputerPlayer(). Again, the reason is that the compiler cannot find the addComputerPlayer() method in the CLUIPlayableGame interface, so we must tell it to consider game as a TwoPlayerGame, which of course it is. We still need the cast operation for the call to the NimPlayer() constructor.

    All three of the code options that we have considered will generate something like the interactive session shown in Figure [fig-playnim] for a game in which two IPlayers play each other.

    -2.5pc

    How many computers are playing, 0, 1, or 2? {\color{cyan}2}
    
    *** The Rules of One Row Nim ***
    (1) A number of sticks between 7 and 11 is chosen.
    (2) Two players alternate making moves.
    (3) A move consists of subtracting between 1 and
            3 sticks from the current number of sticks.
    (4) A player who cannot leave a positive
             number of sticks for the other player loses.
    
    Player 1 is a  NimPlayerBad
    Player 2 is a  NimPlayer
    Sticks left: 11 Who's turn: Player 1 NimPlayerBad takes 2 sticks.
    Sticks left: 9 Who's turn: Player 2 NimPlayer takes 1 sticks.
    Sticks left: 8 Who's turn: Player 1 NimPlayerBad takes 2 sticks.
    Sticks left: 6 Who's turn: Player 2 NimPlayer takes 1 sticks.
    Sticks left: 5 Who's turn: Player 1 NimPlayerBad takes 3 sticks.
    Sticks left: 2 Who's turn: Player 2 NimPlayer takes 1 sticks.
    Sticks left: 1 Who's turn: Player 1 NimPlayerBad takes 1 sticks.
    Sticks left: 0 Game over! Winner is Player 2 Nice game.

    Given our object-oriented design for the TwoPlayerGame hierarchy, we can now write generalized code that can play any TwoPlayerGame that implements the CLUIPlayableGame interface. We will give a specific example of this in the next section.

    Extending the TwoPlayerGame Hierarchy

    Now that we have described the design and the details of the TwoPlayerGame class hierarchy, let’s use it to develop a new game. If we’ve gotten the design right, developing new two-player games and adding them to the hierarchy should be much simpler than developing them from scratch.

    The new game is a guessing game in which the two players take turns guessing a secret word. The secret word will be generated randomly from a collection of words maintained by the game object. The letters of the word will be hidden with question marks, as in “????????.” On each turn a player guesses a letter. If the letter is in the secret word, it replaces one or more question marks, as in “??????E?.” A player continues to guess until an incorrect guess is made and then it becomes the other player’s turn. Of course, we want to develop a version of this game that can be played either by two humans, or by one human against a computer—that is, against an IPlayer—or by two different IPlayers.

    Let’s call the game class WordGuess. Following the design of OneRowNim, we get the design shown in Figure [fig-wordguessuml]. The WordGuess class extends the TwoPlayerGame class and implements the CLUIPlayableGame interface. We don’t show the details of the interfaces and the TwoPlayerGame class, as these have not changed. Also, following the design of NimPlayerBad, the WordGuesser class implements the IPlayer interface. Note how we show the association between WordGuess and zero or more IPlayers. A WordGuess uses between zero and two instances of IPlayers, which in this game are implemented as WordGuessers.

    public class WordGuess extends TwoPlayerGame implements CLUIPlayableGame {
        private String secretWord;
        private StringBuffer currentWord;
        private StringBuffer previousGuesses;
        private int unguessedLetters;
    
        public WordGuess() {   
            secretWord = getSecretWord();
            currentWord = new StringBuffer(secretWord);
            previousGuesses = new StringBuffer();
            for (int k = 0; k < secretWord.length(); k++)
               currentWord.setCharAt(k,'?');
            unguessedLetters = secretWord.length();
        } // WordGuess()
        public String getPreviousGuesses() {
            return previousGuesses.toString();
        } // getPreviousGuesses()
        public String getCurrentWord() {
            return currentWord.toString();
        } // getCurrentWord()
        private String getSecretWord() {   
            int num = (int)(Math.random()*10);
            switch (num)
            {   case 0: return "SOFTWARE";
                case 1: return "Solution";
                case 2: return "CONSTANT";
                case 3: return "COMPILER";
                case 4: return "ABSTRACT";
                case 5: return "ABNORMAL";
                case 6: return "ARGUMENT";
                case 7: return "QUESTION";
                case 8: return "UTILIZES";
                case 9: return "VARIABLE";
                default: return "MISTAKES";
            } //switch
        } // getSecretWord()
        private boolean guessLetter(char letter) {   
           previousGuesses.append(letter);
           if (secretWord.indexOf(letter) == -1)
                return false; // letter is not in secretWord
            else // find positions of letter in secretWord
            {   for (int k = 0; k < secretWord.length(); k++)
                {   if (secretWord.charAt(k) == letter)
                    {   if (currentWord.charAt(k) == letter)
                            return false; ////already guessed
                        currentWord.setCharAt(k,letter);
                        unguessedLetters--; //one less to find
                    } //if
                } //for
                return true;
            } //else
        } //guessLetter()
    
        public String getRules() {      // Overridden from TwoPlayerGame
            return "\n*** The Rules of Word Guess ***\n" +
            "(1) The game generates a secret word.\n" +
            "(2) Two players alternate taking moves.\n" +
            "(3) A move consists of guessing a letter in the word.\n" +
            "(4) A player continues guessing until a letter is wrong.\n" +
            "(5) The game is over when all letters of the word are guessed\n" +
            "(6) The player guessing the last letter of the word wins.\n";
        } //getRules()
        public boolean gameOver() {   // From TwoPlayerGame
            return (unguessedLetters <= 0);
        } // gameOver()
        public String getWinner() {  // From TwoPlayerGame
            if (gameOver())
                return "Player " + getPlayer();
            else return "The game is not over.";
        } // getWinner()
        public String reportGameState() {   
            if (!gameOver())
                return "\nCurrent word " + currentWord.toString() + " Previous guesses " 
                    + previousGuesses + "\nPlayer " + getPlayer() + " guesses next.";
            else
            return "\nThe game is now over! The secret word is " + secretWord 
                  + "\n" + getWinner() + " has won!\n";
        } // reportGameState()
        public String getGamePrompt() {    // From CLUIPlayableGame
            return "\nGuess a letter that you think is in the secret word: ";
        } // getGamePrompt()
        public String move(String s) {   
            char letter = s.toUpperCase().charAt(0);
            if (guessLetter(letter)) {   //if correct 
                return "Yes, the letter " + letter +
                       " IS in the secret word\n";
             } else {   
                 changePlayer();
                 return "Sorry, " + letter + " is NOT a " +
                        "new letter in the secret word\n";
             } 
        } // move()
        public void play(UserInterface ui) {  // From CLUIPlayableGame
            ui.report(getRules());
            if (computer1 != null) 
                ui.report("\nPlayer 1 is a " + computer1.toString());
            if (computer2 != null)
                ui.report("\nPlayer 2 is a " + computer2.toString());
    
            while(!gameOver()) {  
               IPlayer computer = null;       // Assume no computers playing
               ui.report(reportGameState());
               switch(getPlayer()) {
               case PLAYER_ONE:             // Player 1's turn
                   computer = computer1;
                   break;
               case PLAYER_TWO:             // Player 2's turn
                   computer = computer2;
                   break;
               } // cases
    
               if (computer != null) {      // If computer's turn
                   ui.report(move(computer.makeAMove("")));
               } else {                     // otherwise, user's turn
                   ui.prompt(getGamePrompt());
                   ui.report(move(ui.getUserInput()));
               }
            } // while
            ui.report(reportGameState());  // The game is now over
        } //play()
    } //WordGuess class

    Let’s turn now to the details of the WordGuess class, whose source code is shown in Figures [fig-wordguess-oop] and [fig-wordguess2-oop]. The game needs to have a supply of words from which it can choose a secret word to present to the players. The getSecretWord() method will take care of this task. It calculates a random number and then uses that number, together with a switch statement, to select from among several words that are coded right into the switch statement. The secret word is stored in the secretWord variable. The currentWord variable stores the partially guessed word. Initially, currentWord consists entirely of question marks. As the players make correct guesses, currentWord is updated to show the locations of the guessed letters. Because currentWord will change as the game progresses, it is stored in a StringBuffer, rather than in a String. Recall that Strings are immutable in Java, whereas a StringBuffer contains methods to insert letters and remove letters.

    The unguessedLetters variable stores the number of letters remaining to be guessed. When unguessedLetters equals 0, the game is over. This condition defines the gameOver() method, which is inherited from TwoPlayerGame. The winner of the game is the player who guessed the last letter in the secret word. This condition defines the getWinner() method, which is also inherited from TwoPlayerGame. The other methods that are inherited from TwoPlayerGame or implemented from the CLUIPlayableGame are also implemented in a straightforward manner.

    A move in the WordGuess game consists of trying to guess a letter that occurs in the secret word. The move() method processes the player’s guesses. It passes the guessed letter to the guessLetter() method, which checks whether the letter is a new, secret letter. If so, guessLetter() takes care of the various housekeeping tasks. It adds the letter to previousGuesses, which keeps track of all the players’ guesses. It decrements the number of unguessedLetters, which will become 0 when all the letters have been guessed. And it updates currentWord to show where all occurrences of the secret letter are located. Note how guessLetter() uses a for-loop to cycle through the letters in the secret word. As it does so, it replaces the question marks in currentWord with the correctly guessed secret letter. The guessLetter() method returns false if the guess is incorrect. In that case, the move() method changes the player’s turn. When correct guesses are made, the current player keeps the turn.

    The WordGuess game is a good example of a string-processing problem. It makes use of several of the String and StringBuffer methods that we learned in Chapter 7. The implementation of WordGuess, as an extension of TwoPlayerGame, is quite straight forward. One advantage of the TwoPlayerGame class hierarchy is that it decides many of the important design issues in advance. Developing a new game is largely a matter of implementing methods whose definitions have already been determined in the superclass or in the interfaces. This greatly simplifies the development process.

    Let’s now discuss the details of WordGuesser class (Fig. [fig-wordguesser-oop]). Note that the constructor takes a WordGuess parameter. This allows WordGuesser to be passed a reference to the game, which accesses the game’s public methods, such as getPreviousGuesses(). The toString() method is identical to the toString() method in the NimPlayerBad example. The makeAMove() method, which is part of the IPlayer interface, is responsible for specifying the algorithm that the player uses to make a move. The strategy in this case is to repeatedly pick a random letter from A to Z until a letter is found that is not contained in previousGuesses. That way, the player will not guess letters that have already been guessed.

    public class WordGuesser implements IPlayer {   
      private WordGuess game;
      public WordGuesser (WordGuess game) {   
        this.game = game;
      }
      public String makeAMove(String prompt)  {   
        String usedLetters = game.getPreviousGuesses();
        char letter;
        do {  // Pick one of 26 letters
          letter = (char)('A' + (int)(Math.random() * 26));
        } while (usedLetters.indexOf(letter) != -1);
        return "" + letter;
      }
      public String toString() { // returns 'NimPlayerBad'
        String className = this.getClass().toString(); 
        return className.substring(5);        
      }
    } // WordGuesser

    Principles Of Object-Oriented Design

    To conclude this chapter, it will be helpful to focus briefly on how the examples we’ve seen address the various object-oriented design (OOD) principles we set out at the beginning of the book.

    • Divide-and-Conquer Principle. Notice how all of the problems tackled in this chapter have been solved by dividing them into several classes, with each of the classes divided into separate methods. The very idea of a class hierarchy is an application of this principle.
    • Encapsulation Principle. The superclasses in our designs, Cipher and TwoPlayerGame, encapsulate those features of the class hierarchy that are shared by all objects in the hierarchy. The subclasses, CaesarCipher and OneRowNim, encapsulate features that make them distinctive with the class hierarchy.
    • Interface Principle. The several Java interfaces we’ve designed, IPlayer, CLUIPlayableGame and UserInterface, specify clearly how various types of related objects will interact with each other through the methods contained in the interfaces. Clean interfaces make for clear communication among objects.
    • Information Hiding Principle. We have continued to make consistent use of the private and public qualifiers, and have now introduced the protected qualifier to extend this concept. The inheritance mechanism gives subclasses access to protected and public elements of their superclasses.
    • Generality Principle. As you move down a well-designed class hierarchy, you go from the more general to the more specific features of the objects involved. The abstract encode() method specifies the general form that encoding will take while the various implementations of this method in the subclasses provide the specializations necessary to distinguish, say, Caesar encoding from Transpose encoding. Similarly, the abstract makeAMove() method in the IPlayer interface provides a general format for a move in a two-player game, while its various implementations provide the specializations that distinguish one game from another.
    • Extensibility Principle. Overriding inherited methods and implementing abstract methods from either an abstract superclass or a Java interface provide several well-designed ways to extend the functionality in an existing class hierarchy. Extending a class is a form of specialization of the features inherited from the superclass.
    • Abstraction Principle. Designing a class hierarchy is an exercise in abstraction, as the more general features of the objects involved are moved into the superclasses. Similarly, designing a Java interface or an abstract superclass method is a form of abstraction, whereby the signature of the method is distinguished from its various implementations.

    These, then, are some of the ways that the several examples we have considered and this chapter’s discussion have contributed to a deepening of our understanding of object-oriented design.

    abstract method

    actual type (dynamic type)

    ciphertext

    class inheritance

    cryptography

    dynamic binding (late binding)

    interface

    overloaded method

    plaintext

    polymorphic method

    polymorphism

    static binding (early binding)

    static type (declared type)

    substitution cipher

    transposition cipher

    Inheritance is an object-oriented mechanism whereby subclasses inherit the public and protected instance variables and methods from their superclasses.

    Dynamic binding (or late binding) is the mechanism by which a method call is bound to (associated with) the correct implementation of the method at run time. In Java, all method calls, except for final or private methods, are resolved using dynamic binding.

    Static binding (or early binding) is the association of a method call with its corresponding implementation at compile time.

    Polymorphism is an object-oriented language feature in which a method call can lead to different actions depending on the object on which it is invoked. A polymorphic method is a method signature that is given different implementation by different classes in a class hierarchy.

    A static type is a variable’s declared type. A dynamic type, or actual type, is the type of object assigned to that variable at a given point in a running program.

    An abstract method is a method definition that lacks an implementation. An abstract class is one that contains one or more abstract methods. An abstract class can be subclassed but not instantiated.

    A Java interface is a class that contains only method signatures and (possibly) constant declarations, but no variables. An interface can be implemented by a class by providing implementations for all of its abstract methods.

    Running the TestPrint program will produce the output shown here. It is clear that the inherited toString() method is used by println() when printing a TestPrint object.

     56.0
     TestPrint@be2d65

    If you override the toString() method in TestPrint, you should get something like the output shown here, depending on how you code toString(). It is clear that the toString() method is used polymorphously by println().

     56.0
     Hello from TestPrint

    The output produced when constructing objects of type A and B in the order shown in the exercise would be as follows, with each letter occurring on a separate line:

     A B B

    The new implementation of B’s method() will invoke A’s version of the method before printing B. This will print “A A B A B”.

     void method () { 
         super.method();  
         System.out.println("B");
     }

    Give the definitions of classes A and B in the exercise, the marked statements would be invalid:

    A a = new B();   // Valid a B is an A
    a = new A();     // Ok
    B b = new A();   // Invalid. An A is not necessarily a B
    b = new B();     // OK

    Given the class definitions and code segment in this exercise, the output would be, A A B A B C, with each letter printing on a separate line.

    Definition of an Pig subclass of Animal:

     public class Pig extends Animal {
        public Pig() {
            kind = "pig";
        }
        public String speak() {
            return "oink";
        }
    }

    If polymorphism was not used in our design, the talk() method would have to be modified to the following in order to accommodate a Pig subclass:

    public String talk(Animal a) {
      if (a instanceof Cow)
         return "I am a " + kind + " and I go " + a.moo();
       else if (a instanceof Cat)
         return "I am a " + kind + " and I go " + a.meow();
       else if (a instanceof Pig)
         return "I am a " + kind + " and I go " + a.oink();
       else
         return "I don't know what I am";
    }

    Code to swap two boolean variables:

     boolean temp = b1; // Save b1's value
     b1 = b2;           // Change b1 to b2
     b2 = temp;         // Change b2 to b1's original value

    Creating a ToggleButton that can be used to deal or collect cards:

     private ToggleButton dealer = 
                new ToggleButton("deal","collect");
     add(dealer);
     dealer.addActionListener(this);

    Modify the Caesar class so that it will allow various-sized shifts to be used.

    private int shift;                               
    public void setShift(int n) { shift = n;    }
    public int getShift()       { return shift; }
    // Modification to encode():
    ch = (char)('a' + (ch -'a'+ shift) % 26); // Shift
    // Modification to decode():
    ch = (char)('a' + (ch -'a'+ (26-shift)) % 26); // Shift

    Modify Transpose.encode() so that it uses a rotation instead of a reversal. The operation here is very similar to the shift operation in the Caesar cipher. It uses modular arithmetic to rearrange the letters in the word. For example, suppose the word is “hello”. Its letters are indexed from 0 to 4. The following table shows how the expression ((k+2) % 5) will rearrange the letters as k varies from 0 to 4:

        k  charAt(k)  (k+2) % 5   charAt((k+2) % 5)
        -------------------------------------------
        0  'h'          2          'l'
        1  'e'          3          'l'
        2  'l'          4          'o'
        3  'l'          0          'h'
        4  'o'          1          'e'
    
    // Modification to encode():
    public String encode(String word) {
        StringBuffer result = new StringBuffer();
        for (int k=0; k < word.length(); k++)
            result.append(word.charAt((k+2) % word.length()));
        return result.toString();
    }

    A NimPlayer class that plays the optimal OneRowNim game would be identical to the NimPlayerBad class except the move():int method would be replaced with the following implementation:

     public int move() {   
        int sticksLeft = game.getSticks();
        if (sticksLeft % (game.MAX_PICKUP + 1) != 1)
            return (sticksLeft - 1) % (game.MAX_PICKUP +1);
        else {
            int maxPickup = Math.min(game.MAX_PICKUP, sticksLeft);
             return 1 + (int)(Math.random() * maxPickup);
        }
     }

    Fill in the blanks in each of the following sentences:

    =13pt

    A method that lacks a body is an


    method.

    An


    is like a class except that it contains only instance methods, no instance variables.

    Two ways for a class to inherit something in Java is to


    a class and


    an interface.

    Instance variables and instance methods that are declared


    or


    are inherited by the subclasses.

    An object can refer to itself by using the


    keyword.

    If a GUI class intends to handle ActionEvents, it must implement the


    interface.

    A


    method is one that does different things depending upon the object that invokes it.

    =11pt

    Explain the difference between the following pairs of concepts:

    Class and interface.

    Stub method and abstract method.

    Extending a class and instantiating an object.

    Defining a method and implementing a method.

    A protected method and a public method.

    A protected method and a private method.

    Draw a hierarchy to represent the following situation. There are lots of languages in the world. English, French, Chinese, and Korean are examples of natural languages. Java, C, and C++ are examples of formal languages. French and Italian are considered romance languages, while Greek and Latin are considered classical languages.

    Look up the documentation for the JButton class on Sun’s Web site:

    http://java.sun.com/j2se/1.5.0/docs/api/

    List the names of all the methods that would be inherited by the ToggleButton subclass that we defined in this chapter.

    Design and write a toString() method for the ToggleButton class defined in this chapter. The toString() method should return the ToggleButton’s current label.

    Design a class hierarchy rooted in the class Employee that includes subclasses for HourlyEmployee and SalaryEmployee. The attributes shared in common by these classes include the name, and job title of the employee, plus the accessor and mutator methods needed by those attributes. The salaried employees need an attribute for weekly salary, and the corresponding methods for accessing and changing this variable. The hourly employees should have a pay rate and an hours worked variable. There should be an abstract method called calculateWeeklyPay(), defined abstractly in the superclass and implemented in the subclasses. The salaried worker’s pay is just the weekly salary. Pay for an hourly employee is simply hours worked times pay rate.

    Design and write a subclass of JTextField called IntegerField that is used for inputting integers but behaves in all other respects like a JTextField. Give the subclass a public method called getInteger().

    Implement a method that uses the following variation of the Caesar cipher. The method should take two parameters, a String and an int N. The result should be a String in which the first letter is shifted by N, the second by \(N+1\), the third by \(N+2\), and so on. For example, given the string “Hello,” and an initial shift of 1, your method should return “Igopt.”

    Write a method that converts its String parameter so that letters are written in blocks five characters long.

    Design and implement an GUI that lets the user type a document into a TextArea and then provides the following analysis of the document: the number of words in the document, the number of characters in the document, and the percentage of words that have more than six letters.

    Design and implement a Cipher subclass to implement the following substitution cipher: Each letter in the alphabet is replaced with a letter from the opposite end of the alphabet: a is replaced with z, b with y, and so forth.

    One way to design a substitution alphabet for a cipher is to use a keyword to construct the alphabet. For example, suppose the keyword is “zebra.” You place the keyword at the beginning of the alphabet, and then fill out the other 21 slots with remaining letters, giving the following alphabet:

    Cipher alphabet:   zebracdfghijklmnopqstuvwxy
    Plain alphabet:    abcdefghijklmnopqrstuvwxyz

    Design and implement an Alphabet class for constructing these kinds of substitution alphabets. It should have a single public method that takes a keyword String as an argument and returns an alphabet string. Note that an alphabet cannot contain duplicate letters, so repeated letters in a keyword like “xylophone” would have to be removed.

    Design and write a Cipher subclass for a substitution cipher that uses an alphabet from the Alphabet class created in the previous exercise.

    Challenge: Find a partner and concoct your own encryption scheme. Then work separately with one partner writing encode() and the other writing decode(). Test to see that a message can be encoded and then decoded to yield the original message.

    Design a TwoPlayerGame subclass called MultiplicationGame. The rules of this game are that the game generates a random multiplication problem using numbers between 1 and 10, and the players, taking turns, try to provide the answer to the problem. The game ends when a wrong answer is given. The winner is the player who did not give a wrong answer.

    Design a class called MultiplicationPlayer that plays the multiplication game described in the previous exercise. This class should implement the IPlayer interface.

    Design a TwoPlayerGame subclass called RockPaperScissors. The rules of this game are that each player, at the same time, picks either a rock, a paper, or a scissors. For each round, the rock beats the scissors, the scissors beats the paper, and the paper beats the rock. Ties are allowed. The game is won in a best out of three fashion when one of the players wins two rounds.

    Design a class called RockPaperScissorsPlayer that plays the the game described in the previous exercise. This class should implement the IPlayer interface.


    This page titled 8.1: Section 1- is shared under a CC BY 4.0 license and was authored, remixed, and/or curated by Ralph Morelli & Ralph Wade via source content that was edited to the style and standards of the LibreTexts platform; a detailed edit history is available upon request.

    • Was this article helpful?