オブジェクト・プログラミング 第10回 オブジェクト指向設計のキモ 前回多かった質問(1) 削除するときに無駄なメモリが残ってしまう きがするのですが? 100番地 101番地 102番地 103番地 104番地 ‘お’ ‘は’ ‘よ’ ‘ご’ ‘ざ’ 101 102 103 104 null Javaでは、いらないメモリを常に見張っている「ガベージコレクタ」 というすばらしい機能が標準でついています。 誰からも参照されていないオブジェクトを自動的にメモリから消します。 前回多かった質問(2) やけに簡単でした 是非発展問題に挑戦してください。 やけに難しくなりました。 一番難しいところですので、しっかり復習してく ださい。 今日考えて欲しいこと オブジェクト指向でソフトウエアを設計する 上でのとても重要な考え方。 保守性 バグのないプログラムをつくるために。 プログラマーの人的ミスでバグを生まないように。 拡張性 人の作ったプログラムをなるべく再利用できる。 今日の目標 「継承」を使った設計の利点を説明できるように なる。 オブジェクト「アクセス指定」の利点を説明できる ようになる。 「拡張性」を高めるために 「保守性」を高めるために クラス図が読めるだけでなく、簡単なものを書け るようになる。 練習問題を書いて、提出してもらいます。 前回やったこと 第5回で配列で実装した、ItemInfoFolderを 連結リストで実装した。 →中身を替えただけ。 前回やったことのポイント ItemInfoFolderの中身を変えるだけで、配列か ら連結リストへ移行できた。 他のクラスを変える必要が無かった。 →クラス設計が成功した。 一つの変更をするときに、なるべく少ない個所を 書き換えるだけで済むようにするのがソフトウエ ア設計の基本です。→拡張性 もし、あっちこっち変えなければいけなかったら大変! →バグがバグを呼ぶ。 今日の課題 配列で作ったItemInfoFolderと 連結リストで作ったItemInfoFolderの 性能比較を行ないなさい。 前回の問題点 前回の方法で、連結リストへの変更はうま くできました。 しかし、前回の方法では、2つのデータ構 造を「必要に応じて使い分ける」ということ が難しくなります。 今日の課題をうまくやるためにはどうしたらい いか? 前回の方法の延長で 今回の課題をクラス設計する ※ImplはImplementation(実装)の略です。 前ページのクラス設計の問題点 なぜまずいのか? VendingMachineMainに、配列と連結リストの 性能比較をするための、ほとんど同じコードを 2回書く必要があります。 同じコードを2回書かなければならないよう な設計は、ソフトウエアの保守性という観 点からすると最低の設計です。 重複コードでは、一つの個所を修正するときに、 あっちこっちを修正する必要が出てきます。 VendingMachineMainのリスト //まずい設計のVendingMachineMainのmainメソッド public static void main(String[] args) { StopWatch stopwatch = new StopWatch(); //ストップウオッチを作る //まず、連結リストの時間を測る ItemInfoFolderListImpl listfolder = new ItemInfoFolderListImpl(); stopwatch.start();//ストップウオッチをスタートさせる //挿入する listfolder.insert(new ItemInfo(2000,"コーラ")); listfolder.insert(new ItemInfo(3000,"ソーダ")); //.......コードが続く //次に、配列の時間を測る ItemInfoFolderArrayImpl arrayfolder = new ItemInfoFolderArrayImpl(); } stopwatch.start();//ストップウオッチをスタートさせる //挿入する arrayfolder.insert(new ItemInfo(2000,"コーラ")); arrayfolder.insert(new ItemInfo(3000,"ソーダ")); //.......コードが続く この部分は、 同じコードなので、 メソッド化したい。 しかし! 現状の設計では、 対象クラスが違う という理由で メソッド化できない。 さらに、拡張性もよくない。 本授業では取り上げませんが、HashTableというデータ構造もあります。 その場合、Mainに3回目の重複コードが加えられることになります。 そこで。。。(ここから難しいかも) ArrayImplとListImplの機能を抽象化した クラスItemInfoFolderを用意します。 え?昔に戻った? いえいえ、このItemInfoFolderは、実装を定義しないクラスです。 実装を定義しないということは、その実装が、配列か、連結リストか は知らないけれども、ItemInfoを管理するためにinsertやdeleteの 機能を持ちますよ!という意味です。 ここで継承を使います。 ItemInfoFolderArrayImplは ItemInfoFolderを継承して いるというクラス図の記号 継承を使うことによって、配列で実装したクラスも連結リストで実装した クラスも、ItemInfoFolderという共通の性質を持つことを定義できます。 継承の用語 ItemInfoFolderクラスは、 ItemInfoFolderListImplクラスと ItemInfoFolderArrayImplクラスの スーパークラス ItemInfoFolderListImplクラスと ItemInfoFolderArrayImplクラスは ItemInfoFolderクラスの サブクラス 継承とは? 異なるクラスを抽象化し、共通の振る舞い やデータ構造を共有化したスーパークラス を作ることができる。 サブクラスは、継承することによって、スー パークラスの振る舞いやデータ構造をすべ て受け継ぐことができる。 今回の場合、insertやdeleteなどの振る舞い の定義だけ受け継ぎます。 継承を利用した、今日の課題の 設計 VendingMachineMainクラスは、配列だか、連結リストだかは、 わからないけど、挿入、削除ができる、ItemInfoFolderを使います という設計にします。 Javaを使った継承の実装練習 まだ、何が嬉しいかは、ピンと来ないと思う ので、Javaを使って、簡単な実装の練習を してみましょう。 練習課題 Personクラスの実装 /** * 継承の練習:人間クラス */ public abstract class Person { クラス宣言の前に abstract宣言をします。 抽象クラスという意味になり、 抽象メソッドが使えます。 /** * あいさつするメソッド。 * 同じ人間でも、使う言葉によって、 * 方法は異なるのでここでは実装しません。 メソッドの返り値宣言の前に */ abstract宣言をします。 public abstract void greeting(); 抽象メソッドの意味になり、 サブクラスに実装を任せます。 } JapaneseImplクラスの実装 /** * 継承の練習:人間を日本人として実装したクラス */ public class PersonJapaneseImpl extends Person{ /** * 挨拶するメソッドの実装 */ public void greeting(){ System.out.println("こんにちは"); } } Personクラスを 継承します! という宣言をします。 AmericanImplクラスの実装 /** * 継承の練習:人間をアメリカ人として実装したクラス */ public class PersonAmericanImpl extends Person{ /** * 挨拶するメソッドの実装 */ public void greeting(){ System.out.println("hello!"); } } PersonMainの実装 これはまずい例です。 abstractクラスはインスタンス化(new)でき ません。 //まずい例 //PersonMainクラス、メインメソッドを抜粋 public static void main(String[] args) { Person person = new Person(); person.greeting(); } PersonMainの実装 //PersonMainクラス、メインメソッドを抜粋 public static void main(String[] args) { Person person1 = new PersonJapaneseImpl(); person1.greeting(); Person person2 = new PersonAmericanImpl(); person2.greeting(); } <実行結果> こんにちは hello! 今までの誤解 Person person1 = new PersonJapaneseImpl(); この行の意味は、Personクラスのインスタンスが入る箱をつくり、(メ モリを確保し)PersonJapaneseImplクラスをインスタンス化して代入 しなさい。の意。 今までは、箱には、同じ種類のインスタンスしか入らないと教えまし たが、実は違いました。 訂正すると、同じ種類のインスタンスか、もしくは、サブクラスのイン スタンスを入れることができるということになります。 ※逆にスーパークラスを入れることはできません。 継承の利点 mainは、サブクラスの実装がどうなってい ようと、Personとして扱うことができる。 サブクラスの実装に依存しないコードが書ける ため、サブクラスを増やすだけで機能を拡張 できる。 public static void main(String[] args) { Person person; person = new PersonJapaneseImpl(); person.greeting();//Japaneseのgreetingが呼ばれる person = new PersonAmericanImpl(); person.greeting();//Americanのgreetingが呼ばれる } 継承のもう一つの利点 サブクラスに共通のコードを2つに書く必要 が無くなる 握手をするメソッド をつけくわえたい 握手は万国共通 handShake()メソッドは、万国共通なので、 Personクラスで実装してしまいます。 Personクラスを継承したすべてのサブクラ スにhandShake()メソッドが継承され、使え るようになります。 拡張したPersonクラス /** * 継承の練習:人間クラス */ public abstract class Person { /** * あいさつするメソッド。 * 同じ人間でも、使う言葉によって、方法は異なるのでここでは実装しません。 */ public abstract void greeting(); /** * 握手するメソッド:万国共通のはず */ public void handShake(){ System.out.println(“握手しました”);//日本語なのは勘弁してください。 } } Personクラスの変更だけでサブ クラスの機能拡張ができます。 JapaneseImpl,AmericanImplなどのクラス は、変えてません。 自動的に継承されます。 //握手をするように改造したmain public static void main(String[] args) { Person person; person = new PersonJapaneseImpl(); person.handShake();//握手しましたと表示される person = new PersonAmericanImpl(); person.handShake();//握手しましたと表示される } 握手しない人種を作りたい メソッドの再定義 (オーバーライド) をします。 NoHandShakePersonの実装 /** * 継承の練習:握手しない人種として実装したクラス */ public class NoHandShakePerson { /** * 挨拶するメソッドの実装 */ public void greeting(){ System.out.println("おっス"); } /** * 握手するメソッド:握手しない人種なので、再定義する */ public void handShake(){ System.out.println("できません"); } } 実行結果 //PersonMainクラス、メインメソッドを抜粋 public static void main(String[] args) { Person person; person = new PersonJapaneseImpl(); person.handShake();//”握手しました“と表示される person = new PersonAmericanImpl(); person.handShake();//”握手しました“と表示される person = new NoHandShakePerson(); person.handShake();//”できません“と表示される } さて、今日の課題に戻りましょう コードの重複部分を取り除き、メ ソッド化が可能になります。 //よい設計のVendingMachineMainのmainメソッド public static void main(String[] args) { //連結リスト実装の評価 performanceAssessment(new ItemInfoFolderListImpl()); //配列実装の評価 performanceAssessment(new ItemInfoFolderArrayImpl()); } スーパークラスを引数に とるので、どんなサブクラスでも入れられる コ昔 ー重 ド複 し て い た //性能を調べる重複したコードをメソッド化 public static void performanceAssessment(ItemInfoFolder folder){ StopWatch stopwatch = new StopWatch(); //ストップウオッチを作る } stopwatch.start();//ストップウオッチをスタートさせる //挿入する folder.insert(new ItemInfo(2000,"コーラ")); folder.insert(new ItemInfo(3000,"ソーダ")); //.......コードが続く オブジェクトの保守性を高める オブジェクトのカプセル化 アクセス指定 第8回で作った ItemQueueクラス 配列で実装していましたが、このコードに は、保守性という面で問題があります。 //円環キューを使って商品を収納するクラス public class ItemQueue { int arrayMax = 5;//配列のサイズ Item[] itemArray = new Item[arrayMax];//商品が入る配列 int addCursol = 0;//追加カーソル int removeCursol =0;//削除カーソル //挿入するメソッド public void insert(Item insertItem){ itemArray[addCursol] = insertItem;//追加カーソルの位置に挿入する addCursol++; //追加カーソルを1増やす if (addCursol >= arrayMax){//配列の最後にきたら addCursol = 0; //ラップアラウンドする } 何故問題があるの? このQueueクラスは、以下のように使われ ることを想定しています。 //想定された使い方 public static void main(String args[]){ ItemQueue queue = new ItemQueue(); //最後に挿入 queue.insert(new Item(“2001/03/05”)); //先頭を削除 Item item = queue.remove(); } 不正な使い方 しかし、以下のように使われるとキューは 正常な動作をしなくなってしまいます。 //不正な使い方 public static void main(String args[]){ ItemQueue queue = new ItemQueue(); //キューの最後に挿入 queue.insert(new Item(“2001/03/05”)); queue.insert(new Item(“2001/03/08”)); //動くけど不正な使い方 queue.itemArray[0] = null; } //製造日03/05のItemが返ってきて欲しいのにおそらくnullが返って来る Item item = queue.remove(); 外部からキューの配列にアクセ スされると困る queue.itemArray[0] = null; この行が問題ですね。キューは、次にremove() が呼ばれたとき、何も知らずに先頭である配列 の0番目を返すでしょう。 つまり、キューの中の配列は、キュー内部で操作 されるべきであり、外部から参照されては、まず いことが起こる可能性があるのです。 そんなことしなければいい!とおっしゃるかもしれませ んが、人的ミスというのは怖いものです。間違えない 可能性は0とは言い切れません。 外部からアクセスできないように する そのようなミスの可能性を0にする方法が あります。 //円環キューを使って商品を収納するクラス public class ItemQueue { private private private private int arrayMax = 5;//配列のサイズ Item[] itemArray = new Item[arrayMax];//商品が入る配列 int addCursol = 0;//追加カーソル int removeCursol =0;//削除カーソル 外部から参照されては困る変数、メソッドには、 privateアクセス修飾子を変数の前につけます 不正できなくなる //不正できない public static void main(String args[]){ ItemQueue queue = new ItemQueue(); //キューの最後に挿入 queue.insert(new Item(“2001/03/05”)); queue.insert(new Item(“2001/03/08”)); queue.itemArray[0] = null;//itemArrayにprivateがついていれば //コンパイルエラー } //製造日03/05のItemが返ってきて欲しいのにおそらくnullが返って来る Item item = queue.remove(); Javaで使えるアクセス修飾子 public private そのクラス内でのみ参照可能 protected すべてのクラスから参照可能 そのクラスとそのサブクラスでのみ参照可能 何もつけない パッケージ内でのみ参照可能 ※パッケージについては、授業で取り上げません。 各自勉強してください。 基本的にクラスのすべての属性 はprivateでよい。 現状のItemInfoクラス //商品情報のクラス public class ItemInfo{ int no; // 商品番号 String name; // 商品名 } オブジェクトの カプセル化 という概念です。 //コンストラクタ public ItemInfo(int initNo, String initName){ no = initNo; name = initName; } 保守性を高くしたItemInfoクラス //商品情報のクラス public class ItemInfo{ private int no; // 商品番号 private String name; // 商品名 商品番号と商品名への 直な参照を禁止します。 //コンストラクタ public ItemInfo(int initNo, String initName){ no = initNo; name = initName; } //商品番号を得るメソッド public int getNo(){ return no; } //名前を得るメソッド public String getName(){ return name; } } 変わりに、publicで それらを得ることができる メソッドを用意します。 これで、商品番号と商品名はコンストラクタで 一旦設定されたら、外部から変更できなくなりました。 外部から設定変更を許すとき //商品情報のクラス public class ItemInfo{ private int no; // 商品番号 private String name; // 商品名 //コンストラクタ省略 //商品番号を得るメソッド public int getNo(){ return no; } //名前を得るメソッド public String getName(){ return name; } } //名前を設定するメソッド public void setName(String newName){ name = newName; } 変更を許す属性にのみ publicな変更メソッドを 用意します。 めんどくさいかもしれませんが、 このポリシーは、特に複数人で の作業をするときには重要です。 (オブジェクト指向は、複数人で の作業を前提に考えられてい ます。) 使い方 //第9回課題のItemInfoFolderのdisplay()から抜粋 ItemInfo iteminfo = current.data; System.out.println(“[“+iteminfo.no+”,”+iteminfo.name+”]”); //新しい使い方 ItemInfo iteminfo = current.data; System.out.println(“[“+iteminfo.getNo()+”,”+iteminfo.getName()+”]”); 今日の課題1 第8回で配列のラップアラウンドを使った キューを実装しました。 配列でも、連結リストでも使えるように、設計し なおしなさい。 また、アクセス指定も考えて、適宜メソッドを付 け加えなさい。 クラス図を手書きで書いて授業時間内に 提出してください。 今日の課題2 配列で作ったItemInfoFolderと 連結リストで作ったItemInfoFolderの 性能比較を行ないなさい。 授業中に示した設計図どうり実装しなさい。 比較する項目については、各自で考えなさい。 今回は、比較が主眼ではなく、設計、実装が主 眼ですので、詳細な考察は必要ありません。 スケルトンを配ります。 発展課題 さらに、ソートのアルゴリズムも柔軟に交換 できるような設計図を書き、実装しなさい。 提出方法 [email protected]宛て。 今回はソースと簡単な考察を送ってください。 ソースは自分が変更したクラスをすべて送ること Subjectはログイン名を必ず書いてください。 (感想を任意でお願いします。) ex) t96844ym 余計な[]などをつけないでください。 水曜まで。
© Copyright 2024 ExpyDoc