第九章

介面和多型


9.1 開發可重複使用的解決方案

第六章的 DataSet

    DataSet類別用來計算一組數值的平均和最大值。

BankAccount 的 DataSet

    如果要在一組銀行帳戶中,找出最大的存款金額最大的, 則類別 DataSet 可修改為
public class DataSet // modified for BankAccount objects
{
   . . .
   public void add(BankAccount x)
   {
      sum = sum + x.getBalance();
      if (count == 0
            || maximum.getBalance() < x.getBalance())
         maximum = x;
      count++;
   }

   public BankAccount getMaximum()
   {
      return maximum;
   }

   private double sum;
   private BankAccount maximum;
   private int count;
}

Coin 的 DataSet

    同樣要計算一把硬幣中找出最大的硬幣值, 則類別 DataSet 修改為
public class DataSet // modified for Coin objects
{
   . . .
   public void add(Coin x)
   {
      sum = sum + x.getValue();
      if (count == 0
            || maximum.getValue() < x.getValue())
         maximum = x;
      count++;
   }

   public Coin getMaximum()
   {
      return maximum;
   }

   private double sum;
   private Coin maximum;
   private int count;
}

Measurable 介面

介面與類別

Measurable 物件的通用 DataSet

public class DataSet // modified for Coin objects
{
   . . .
   public void add(Measurable x)
   {
      sum = sum + x.getMeasure();
      if (count == 0
            || maximum.getMeasure() < x.getMeasure())
         maximum = x;
      count++;
   }

   public Measurable getMaximum()
   {
      return maximum;
   }

   private double sum;
   private Measurable maximum;
   private int count;
}

實現介面

修改 BankAccountCoin 類別成為 Measurable

class BankAccount implements Measurable
{
   public double getMeasure()
   {
      return balance;
   }
   additional methods and fields
}
class Coin implements Measurable
{
   public double getMeasure()
   {
      return value;
   }
   additional methods and fields
}

File DataSetTest.java

/**
   This program tests the DataSet class.
*/
public class DataSetTest
{
   public static void main(String[] args)
   {

      DataSet bankData = new DataSet();

      bankData.add(new BankAccount(0));
      bankData.add(new BankAccount(10000));
      bankData.add(new BankAccount(2000));

      System.out.println("Average balance = "
         + bankData.getAverage());
      Measurable max = bankData.getMaximum();
      System.out.println("Highest balance = "
         + max.getMeasure());

      DataSet coinData = new DataSet();

      coinData.add(new Coin(0.25, "quarter"));
      coinData.add(new Coin(0.1, "dime"));
      coinData.add(new Coin(0.05, "nickel"));

      System.out.println("Average coin value = "
         + coinData.getAverage());
      max = coinData.getMaximum();
      System.out.println("Highest coin value = "
         + max.getMeasure());
   }
}


DataSet 和相關類別的 UML 圖


Syntax 9.1: 介面的設計


 
public interface InterfaceName
 {
    method signatures
 }
例題:
 
public interface Measurable
 {
    double getMeasure();
 }
用途:

To define an interface and its method signatures. The methods are automatically public.

Syntax 9.2: 介面的實現

 
 public class ClassName
   implements InterfaceName, InterfaceName, ...
 {
     methods
     instance variables
 }
例題:
 
 public class BankAccount implements Measurable
 {
    // other BankAccount methods
    public double getMeasure()
    {
    // method implementation
    }
 }
 
用途:

To define a new class that implements the methods of an interface

9.2 型式的轉換

Casts

instanceof 運算子

Syntax 9.3: instanceof 運算子

 object instanceof ClassName

例題:

 
if (x instanceof Coin)
  {
    Coin c = (Coin)x;
  }

用途:

To return true if the object is an instance of ClassName (or one of its subclasses), false otherwise

9.3 多型

9.4 使用策略介面以改進可重複使用性

使用策略介面

Measurer 介面和相關類別的 UML 圖


內部類別

public static void main(String[] args)
{
   class RectangleMeasurer implements Measurer
   {
      . . .
   }
   Measurer m = new RectangleMeasurer();
   . . .
   // RectangleMeasurer class not used beyond this point
}

File DataSet.java

/**
   Computes the average of a set of data values.
*/
public class DataSet
{
   /**
      Constructs an empty data set with a given measurer
      @param aMeasurer the measurer that is used to measure data values
   */
   public DataSet(Measurer aMeasurer)
   {
      sum = 0;
      count = 0;
      maximum = null;
      measurer = aMeasurer;
   }

   /**
      Adds a data value to the data set
      @param x a data value
   */
   public void add(Object x)
   {
      sum = sum + measurer.measure(x);
      if (count == 0
         || measurer.measure(maximum) < measurer.measure(x))
         maximum = x;
      count++;
   }

