Inheritance Review

Today's class starts with a self-paced review of Inheritance. Attempt to answer all of the fill-in-the-blanks below, then download the set of files and review your answers. Make sure that you understand why each of the lines works or does not.

Parent.java
public class Parent {
  public String getName() {
    return "John Paul";
  }
  public String getLast() {
    return "Jones";
  }
}
Child.java
public class Child extends Parent {
  public String myName = "The Crazy One";
  public String getName() {
    return this.myName;
  }
}
Brother.java
public class Brother extends Parent {
  public String getName() {
    return "Paul";
  }
  public String getDegree() {
    return "Masters";
  }
}
Sister.java
public class Sister extends Parent {
  public String getName() {
    return "Jane";
  }
  public String getLast() {
    return "King";
  }
}
Neice.java
public class Neice extends Sister {
  public String getName() {
    return "Samantha";
  }
}

Class Hierarchy
                                Parent
                             /    |    \
                        Child  Brother  Sister
                                          \
                                           \
                                          Neice 

Example.java
public class Example {
    public static void main(String[] args) {
      Parent P = new Parent();
      Child C = new Child();
      Sister S = new Sister();
      Brother B = new Brother();
      Neice N = new Neice();

      // Does each of the following work?
      B = S; ______
      P = C; ______
      P.getName(); ________

      C = P; ______
      P = N; ______

      String t1 = P.getLast();   ______
      P = B;                     ______

      String t2 = P.getDegree(); ______
      String t3 = B.getDegree(); ______

      ArrayList<Parent> AL = new ArrayList<Parent>();
      AL.add(new Parent());
      AL.add(new Brother());
      AL.add(new Sister());
      AL.add(new Neice());

      String t4 = AL.get(1).getDegree(); ______
      Child B2 = (Child)P; ______
    }
  }


Data Hiding one More Time!

Let's go back to our earlier lessons where the moral of the story was that we wanted the code operating on the fields of an object a method of that object's class. That idea meant that, when designing a class, we put the focus not on what data should be in the class, but what methods it should have. This is a lesson we all need to learn over an over again. We want to provide yet another example of that idea, but this time in the context of GUI programming.

Disobeying the law

The code below produces the world's simplest (and perhaps worst) GUI program: a primitive conversion program, as pictured here:

The version of the program given directly below violates the principle of information hiding. All the fields are public!

Convert.java
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;

public class Convert extends JFrame {
  public JTextField inval;
  public JTextField inunit;
  public JTextField outunit;
  public JTextField outval;

  public Convert() {
    inval   = new JTextField("0.0", 10);
    inunit  = new JTextField("feet", 10);
    outunit = new JTextField("feet", 10);
    outval  = new JTextField("0.0", 10);
    outval.setEditable(false);

    ConvListener cl = new ConvListener(this);
    inval.addActionListener(cl);
    inunit.addActionListener(cl);
    outunit.addActionListener(cl);

    JPanel p = new JPanel(new FlowLayout());
    p.add(inval);
    p.add(inunit);
    p.add(outunit);
    p.add(outval);
    add(p);
    pack();
  }
}
ConvListener.java
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;

public class ConvListener implements ActionListener {
  private Convert conv;
  public ConvListener(Convert c) {
    conv = c;
  }

  public void actionPerformed(ActionEvent e) {
    double xin = Double.parseDouble(conv.inval.getText());
    double yin = xin;

    if( conv.inunit.getText().equals("inches") )
      yin = xin / 12.0;
    else if( conv.inunit.getText().equals("yards") )
      yin = xin * 3.0;

    double zout = yin;

    if( conv.outunit.getText().equals("inches") )
      zout = yin * 12.0;
    else if( conv.outunit.getText().equals("yards") )
      zout = yin / 3.0;

    conv.outval.setText(String.format("%.2f", zout));
  }
}
ConvWin.java
import javax.swing.*;

public class ConvWin {
  public static void main(String[] args) {
    JFrame f = new Convert();

    f.setVisible(true);
  }
}

Obeying the letter, but not the spirit

For many students, the way to rewrite the above code in the proper Object Oriented way is clear: make the fields private, but add getters and setters for each field, as shown below. Perhaps this follows the letter of the law, but definitely not the spirit. All you've really accomplished is to make a "struct" with a lot of extra code around it. In fact, other than extra code, the structure of the program hasn't changed at all. You may have done this earlier in the semester and felt like you were being silly, and that's because it kind of is.

