導入
同じ機能を持つクラスで、
展開
工夫の必要が生まれる
前回の学習の流れを次に示します。
- 設定情報が記載されたテキストファイルを読み込むクラスを作ろう。
- クラス1ができた。
- 別の方法でテキストファイルを読み込みたい。では別のクラスを作ろう。
- クラス2ができた。
クラス1とクラス2は同じ結果を得られるのですが、
小さなコードでしたので困りませんでしたが、
インターフェイスを利用する
前回は
今回の場合はメソッドを呼ぶ方法と得られる情報が同じでしたから、
似たような働きをするプログラミング言語の機能に抽象クラスがあります。抽象クラスはインターフェイスと同じ機能に加えて、
- 継承はトラブルメーカーなので、
あまり使いたくない。 - 抽象クラスは複数継承できない。インターフェイスなら複数実装できる。
- 窓口を共通にする仕事のためにはインターフェイスで十分。
「複数継承」
先々も便利に使えるインターフェイスの最も基本的な使い方、
共通部分をインターフェイスに切り出す
詳しいインターフェイスの記述方法は参考文献等を読んでもらうとして、
interface ConfigInfo {
// フィールド
String CONFIG_FILENAME = "CONFIG.TXT";
// 抽象メソッド
String getVersion();
}
現実的・CONFIG_
はインターフェイスに記載すべきではないと思いますが、getVersion
であるとはっきりしました。このメソッドは必ず実装しないとコンパイルできませんから、ConfigInfo
インターフェイスを実装するクラスにloadVersionString
というメソッドを別に作ることはできますが、getVersion
メソッドは必ず実装しなければなりません。共通の情報を取得するためのメソッド名がクラスによって異なるのは、
インターフェイスConfigInfo
を実装したクラスのsketch AppInfoLoader3.
と、
なお、AppInfoLoader.
とAppInfoLoader2.
は前回第15回で作成したファイルです。同じスケッチフォルダにコピーを置いてください。
AppInfoLoader3.pde
。インターフェイスConfigInfo
を実装したクラスpublic class AppInfoLoader3 implements ConfigInfo{
public static final String CLASS_NAME = "AppInfoLoader3";
private String version;
public AppInfoLoader3(){
String lines[] = loadStrings(CONFIG_FILENAME);
if (0 < lines.length){
version = "No Version Info(Config file contains " + lines.length + " lines but...)";
for(String v : lines){
String[] tokens = split(v, ",");
if(tokens[0].equals("VERSION")){
version = tokens[1];
}
}
} else {
version = "No Info";
}
}
public String getVersion(){
return version;
}
}
TestAppInfoLoader.pde
//Test AppInfoLoader class
private final String TARGET_CLASS_NAME = "AppInfoLoader";
private final String TARGET_CLASS_NAME2 = "AppInfoLoader2";
private final String TARGET_CLASS_NAME3 = "AppInfoLoader3";
private final String SOME_TROUBLE = "Some trouble happend.";
private final String TARGET_CLASS_VERSION = "1.0";
void setup(){
test();
}
void test(){
noLoop();
println("Test Start.");
println("...AppInfoLoader begins.");
assert AppInfoLoader.CLASS_NAME.equals(TARGET_CLASS_NAME)
: TARGET_CLASS_NAME + " : " + SOME_TROUBLE;
AppInfoLoader ail = new AppInfoLoader();
assert ail.CLASS_NAME.equals(TARGET_CLASS_NAME)
: TARGET_CLASS_NAME + " : " + SOME_TROUBLE;
assert ail.getVersion().equals(TARGET_CLASS_VERSION)
: TARGET_CLASS_NAME + " : " + SOME_TROUBLE
+ "(" + ail.getVersion() + ")";
println("...AppInfoLoader finished.");
println("...AppInfoLoader2 begins");
assert AppInfoLoader2.CLASS_NAME.equals(TARGET_CLASS_NAME2)
: TARGET_CLASS_NAME2 + " : " + SOME_TROUBLE;
AppInfoLoader2 ail2 = new AppInfoLoader2();
assert ail2.CLASS_NAME.equals(TARGET_CLASS_NAME2)
: TARGET_CLASS_NAME2 + " : " + SOME_TROUBLE;
assert ail2.getVersion().equals(TARGET_CLASS_VERSION)
: TARGET_CLASS_NAME2 + " : " + SOME_TROUBLE
+ "(" + ail2.getVersion() + ")";
println("...AppInfoLoader2 finished.");
println("...AppInfoLoader3 begins");
assert AppInfoLoader3.CLASS_NAME.equals(TARGET_CLASS_NAME3)
: TARGET_CLASS_NAME3 + " : " + SOME_TROUBLE;
AppInfoLoader3 ail3 = new AppInfoLoader3();
assert ail3.CLASS_NAME.equals(TARGET_CLASS_NAME3)
: TARGET_CLASS_NAME3 + " : " + SOME_TROUBLE;
assert ail3.getVersion().equals(TARGET_CLASS_VERSION)
: TARGET_CLASS_NAME3 + " : " + SOME_TROUBLE
+ "(" + ail3.getVersion() + ")";
println("...AppInfoLoader3 finished.");
println("All Test Done.");
exit();
}
インターフェイスは実装を強制する道具
こうしてインターフェイスを使うのであれば、CLASS_
も共通です。ただし値がクラスごとに異なりますので、getClassName
を追加しましょう。そして具体的な値は実装時に与えるわけです。こうして、AppInfoLoader3.
のタブを加えて実行しようとしても、AppInfoLoader3
クラスはConfigInfo
インターフェイスのgetClassName
メソッドを実装していませんからエラーを発生し実行できません。
インターフェイスにメソッド追加の変更が生じると、
TestAppInfoLoader2.pde
//Test AppInfoLoader class
private final String TARGET_CLASS_NAME4 = "AppInfoLoader4";
private final String SOME_TROUBLE = "Some trouble happend.";
private final String TARGET_CLASS_VERSION = "1.0";
void setup(){
test();
}
void test(){
noLoop();
println("Test Start.");
println("...AppInfoLoader4 begins");
assert AppInfoLoader4.CLASS_NAME.equals(TARGET_CLASS_NAME4)
: TARGET_CLASS_NAME4 + " : " + SOME_TROUBLE;
AppInfoLoader4 ail4 = new AppInfoLoader4();
assert ail4.CLASS_NAME.equals(TARGET_CLASS_NAME4)
: TARGET_CLASS_NAME4 + " : " + SOME_TROUBLE;
assert ail4.getClassName().equals(TARGET_CLASS_NAME4)
: TARGET_CLASS_NAME4 + " : " + SOME_TROUBLE;
assert ail4.getVersion().equals(TARGET_CLASS_VERSION)
: TARGET_CLASS_NAME4 + " : " + SOME_TROUBLE
+ "(" + ail4.getVersion() + ")";
println("...AppInfoLoader4 finished.");
println("All Test Done.");
exit();
}
ConfigInfo.pde
interface ConfigInfo {
// フィールド
String CONFIG_FILENAME = "CONFIG.TXT";
// 抽象メソッド
String getVersion();
String getClassName();
}
AppInfoLoader4.pde
public class AppInfoLoader4 implements ConfigInfo{
public static final String CLASS_NAME = "AppInfoLoader4";
private String version;
public AppInfoLoader4(){
String lines[] = loadStrings(CONFIG_FILENAME);
if (0 < lines.length){
version = "No Version Info(Config file contains " + lines.length + " lines but...)";
for(String v : lines){
String[] tokens = split(v, ",");
if(tokens[0].equals("VERSION")){
version = tokens[1];
}
}
} else {
version = "No Info";
}
}
public String getVersion(){
return version;
}
public String getClassName(){
return CLASS_NAME;
}
}
インターフェイスは代理窓口
インターフェイスは、
次のコードを読んでください。インターフェイスConfigInfo
を実装する、AppInfoLoader4
とAppInfoLoader5
のインタフェイスを ConfigInfo
インターフェイスのインスタンスa
に代入しています。ConfigInfo
の実装部分のテストをするコードは同じですから、
TestAppInfoLoader3.pde
//Test AppInfoLoader class
private final String TARGET_CLASS_NAME4 = "AppInfoLoader4";
private final String TARGET_CLASS_NAME5 = "AppInfoLoader5";
private final String SOME_TROUBLE = "Some trouble happend.";
private final String TARGET_CLASS_VERSION = "1.0";
void setup(){
test();
}
void test(){
noLoop();
println("Test Start.");
ConfigInfo a = new AppInfoLoader4();
testAppInfoLoader(a,
TARGET_CLASS_NAME4,
TARGET_CLASS_VERSION);
a = new AppInfoLoader5();
testAppInfoLoader(a,
TARGET_CLASS_NAME5,
TARGET_CLASS_VERSION);
println("All Test Done.");
exit();
}
void testAppInfoLoader(ConfigInfo c,
String name,
String ver ){
println("..." + c.getClassName() + " begins");
assert c.getClassName().equals(name)
: name + " : " + SOME_TROUBLE;
assert c.getVersion().equals(ver)
: name + " : " + SOME_TROUBLE
+ "(" + c.getVersion() + ")";
println("..." + c.getClassName() + " finished.");
}
AppInfoLoader4.pde
public class AppInfoLoader4 implements ConfigInfo{
public static final String CLASS_NAME = "AppInfoLoader4";
private String version;
public AppInfoLoader4(){
String lines[] = loadStrings(CONFIG_FILENAME);
if (0 < lines.length){
version = "No Version Info(Config file contains " + lines.length + " lines but...)";
for(String v : lines){
String[] tokens = split(v, ",");
if(tokens[0].equals("VERSION")){
version = tokens[1];
}
}
} else {
version = "No Info";
}
}
public String getVersion(){
return version;
}
public String getClassName(){
return CLASS_NAME;
}
}
AppInfoLoader5.pde
public class AppInfoLoader5 implements ConfigInfo{
public static final String CLASS_NAME = "AppInfoLoader5";
private static final String CONFIG_FILENAME = "CONFIG.TXT";
private String version;
public AppInfoLoader5(){
try {
BufferedReader br = createReader(CONFIG_FILENAME);
version = "No Info";
String line = br.readLine();
while(line != null){
String[] tokens = split(line, ",");
if(tokens[0].equals("VERSION")){
version = tokens[1];
}
line = br.readLine();
}
} catch(IOException e){
e.printStackTrace();
}
}
public String getVersion(){
return version;
}
public String getClassName(){
return CLASS_NAME;
}
}
ConfigInfo.
は先ほどのものと同じです。
TestAppInfoLoader3.
のメソッドtest
やメソッドtestAppInfoLoader
を読んで便利さを感じましたか? これは実際にデザインパターンを学んだときに真価を実感します。楽しみにしておいてください。
演習
演習1(難易度:easy)
設定ファイルCONFIG.
が存在しない場合は、AppInfoLoader5
クラスを変更しましょう。新しいクラスのファイル名をAppInfoLoader6.
としてください。また、TestAppInfoLoader4.
としましょう。sketchフォルダdataディレクトリにあるCONFIG.
のファイル名を_CONFIG.
などとして、
演習2(難易度:easy)
インターフェイスConfigInfo
に設定ファイルを指定するメソッドを追加しましょう。メソッド名は動作を分かりやすく表すものにしてください。CONFIG.
ばかりでなくSETTING.
などというファイル名の設定ファイルも読み込めるようにします。これを実装するクラスをAppInfoLoader7
とし、AppInfoLoader7.
というファイル名で保存しましょう。テスト用のsketchファイル名はTestAppInfoLoader5.
としましょう。AppInfoLoader7
のインスタンス生成時にはデフォルトでCONFIG.
を読み込みましょう。
まとめ
- 実装を強制するためのインターフェイスの使い方を学習しました。
- インスタンスを切り替えるためのインターフェイスの使い方を学習しました。
学習の確認
それぞれの項目で、
- 実装を強制するインターフェイスの役目が理解できましたか?
- 理解できた。気持ちよく納得した。
- 理解できた。しかし、
今ひとつスッキリしない。 - 理解できない。
- インスタンスを切り替えるためのインターフェイスの役目が理解できましたか?
- 理解できた。気持ちよく納得した。
- 理解できた。しかし、
今ひとつスッキリしない。 - 理解できない。
参考文献
- 『Java言語プログラミングレッスン
(下)』(結城浩 著、 ソフトバンククリエィティブ株式会社) - Java言語のオブジェクト指向的特徴を大変分かりやすく解説した入門書中の名著。上下巻ともにJava言語入門者は必携。特に下巻はJava言語でオブジェクト指向を学ぶ入門書として最優秀。
演習解答
- 以下のファイルをsketchフォルダ
TestAppInfoLoader4
に納めます。 - 以下のファイルをsketchフォルダ
TestAppInfoLoader5
に納めます。