継承するクラスのデータを守る
チャーシューメンを最終チェックするときに,チャーシューのチェックがされていない.また,チャーシューメンは,しょうゆ味であるからこそチャーシューメンであって,しょうゆ以外であれば,それをチェックして,しょうゆに変更して出すようにしたい.そこで,ラーメンでチェックしている種類のチェックと,チャーシューをチェックしてからお客さんに出したい.そこで,ChashuMenクラスに,checkChashuMenメソッドを追加する.
public class ChashuMen extends Ramen { private int chashu; ChashuMen() { } public void setChashu(int chashu) { this.chashu = chashu; System.out.println("チャーシュー:" + chashu); } public void checkChashuMen() { System.out.print("チャーシュー:" + chashu); if(soup != Taste.しょうゆ) { System.out.println("\nしょうゆ味でないチャーシューメンはチャーシューメンではない!"); System.out.println("しょうゆ味に変えます!"); setSoup(Taste.しょうゆ); } } }
そうすると,継承したChashuMenクラスからRamenクラスへのデータアクセスに問題が発生する.
これは,privateの修飾子でアクセス範囲を守ると,継承したサブクラスからもアクセスできなくなってしまうことを意味している.privateが設定されたフィールドには,親子関係があっても,アクセスができないのである.
しかし,このままでは,クラスを継承して機能を拡張するうえで支障になる.そこで,継承したクラスはスーパークラスのデータにアクセスできるようにして,一方で,むやみに他のクラスからアクセスできないようにする方法はないだろうか.
そこで使えそうな修飾子は「protected」である.protectedでは,「サブクラスか,同じパッケージ内のみアクセスできる」ようにするための修飾子である.アクセス範囲を確かめるために,次のようなプログラムを作成する.
public class Ramen { protected int katasa;//1:固い,2:普通,3:柔い protected int ryou;//1-10 protected Taste soup; Ramen() { System.out.println("ラーメンつくるよ"); } public void setRyou(int ryou) { this.ryou = ryou; System.out.println("量:" + ryou); } public void setKatasa(int katasa) { this.katasa = katasa; System.out.println("固さ:" + katasa); } public void setSoup(Taste taste) { this.soup = taste; System.out.println("スープ:" + this.soup); } public void checkRamen() { System.out.print("量:" + ryou); System.out.print("固さ:" + katasa); System.out.println("スープ:" + soup); } }
protectedを設定したことにより,chechChashuMenでのエラーがなくなった.publicでも,アクセスできるが,このRamenとChashuMen以外のクラスからアクセスできるようにすることがふさわしいか検討する必要がある.通常は,メソッドを介してアクセスすることで,カプセル化が有効に作用するので,無用にpublicにすることは避けたい.
では,継承があるときには継承先でも変更できるようにアクセス範囲を「protected」に設定するのが基本であると考えるべきだろうか.しかし,必ずしもそうではない.今回は,フィールドに直接アクセスする方法を取ったため,「protected」にしなければならなかったが,「private」のままで,アクセス手段を整備しておく方法もある.
Ramenクラスのメソッドとして,スープの味を取得できるメソッド「getSoup」を用意する.これを使うことで,データに直接アクセスするのではなく,アクセスする手段を用意すれば,直接フィールドを触る必要がないので,「protected」ではなく「private」にしておくこともできる.
public class Ramen { private int katasa;//1:固い,2:普通,3:柔い private int ryou;//1-10 private Taste soup; Ramen() { System.out.println("ラーメンつくるよ"); } public void setRyou(int ryou) { this.ryou = ryou; System.out.println("量:" + ryou); } public void setKatasa(int katasa) { this.katasa = katasa; System.out.println("固さ:" + katasa); } public Taste getSoup() { return this.soup; } public void setSoup(Taste taste) { this.soup = taste; System.out.println("スープ:" + this.soup); } public void checkRamen() { System.out.print("量:" + ryou); System.out.print("固さ:" + katasa); System.out.print("スープ:" + soup); } }
このとき,Ramenクラスを継承するChashuMenクラスでスーパークラスのフィールドに直接アクセスするのではなく,getSoupメソッドを用いて値を取得している.さらに,setSoupメソッドを用いて値を設定している.
public class ChashuMen extends Ramen { private int chashu; ChashuMen() { } public void setChashu(int chashu) { this.chashu = chashu; System.out.println("チャーシュー:" + chashu); } public void checkChashuMen() { System.out.print("チャーシュー:" + chashu); if(getSoup() != Taste.しょうゆ) { System.out.println("\nしょうゆ味でないチャーシューメンはチャーシューメンではない!"); System.out.println("しょうゆ味に変えます!"); setSoup(Taste.しょうゆ); } } }
フィールドを取得するメソッド,フィールドを設定するメソッド,これらをアクセサ(アクセッサ)と呼ぶ.フィールドが「soup」であるとき,「setSoup」「getSoup」のように,set,getを先頭に持つメソッド名からなる形にすることが多い.開発環境によっては,フィールドを設定後,set,getを先頭に持つメソッドを自動生成してくれる機能があるものもある.それぐらい一般的なものである.
課題
タンメンクラスにおいて,スープの味がしおであることを確認し,野菜の量を表示するcheckTanmenメソッドを追加せよ.その際,Ramenクラスのフィールドの修飾子がprivateであると問題が発生することを確認せよ.