   /**
      Gets the average of the added data.
      @return the average or 0 if no data has been added
   */
   public double getAverage()
   {
      if (count == 0) return 0;
      else return sum / count;
   }

   /**
      Gets the largest of the added data.
      @return the maximum or 0 if no data has been added
   */
   public Object getMaximum()
   {
      return maximum;
   }

   private double sum;
   private Object maximum;
   private int count;
   private Measurer measurer;
}

File DataSetTest.java

import java.awt.Rectangle;

/**
   This program demonstrates the use of a Measurer.
*/
public class DataSetTest
{
   public static void main(String[] args)
   {
      class RectangleMeasurer implements Measurer
      {
         public double measure(Object anObject)
         {
            Rectangle aRectangle = (Rectangle)anObject;
            double area
               = aRectangle.getWidth() * aRectangle.getHeight();
            return area;
         }
      }

      Measurer m = new RectangleMeasurer();

      DataSet data = new DataSet(m);

      data.add(new Rectangle(5, 10, 20, 30));
      data.add(new Rectangle(10, 20, 30, 40));
      data.add(new Rectangle(20, 30, 5, 10));

      System.out.println("Average area = " + data.getAverage());
      Rectangle max = (Rectangle)data.getMaximum();
      System.out.println("Maximum area = " + max);
   }
}

File Measurer.java

/**
   Describes any class whose objects can measure other objects.
*/
public interface Measurer
{
   /**
      Computes the measure of an object.
      @param anObject the object to be measured
      @return the measure
   */
   double measure(Object anObject);
}

Syntax 9.4: Inner Classes

 在方法中宣告
class OuterClassName
{
   method signature
   {
      . . .
      class InnerClassName
      {
         nethods
         fields

      }
      . . .
   }
   . . .
}

在類別中宣告
class OuterClassName
{
   nethods
   fields

   accessSpecifier class InnerClassName
   {
      nethods
      fields

   }
   . . .
}



例題:

 
public class Test
{
   public static void main(String[] args)
   {
      class RectangleMeasurer implements Measurer
      {
         . . .
      }
   }
}

用途:

To define an inner class whose methods have access to the same variables and methods as the outer class methods

9.5 處理 Timer 事件

例題: Countdown

  • 程式列印出:
    10
    9
    . . .
    2
    1
    0
    Liftoff!

  • 各次列印相隔一秒

File TimerTest.java

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JOptionPane;
import javax.swing.Timer;

/**
   This program tests the Timer class.
*/
public class TimerTest
{
   public static void main(String[] args)
   {
      class CountDown implements ActionListener
      {
         public CountDown(int initialCount)
         {
            count = initialCount;
         }

         public void actionPerformed(ActionEvent event)
         {
            if (count >= 0)
               System.out.println(count);
            if (count == 0)
               System.out.println("Liftoff!");
            count--;
         }

         private int count;
      }

      CountDown listener = new CountDown(10);

      final int DELAY = 1000; // milliseconds between timer ticks
      Timer t = new Timer(DELAY, listener);
      t.start();

      JOptionPane.showMessageDialog(null, "Quit?");
      System.exit(0);
   }
}


例題: Add Interest

  • 設計 BankAccount 物件
    public class TimerTest
    {
       public static void main(String[] args)
       {
          final BankAccount account = new BankAccount(1000);
    
          class InterestAdder implements ActionListener { . . . }
       }
       private static final double RATE = 5;
    }
    
  • account 需要修飾詞 final, 以便內部類別程式碼可以用得到它。

內部類別可以用得到在它定義所在的類別中的變數

class InterestAdder implements ActionListener
{
   public void actionPerformed(ActionEvent event)
   {
      double interest = account.getBalance() * RATE / 100;
      account.deposit(interest);
      System.out.println("Balance = " + account.getBalance());
   }
}
  • 內部類別可以用得到在它定義所在的類別中的個體變數(instance variable)和方法
  • 內部類別可以用得到在它定義所在的方法中所有的 final 變數

File TimerTest.java

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JOptionPane;
import javax.swing.Timer;

/**
   This program uses a timer to add interest to a bank
   account once per second.
*/
public class TimerTest
{
   public static void main(String[] args)
   {
      final BankAccount account = new BankAccount(1000);

      class InterestAdder implements ActionListener
      {
         public void actionPerformed(ActionEvent event)
         {
            double interest = account.getBalance() * RATE / 100;
            account.deposit(interest);
            System.out.println("Balance = " + account.getBalance());
         }
      }

      InterestAdder listener = new InterestAdder();

      final int DELAY = 1000; // milliseconds between timer ticks
      Timer t = new Timer(DELAY, listener);
      t.start();

      JOptionPane.showMessageDialog(null, "Quit?");
      System.exit(0);
   }

   private static final double RATE = 5;
}


Timer 類別的 UML 圖