株式会社エス・スリー・フォー

[Java入門]3.7 割引してくれるガソリンスタンド

最近のガソリンスタンドには、いろいろなサービスがあって驚かされます。
箱ティッシュをくれるという程度なら「なるほど、車にあると便利だな」とまだ納得できますが、食パンを一斤とか、サラダ油を一本とか、膝掛けを一枚進呈なんてことになると、一体どうしてガソリンスタンドが? とびっくりしてしまいます。

そこまでいかなくても、ある量以上給油すると割り引きしてくれるというようなサービスは、ガソリンスタンドに限らずよくある話です。

3.7.1 サブクラスと継承

すべてのガソリンスタンドが割り引きしてくれるわけではありませんから、それは特殊なガソリンスタンドということになります。それでも、割り引きしてくれるガソリンスタンドは結構たくさんあるわけですから、これを割引してくれるガソリンスタンドクラスにまとめようと思うのはオブジェクト指向の考え方でいけばごく自然なことです。

でも、ちょっと待ってください。割引してくれるガソリンスタンドと言えども、基本的にはガソリンスタンドそのものですよね。そうすると、もしこれをガソリンスタンドのクラスと無関係に作ってしまうと、同じようなことをもう一度するはめになってしまいます。これから作ろうとしているクラスは、ガソリンスタンドに「割り引きしてくれる」という特徴が加わっているだけなのですから、ガソリンスタンドクラスの一部として扱っていいはずです。

そこで、あるクラスを基本にして、特殊なクラスを作るときには、基本のクラスとの違いだけを記述できるようになっています。そうすれば、同じことを2回も3回も記述しなくてもすみます。これを実現してくれるのがサブクラス化です。

例えばここで、割引するガソリンスタンドをDiscountGasStationクラスとします。このクラスはGasStationクラスの定義を引き継いでいると同時に、少しだけ違っています。これをDiscountGasStationクラスはGasStationクラスのサブクラスであるといいます。これに対してGasStationクラスのことを、DiscountGasStationクラスのスーパークラスと呼びます。

また、サブクラスがスーパークラスの定義を引き継ぐことを、継承といいます。

3.7.2 DiscountGasStation クラス

それでは、割引するガソリンスタンドDiscountGasStationクラスを定義してみましょう。まず、何リットル以上給油したら割引するのかを決めておく必要があります。これを表す変数をborder_としましょう。割り引き金額はいくらかという情報も必要ですが、これは変数にしないで、常に一定にしておくことにしましょう。また、割引計算をするようにメソッドを変更する必要があります。

では、このサブクラスをどのように定義するのか、実際にソースコードを見てみましょう。

このソースは、$chapter3/ex6/DiscountGasStation.javaにあります。

Java: DiscountGAsStation.java
class DiscountGasStation extends GasStation {

  private int border_;

  public DiscountGasStation(int initial) {
    this(initial, 20);
  }

  public DiscountGasStation(int initial, int bd) {
    super(initial);
    border_ = bd;
  }

  public int price(int amount) {
    int discount = 0;
    if ( amount > border_ ) {
      discount = 5;
    }
    return amount * (unitPrice() - discount);
  }

}

たったこれだけでDiscountGasStationクラスを定義できるのは、GasStationクラスのサブクラスとして定義しているからです。何度も言うように、スーパークラスで一度定義した内容は、サブクラスで再び定義する必要はありません。

次に、クラス定義の最初の行に注目してください。

class DiscountGasStation extends GasStation

この記述が、DiscountGasStationクラスがGasStationクラスのサブクラスだと言うことことを示しています。このように、サブクラスの名前の後にextendsに続けてそのスーパークラスの名前を書くわけです。

C++: GasStation.h (修正)
#ifndef GASSTATION_H__
#define GASSTATION_H__

class Car;

class GasStation {

private:
  static const int tankCapacity_;
  static int unitPrice_;
  int remain_;
  int totalSales_;

public:
  explicit GasStation(int initial);
  virtual ~GasStation(); // 追加
  int refuel(Car* aCar);
  virtual int price(int amount); // 'virtual' 追加
  bool isNotEmpty();
  void addSales(int price);
  static int tankCapacity();
  int totalSales();
  static void setUnitPrice(int price);
  static int getUnitPrice();

};

#endif
C++: GasStation.cpp (修正)
// 追加
GasStation::~GasStation() {
}
C++: DiscountGasStation.h
#ifndef DICSOUNTGASSTATION_H__
#define DICSOUNTGASSTATION_H__

#include "GasStation.h"

class DiscountGasStation : public GasStation {

private:
  int border_;

public:
  DiscountGasStation(int initial, int bd =20);
  virtual int price(int amount);

};

#endif
C++: DiscountGasStation.cpp
#include "DiscountGasStation.h"

DiscountGasStation::DiscountGasStation(int initial, int bd)
                        : GasStation(initial), border_(bd) {
}