Convert.java
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;

public class Convert extends JFrame {
  private JTextField inval;
  private JTextField inunit;
  private JTextField outunit;
  private JTextField outval;

  public Convert() {
    inval   = new JTextField("0.0", 10);
    inunit  = new JTextField("feet", 10);
    outunit = new JTextField("feet", 10);
    outval  = new JTextField("0.0", 10);
    outval.setEditable(false);

    ConvListener cl = new ConvListener(this);
    inval.addActionListener(cl);
    inunit.addActionListener(cl);
    outunit.addActionListener(cl);

    JPanel p = new JPanel(new FlowLayout());
    p.add(inval);
    p.add(inunit);
    p.add(outunit);
    p.add(outval);
    add(p);
    pack();
  }

  public JTextField getInval() {
    return inval;
  }

  public JTextField getInunit() {
    return inunit;
  }

  public JTextField getOutunit() {
    return outunit;
  }

  public void setOutval(String val) {
    outval.setText(val);
  }
}
ConvListener.java
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;

public class ConvListener implements ActionListener {
  private Convert conv;
  public ConvListener(Convert c) {
    conv = c;
  }

  public void actionPerformed(ActionEvent e) {
    double xin = Double.parseDouble(conv.getInval().getText());
    double yin = xin;

    if( conv.getInunit().getText().equals("inches") )
      yin = xin / 12.0;
    else if( conv.getInunit().getText().equals("yards") )
      yin = xin * 3.0;

    double zout = yin;

    if( conv.getOutunit().getText().equals("inches") )
      zout = yin * 12.0;
    else if( conv.getOutunit().getText().equals("yards") )
      zout = yin / 3.0;

    conv.setOutval(String.format("%.2f", zout));
  }
}
ConvWin.java
import javax.swing.*;

public class ConvWin {
  public static void main(String[] args) {
    JFrame f = new Convert();
    f.setVisible(true);
  }
}

Obeying the spirit ... and achieving enlightenment

Why is the above getter/setter solution not really following the spirit of OOP? Because the getter/setter operations are not natural operations for Convert objects. They're just workarounds, so that you can't be accused of having public fields and violating information hiding. Encapsulation still hasn't been addressed satisfactorily, though. The real point is that the code for updating the Convert panel shouldn't be in ConvListener. It's an operation that manipulates a Convert panel, so it should be a part of Convert.

The solution below gets rid of the getters and setters and adds an "update" method to the Convert class, which tells the convert panel to update itself. That's a proper object oriented solution to this problem.

Convert.java
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;

public class Convert extends JFrame {
  private JTextField inval;
  private JTextField inunit;
  private JTextField outunit;
  private JTextField outval;

  public Convert() {
    inval   = new JTextField("0.0", 10);
    inunit  = new JTextField("feet", 10);
    outunit = new JTextField("feet", 10);
    outval  = new JTextField("0.0", 10);
    outval.setEditable(false);

    ConvListener cl = new ConvListener(this);
    inval.addActionListener(cl);
    inunit.addActionListener(cl);
    outunit.addActionListener(cl);

    JPanel p = new JPanel(new FlowLayout());
    p.add(inval);
    p.add(inunit);
    p.add(outunit);
    p.add(outval);
    add(p);
    pack();
  }

  public void update() {
    double xin = Double.parseDouble(inval.getText());
    double yin = xin;

    if( inunit.getText().equals("inches") )
      yin = xin / 12.0;
    else if( inunit.getText().equals("yards") )
      yin = xin * 3.0;

    double zout = yin;

    if( outunit.getText().equals("inches") )
      zout = yin * 12.0;
    else if( outunit.getText().equals("yards") )
      zout = yin / 3.0;

    outval.setText(String.format("%.2f", zout));
  }
}
ConvListener.java
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;

public class ConvListener implements ActionListener {
  private Convert conv;
  public ConvListener(Convert c) {
    conv = c;
  }

  public void actionPerformed(ActionEvent e) {
    conv.update();
  }
}
ConvWin.java
import javax.swing.*;

public class ConvWin {
  public static void main(String[] args) {
    JFrame f = new Convert();
    f.setVisible(true);
  }
}

The moral of the story: you need to consider all aspects of OOP design jointly, and not "solve" each one individually. This particular example had an issue with data hiding, but its solution also involved solving an issue with encapsulation at the same time. What you're left with is a very nice Convert class that can be used as-is in a variety of programs, not just this one example!