[Java入門]3.9 車とガソリンタンクの共通点
車に乗っていてガス欠になってしまったことはありませんか? バイクならスタンドまで手で押していってもいいのですが、さすがに車を押していくのはちょっと無理です。
こんなとき、何かガソリンを入れられるような缶などがあれば、それだけをもってガソリンスタンドを探しにいけますね。とりあえずそのスタンドにたどり着けるくらいのガソリンを買ってくればいいのですから。
そうすると、車だけでなく、缶やポリタンク等もガソリンスタンドを利用できるということになります。前節で定義したガソリンタンクのクラスを思い出してみてください。そもそもこのタンククラスは、車クラスからガソリンを入れるという機能を取り出したものですから、当然のことながらガソリンを入れることができます。
同じ様に、缶クラスやポリタンククラスを定義してみると、すべてガソリンを入れるという同じ機能を持っていることになるでしょう。
ここで、実際にガソリンを入れるガソリンスタンドの立場になってみると、形態は違ってもみんな同じお客さんですから、同じ様な手順で扱いたいですね。車クラスとタンククラスではこの手順は共通しており、ガソリンタンクの残量を確認するときにはisNotFullメソッドを使い、給油するときにはrefuelメソッドを使っています。
これは、車の持つ機能の一部を取り出してタンククラスを作ったのですから、当然のことかもしれません。しかし、新たに缶クラスやポリタンククラスを作るときにも同じ機能を同じメソッドで同じように扱えるようにするべきでしょう。そうしなければ、ガソリンスタンドは、違う容器が来る度に、給油の方法を変えなければならなくなります。同じ「ガソリンを入れられるもの」なのですから、同じメソッドでガソリンを入れることができた方が何かと便利です。
このためには、ガソリンを入れるために必要なメソッドを、ひとかたまりの決まった枠組みとして定義しておけばよいでしょう、そして、「ガソリンを入れられるもの」のクラスは必ずその枠組みに従ってメソッドを定義するようにすればよいのです。
3.9.1 ガソリンを入れることができるインターフェース
Javaには、まさにこれを実現するための仕組みが用意されています。
それは、インターフェースというものです。つまり、インターフェースはある機能に必要なメソッドの枠組で、なおかつその機能を実現するのに十分なメソッドから構成されています。
それでは実際に、「ガソリンを入れられる」という機能のインターフェースを定義してみます。まず、名前を付けましょう。ガソリンを入れられるのですから、Refuelableにします。このインターフェースに必要なメソッドは、タンククラスが持っているメソッドそのものです。インターフェースではメソッド名だけ決めていればいいので、変数については考えなくてかまいません。まとめると以下のようになります。
- Java: Refuelable.java
-
interface Refuelable { boolean isNotFull(); void refuel(int amount); }
このソースは、$chapter3/ex8/Refuelable.javaにあります。
メソッドの記述が、クラス定義のときとはちょっと違っています。メソッドの中身がなく、セミコロン(;) があるだけです。
これには理由があります。車クラスとタンククラスのクラス定義を思い出してください。車クラスのisNotFullメソッドと、タンククラスのisNotFullメソッドは、名前は同じでしたが、変数の構成や実際のメソッド中の手続きは異なっていました。ですから、実際の手続きを示す部分までインターフェースで記述することはできないのです。
インターフェースはメソッドの名前を決めるだけで、メソッドの具体的な動作には関知しません。つまりインターフェースという機構は、同じ機能を別なクラスで実現するための枠組みと考えることができるわけです。
■
- C++: Refuelable.h
-
#ifndef REFUELABLE_H__ #define REFUELABLE_H__ class Refuelable { public: virtual ~Refuelable(); virtual bool isNotFull() const =0; virtual void refuel(int amount) =0; }; #endif - C++: Refuelable.cpp
-
#include "Refuelable.h" Refuelable::~Refuelable() { }
- C#: Refuelable.cs
-
interface Refuelable { bool isNotFull(); void refuel(int amount); }
- VB: Refuelable.vb
-
Interface Refuelable Function isNotFull() As Boolean Sub refuel(ByVal amount As Integer) End Interface
□
3.9.2 Refuelable インターフェースの実装
インターフェースに対して、各クラスにおけるメソッドの具体的な動作のことを実装と言います。そして、あるクラスがあるインターフェースで決められた機能をすべて持っているとき、そのクラスはそのインターフェースを実装していると言います。
それでは、インターフェースを実装するクラスは、どのように記述すればよいのでしょうか。ガソリンスタンドの例で見てみましょう。CarクラスもTankクラスもメソッド自体は既に定義されているので、クラスの定義の一箇所だけ変更すればよいでしょう。クラス名の後ろにimplementsに続けて実装するインターフェース名を記述するだけです。この場合であれば、それぞれ次のように記述します。
class Car implements Refuelable {
: (省略)
}
class Tank implements Refuelable {
: (省略)
}
なお、それぞれの完全なソースは$chapter3/ex8/Car.javaと$chapter3/ex8/Tank.javaにあります。前の版と実際に比べて、本当にこれしか変更していないということを確認してください。
この場合はどちらのクラスもスーパークラスを決めていないのでextendsはありませんが、ある場合にはimplementsはスーパークラス名の後ろに書きます。
■ C++:
class Car : virtual public Refuelable {
: (省略)
};
class Tank : virtual public Refuelable {
: (省略)
};
□
■ C#:
class Car : Refuelable {
: (省略)
}
class Tank : Refuelable {
: (省略)
}
□
■ VB:
Class Car
Implements Refuelable
: (省略)
Public Function isNotFull() As Boolean Implements Refuelable.isNotFull
Return tank_.isNotFull()
End Function
Public Sub refuel(ByVal amount As Integer) Implements Refuelable.refuel
tank_.refuel(amount)
End Sub
: (省略)
End Class
Class Tank
Implements Refuelable
: (省略)
Public Function isNotFull() As Boolean Implements Refuelable.isNotFull
Return remain_ < capacity_
End Function
Public Sub refuel(ByVal amount As Integer) Implements Refuelable.refuel
remain_ = remain_ + amount
End Sub
End Class
□
3.9.3 GasStation クラスへの修正
これで車クラスもタンククラスもRefuelableインターフェースとして扱えるようになりましたので、GasStationの方もRefuelableインターフェースを使うようにしましょう。
refuelメソッドでCarクラスを使っていた部分を、すべてRefuelableインターフェースを使うように書き換えます。
public int refuel(Refuelable aRefuelable) {
int charged = 0;
while ( aRefuelable.isNotFull() ) {
aRefuelable.refuel(1);
charged = charged + 1;
}
return charged;
}
■ C++
int GasStation::refuel(Refuelable* aRefuelable) {
int charged = 0;
while ( aRefuelable->isNotFull() ) {
aRefuelable->refuel(1);
charged = charged + 1;
}
return charged;
}
□
■ C#
public int refuel(Refuelable aRefuelable) {
int charged = 0;
while ( aRefuelable.isNotFull() ) {
aRefuelable.refuel(1);
charged = charged + 1;
}
return charged;
}
□
■ VB
Public Function refuel(ByRef aRefuelable As Refuelable) As Integer
Dim charged As Integer = 0
While aRefuelable.isNotFull()
aRefuelable.refuel(1)
charged = charged + 1
End While
Return charged
End Function
□
3.9.4 GasStationSimulation クラスへの修正
GasStationSimulationクラスはどうでしょうか。Carクラスを使っていたところを全部Refuelableに直すというわけにはいきません。インターフェースは実装を規定していないため、変数や他のメソッドの構成などがわからないので、オブジェクトを作ることはできないのです。
それでは困るので、お客さんごとに乱数を使って車かタンクのどちらかを自動的に選ぶようにしましょう。現実ならば車の方が圧倒的に多いのでしょうけど、ここでは簡単にほぼ半々だということにしてしまいましょう。乱数の値が負か、0以上かで分けます。タンクについては常に空だということにします。
以上のことを考慮すると、GasStationSimulationクラスは次のようになります。
- Java: GasStationSimulation.java
-
import java.io.BufferedReader; import java.io.InputStreamReader; import java.util.Random; class GasStationSimulation { private GasStation aGS_; private int askInt(String message) { try { System.out.print(message); System.out.flush(); String line = new BufferedReader(new InputStreamReader(System.in)).readLine(); return Integer.parseInt(line); } catch ( java.io.IOException e ) { System.out.println("could not get input. Sorry."); return 0; } } private void dealCustomer() { Random aRandom = new Random(); int charged[] = new int[5]; boolean isCar[] = new boolean[5]; int totalAmount = 0; Refuelable aRefuelable; for ( int i = 0; aGS_.isNotEmpty() && i < charged.length; i = i + 1 ) { if ( aRandom.nextInt() < 0 ) { int initial = aRandom.nextInt() % Car.tankCapacity(); if ( initial < 0 ) { initial = -initial; } aRefuelable = new Car(initial); isCar[i] = true; } else { int capa = aRandom.nextInt() % Car.tankCapacity(); if ( capa < 0 ) { capa = -capa; } aRefuelable = new Tank(capa, 0); isCar[i] = false; } charged[i] = aGS_.refuel(aRefuelable); totalAmount = totalAmount + charged[i]; } System.out.print("No."); for ( int i = 0; i < charged.length; i++ ) { if ( isCar[i] ) { System.out.print("\t" + i + "(Car)"); } else { System.out.print("\t" + i + "(Tank)"); } } System.out.print("\namount:"); for ( int i = 0; i < charged.length; i++ ) { System.out.print("\t" + charged[i]); } System.out.print("\nprice:"); for ( int i = 0; i < charged.length; i++ ) { int price = aGS_.price(charged[i]); aGS_.addSales(price); System.out.print("\t" + price); } System.out.println("\ntotal: " + totalAmount + " [liter] / " + aGS_.totalSales() + " [yen]"); } public void simulate() { System.out.println("For all gas stations:"); int initial = askInt(" initial amount? "); int unitPrice = askInt(" unit price? "); GasStation.setUnitPrice(unitPrice); 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("borderline amount? "); aGS_ = new DiscountGasStation(initial, border); break; default: break loop; } dealCustomer(); } } public static void main(String[] args) { GasStationSimulation simulation = new GasStationSimulation(); simulation.simulate(); } }
■
- C++: GasStationSimulation.h
-
#ifndef GASSTATIONSIMULATION_H__ #define GASSTATIONSIMULATION_H__ #include <string> #include "GasStation.h" class GasStationSimulation { private: GasStation* aGS_; int askInt(const std::string& message); void dealCustomer(); public: void simulate(); }; #endif - C++: GasStationSimulation.cpp
-
#include <iostream> #include <cstdlib> // rand #include <vector> // vector #include "GasStationSimulation.h" #include "Car.h" #include "Tank.h" #include "GasStation.h" #include "DiscountGasStation.h" int GasStationSimulation::askInt(const std::string& message) { std::cout << message << std::flush; int result; if ( std::cin >> result ) { return result; } std::cout << "could not get input. sorry." << std::endl; return 0; } void GasStationSimulation::dealCustomer() { std::vector<int> charged(5); typedef std::vector<int>::size_type size_type; bool isCar[5]; int totalAmount = 0; Refuelable* aRefuelable; for ( size_type i = 0; aGS_->isNotEmpty() && i < charged.size(); ++i ) { if ( std::rand() % 2 == 0 ) { int initial = std::rand() % Car::tankCapacity(); aRefuelable = new Car(initial); isCar[i] = true; } else { int capa = std::rand() % Car::tankCapacity(); aRefuelable = new Tank(capa, 0); isCar[i] = false; } charged[i] = aGS_->refuel(aRefuelable); totalAmount = totalAmount + charged[i]; delete aRefuelable; } std::cout << "No."; for ( size_type i = 0; i < charged.size(); ++i ) { if ( isCar[i] ) { std::cout << '\t' << static_cast<unsigned>(i) << "(Car)"; } else { std::cout << '\t' << static_cast<unsigned>(i) << "(Tank)"; } } std::cout << "\namount:"; for ( size_type i = 0; i < charged.size(); ++i ) { std::cout << '\t' << charged[i]; } std::cout << "\nprice:"; for ( size_type i = 0; i < charged.size(); ++i ) { int price = aGS_->price(charged[i]); aGS_->addSales(price); std::cout << '\t' << price; } std::cout << "\ntotal: " << totalAmount << " [liter] / " << aGS_->totalSales() << " [yen]" << std::endl; } 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; } } }
- C#: GasStationSimulation.cs
-
class GasStationSimulation { private GasStation aGS_; private int askInt(string message) { System.Console.Write(message); System.Console.Out.Flush(); try { return System.Int32.Parse(System.Console.ReadLine()); } catch ( System.Exception ) { System.Console.WriteLine("could not get input. sorry."); return 0; } } private void dealCustomer() { System.Random aRandom = new System.Random(); int[] charged = new int[5]; bool[] isCar = new bool[5]; int totalAmount = 0; Refuelable aRefuelable; for ( int i = 0; aGS_.isNotEmpty() && i < charged.Length; ++i ) { if ( aRandom.Next(2) == 0 ) { int initial = aRandom.Next(Car.tankCapacity); aRefuelable = new Car(initial); isCar[i] = true; } else { int capa = aRandom.Next(Car.tankCapacity); aRefuelable = new Tank(capa, 0); isCar[i] = false; } charged[i] = aGS_.refuel(aRefuelable); totalAmount = totalAmount + charged[i]; } System.Console.Write("No."); for ( int i = 0; i < charged.Length; ++i ) { if ( isCar[i] ) { System.Console.Write("\t" + i + "(Car)"); } else { System.Console.Write("\t" + i + "(Tank)"); } } System.Console.Write("\namount:"); for ( int i = 0; i < charged.Length; ++i ) { System.Console.Write("\t" + charged[i]); } System.Console.Write("\nprice:"); for ( int i = 0; i < charged.Length; ++i ) { int price = aGS_.price(charged[i]); aGS_.addSales(price); System.Console.Write("\t" + price); } System.Console.WriteLine("\ntotal: " + totalAmount + " [liter] / " + aGS_.totalSales + " [yen]"); } 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: GasStationSimulation.vb
-
Class GasStationSimulation Private aGS_ As GasStation Private Function askInt(ByVal message As String) System.Console.Write(message) System.Console.Out.Flush() Try Return System.Int32.Parse(System.Console.ReadLine()) Catch e As System.Exception System.Console.WriteLine("could not get input. Sorry.") Return 0 End Try End Function Private Sub dealCustomer() Dim aRandom As System.Random = New System.Random() Dim charged(5) As Integer Dim isCar(5) As Boolean Dim totalAmount As Integer = 0 Dim aRefuelable As Refuelable Dim i As Integer For i = 0 To charged.Length - 1 If aRandom.Next(2) = 0 Then Dim initial As Integer = aRandom.Next(Car.tankCapacity) aRefuelable = New Car(initial) isCar(i) = True Else Dim capa As Integer = aRandom.Next(Car.tankCapacity) aRefuelable = New Tank(capa, 0) isCar(i) = False End If charged(i) = aGS_.refuel(aRefuelable) totalAmount = totalAmount + charged(i) Next System.Console.Write("No.") For i = 0 To charged.Length - 1 If isCar(i) Then System.Console.Write(vbTab + CStr(i) + "(Car)") Else System.Console.Write(vbTab + CStr(i) + "(Tank)") End If Next System.Console.Write(vbCrLf + "amount:") For i = 0 To charged.Length - 1 System.Console.Write(vbTab + CStr(charged(i))) Next System.Console.Write(vbCrLf + "price:") For i = 0 To charged.Length - 1 Dim price As Integer = aGS_.price(charged(i)) aGS_.addSales(price) System.Console.Write(vbTab + CStr(price)) Next System.Console.WriteLine(vbCrLf + "total: " + CStr(totalAmount) + " [liter] /" _ + CStr(aGS_.totalSales()) + " [yen]") End Sub 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 Shared Sub Main() Dim simulation As GasStationSimulation = New GasStationSimulation() simulation.simulate() End Sub End Class
□
3.9.5 実行結果
Refuelableインターフェースを使うガソリンスタンドシミュレーションの実行結果は、次のようになります。
For all gas stations:
initial amount? 250
unit price? 108
Select command:
1) Normal 2) Discount other)exit 1
No. 0(Car) 1(Car) 2(Tank) 3(Car) 4(Tank)
amount: 43 5 21 33 30
price: 4644 540 2268 3564 3240
total: 132 [liter] / 14256 [yen]
Select command:
1) Normal 2) Discount other)exit 2
borderline amount? 25
No. 0(Car) 1(Car) 2(Car) 3(Car) 4(Tank)
amount: 7 46 5 49 6
price: 756 4738 540 5047 648
total: 113 [liter] / 11729 [yen]
Select command:
1) Normal 2) Discount other)exit 3