int DiscountGasStation::price(int amount) {
  int discount = 0;
  if ( amount > border_ ) {
    discount = 5;
  }
  return amount * (getUnitPrice() - discount);
}
C#: GasStation.cs (修正)
  // 'virtual' 追加
  public virtual int price(int amount) {
    return amount * unitPrice_;
  }
C#: DiscountGasStation.cs
class DiscountGasStation : GasStation {

  private int border_;

  public DiscountGasStation(int initial) : this(initial, 20) {
  }

  public DiscountGasStation(int initial, int bd) : base(initial) {
    border_ = bd;
  }

  public override int price(int amount) {
    int discount = 0;
    if ( amount > border_ ) {
      discount = 5;
    }
    return amount * (unitPrice - discount);
  }

}
VB: GasStation.vb (修正)
  ' 'Overridable' 追加
  Public Overridable Function price(ByVal amount As Integer) As Integer
    Return amount * unitPrice_
  End Function
VB: DiscountGasStation.vb
Class DiscountGasStation
  Inherits GasStation

  Private border_ As Integer

  Public Sub New(ByVal initial As Integer)
    Me.New(initial, 20)
  End Sub

  Public Sub New(ByVal initial As Integer, ByVal bd As Integer)
    MyBase.New(initial)
    border_ = bd
  End Sub

  Public Overrides Function price(ByVal amount As Integer) As Integer
    Dim discount As Integer = 0
    If amount > border_ Then
      discount = 5
    End If
    Return amount * (unitPrice * discount)
  End Function

End Class

3.7.3 複数のコンストラクタ

ところで、DiscountGasStationクラスにはコンストラクタが2つ定義されています。一方は引数をひとつだけ必要とし、もう一方は2つの引数を必要としています。引数がひとつだけの方のコンストラクタを見ると、中身は this(initial, 20); だけです。これは何をしているのかというと、同じDiscountGasStationクラスのもう一方のコンストラクタを呼び出しているのです。この this(引数,...) という書き方はコンストラクタの中でしか使えません。

では、もうひとつのコンストラクタの方はどうなっているでしょう。こちらは super(initial); とした後に変数border_に2番目の引数の値を入れています。この super(initial); というのも先ほどの this(initial,20); と同じように、別のコンストラクタを呼び出します。ただし、今度は同じクラスのものではなく、スーパークラスのコンストラクタ、つまりGasStationクラスのコンストラクタを呼び出しているのです。

どうしてこんなことをしているのでしょう。割り引きをするガソリンスタンドといっても、建物の構造上は基本的に普通のガソリンスタンドと同じです。そこで、まず普通のガソリンスタンドとして建ててしまって、何リットル以上お買い上げの方からいくら割り引くかといったことは、その後で考えればいいわけです。ガソリンスタンドを建てる前から、割引制度について決めても仕方がありません。そのために、まずスーパークラスのコンストラクタを呼び出し、その後で、サブクラスで付け加えられた部分について設定します。

これでDiscountGasStationクラスの定義はおしまいです。それでは、このクラスを実際に使ってみましょう。

3.7.4 GasStationSimulation クラスへの修正

DiscountGasStationクラスを実際に使ってみるために、GasStationSimulationクラスを少し変更します。まず、普通のガソリンスタンドと割り引きするガソリンスタンドのどちらを利用するかを選択できるようにします。このとき、askIntメソッドを使って番号で選択するようにすれば、変更も少なくて済みます。

さらに、これまでは5台しか給油を繰り返しませんでしたが、もっと多くの車に給油できた方がいいでしょう。whileでは永遠に繰り返すことにして、ガソリンスタンドを選択するときに、シミュレーションを終了するかどうかも選択できるようにします。

また、今回はガソリンスタンドを作るところから繰り返す必要があるので、dealCustomerメソッドの中で繰り返すわけにはいきません。そこで、新しくsimulateメソッドを作り、繰り返しインスタンスを生成しながら、dealCustomerメソッドを呼び出すようにしましょう。これはinitメソッドの役割も果たしています。

mainメソッドからはinitメソッドとdealCustomerメソッドを呼び出すかわりに、このsimulateメソッドだけを呼び出すようにします。simulateメソッドとmainメソッドは、それぞれ次のようになります。

public void simulate() {
  System.out.println("For all gas stations:");
  int initial = askInt("  initial amount? ");
  int price = askInt("  unit price? ");
  GasStation.setUnitPrice (price);
loop:
  while ( true ) {
    System.out.println("\nSelect command:");
    int type = askInt("  1) Normal  2) Discount  other) exit ");
    switch ( type ) {
    case 1: // 普通のガソリンスタンド
      aGS_ = new GasStation(initial);
      break;
    case 2: // 割引するガソリンスタンド
      int border = askInt("boederline amount? ");
      aGS_ = new DiscountGasStation(initial, border);
      break;
    default:
      break loop;
    }
    dealCustomer();
  }
}

public static void main(String[] args) {
  GasStationSimulation simulation = new GasStationSimulation();
  simulation.simulate();
}

whileの条件をtrueとしてあるので、何時までも繰り返します。両スタンドのガソリンの初期量と単価は、繰り返しに入る前にあらかじめ入力しています。そして繰り返しの中で、普通のガソリンスタンドにするか、割り引きするガソリンスタンドにするか、あるいは繰り返しを抜けてシミュレーションを終了するかを尋ねています。

switch とラベル

これらの処理を切替えているのは、その後のswitchです。これを使うと、括弧の中に与えた値に応じた、caseから後ろの部分を実行します。そして、breakを見つけると中括弧を抜け出すことになっています。中括弧の中には、defaultという文もあります。括弧の中に、与えた値に対応するcaseがなかったときには、ここから先を実行します。

ところで、このswitchではbreak loop;するようになっていますが、このloopとは何でしょうか。ソースリストをよく見ると、whileの前にloop:という行があります。これはラベルといって、特定のwhileforなどに名前を付けるものです。ラベルを指定しないbreakでは、switchの中括弧を抜け出すだけで、whileを抜け出すことはできません。しかし、この例のようにwhileにラベルを付けておくと、breakと使って簡単にそこから抜け出すことができるのです。

結局この例では、1を入力したときには普通のガソリンスタンド、2を入力したときには割り引きするガソリンスタンド、それ以外を入力したときにはシミュレーションを終了、というようになっているわけです。

■ C++

void GasStationSimulation::simulate() {
  std::cout << "For all gas stations:" << std::endl;
  int initial = askInt("  initial amount? ");
  int unitPrice = askInt("  unit price? ");
  GasStation::setUnitPrice(unitPrice);
  bool keepGoing = true;
  while ( keepGoing ) {
    std::cout << "\nSelect command:" << std::endl;
    int type = askInt("  1) Normal  2) Discount  other) exit ");
    switch ( type ) {
    case 1:
      aGS_ = new GasStation(initial);
      break;
    case 2: {
        int border = askInt("borderline amount? ");
        aGS_ = new DiscountGasStation(initial, border);
      }
      break;
    default:
      keepGoing = false;
    }
    if ( keepGoing ) {
      dealCustomer();
      delete aGS_;
      aGS_ = 0;
    }
  }
}

int main() {
  GasStationSimulation* simulation = new GasStationSimulation();
  simulation->simulate();
  delete simulation;
  return 0;
}

■ C#

public void simulate() {
  System.Console.WriteLine("For all gas stations:");
  int initial = askInt("  initial amount? ");
  GasStation.unitPrice = askInt("  unit price? ");
  bool keepGoing = true;
  while ( keepGoing ) {
    System.Console.WriteLine("\nSelect command:");
    int type = askInt("  1) Normal  2) Discount  other) exit ");
    switch ( type ) {
    case 1:
      aGS_ = new GasStation(initial);
      break;
    case 2:
      int border = askInt("borderline amount? ");
      aGS_ = new DiscountGasStation(initial, border);
      break;
    default:
      keepGoing = false;
      break;
    }
    if ( keepGoing ) {
      dealCustomer();
    }
  }
}

[System.STAThread]
static void Main(string[] args) {
  GasStationSimulation simulation = new GasStationSimulation();
  simulation.simulate();
}

■ VB

Public Sub simulate()
  System.Console.WriteLine("For all gas stations:")
  Dim initial As Integer = askInt("  initial amount? ")
  GasStation.unitPrice = askInt("  unit price? ")
  Dim keepGoing As Boolean = True
  While keepGoing
    System.Console.WriteLine(vbCrLf + "Select command:")
    Dim type As Integer = askInt("  1) Normal  2) discount  other) exit ")
    Select Case type
    Case 1
      aGS_ = New GasStation(initial)
    Case 2
      Dim border As Integer = askInt("borderline amount? ")
      aGS_ = New DiscountGasStation(initial, border)
    Case Else
      keepGoing = False
    End Select
    If keepGoing Then
      dealCustomer()
    End If
  End While
End Sub

Public Shared Sub Main()
  Dim simulation As GasStationSimulation = New GasStationSimulation()
  simulation.simulate()
End Sub

3.7.5 実行結果

割引をするガソリンスタンドの実行結果は、次のようになります。

  For all gas stations:
    initial amount? 250
    unit price? 108

  Select command:
    1) Normal 2) Discount other)exit 1
  Car no. 0       1       2       3       4
  amount: 23      34      20      31      7
  price:  2484    3672    2160    3348    756
  total: 115 [liter] / 12420 [yen]

  Select command:
    1) Normal 2) Discount other)exit 2
  borderline amount? 20
  Car no. 0       1       2       3       4
  amount: 44      28      47      27      42
  price:  4532    2884    4841    2781    4326
  total: 188 [liter] / 19364 [yen]

  Select command:
    1) Normal 2) Discount other)exit 3

prev next