2016年度 発展プログラミング演習 第5講 図書館システム 3日目
目次
今までのまとめとこれからの方針
今までは,Javaの基本的な学習のため,型の作成とその型を集合として扱う方法を中心に学びました. 3日目からは,今までの復習を兼ねて新たな機能を追加していきます. その題材として,貸し出し履歴が記録できるようにします.
8. 貸し出し履歴の情報を表す型を作成する.
8-1. 貸し出し履歴に必要な情報
貸し出し履歴には,どのような情報が必要でしょうか.貸し出し履歴とは, 本をユーザに貸し出す時に作成されるデータです.一般的に,図書館の貸し出し履歴には, 誰が,いつ,何を借りて,いつ返したかの情報が必要となるでしょう.
それを踏まえて,今回は次のような情報が最低限,必要となるでしょう. もちろん,必要に応じて他の情報を持たせても構いません.
- 借りた本の情報
- 借りたユーザの情報
- 貸し出した日時
- 返却した日時
8-2. 型作成の意義
貸し出し履歴を記録するためには,貸し出し履歴をデータとして保持しなければいけません.
例えば,右図のように,文字列として保持しておく方法が考えられます.
しかし,この方法だと,本はタイトルでしか管理できないことになります.
異なる出版社から出版された同じタイトルの本があった場合,両者が区別できません.
出版社も貸出履歴に加えたとしても,今度は整合性をきちんと保てるかが問題になります.
もちろん,しっかりとチェックするようプログラムを一所懸命書けば想定通りのシステムが構築できます.
しかし,そのようなことに労力を使うよりも,より高度なことに労力を使う方が全体的な効率は上がります.
では,どうすれば良いでしょうか.
貸し出し履歴を表す一つの型を作成し,その型に必要な情報を持たせれば良いわけです.
例えば,右図のように,貸し出し履歴という型を作成し,そのオブジェクトが情報を持つようにします.
こうすることで,1件の貸し出し履歴を一つのまとまりとして扱えることができます.
そして,文字列の場合に比べて,必要な情報にもアクセスしやすくなりました.
なぜなら,文字列の場合であれば,一つの情報を取り出すために,
文字列全体から必要な情報を検索する必要がありました.文字列処理を行わなければいけませんでした.
一方の,貸し出し履歴という型を作成した場合,貸し出し履歴に含まれる情報は,
貸し出し履歴オブジェクトが持つ変数として表されます.変数のアクセスで必要な情報が取得できます.
どちらが簡単にプログラミングできるかは明白でしょう.
8-3. 貸し出し履歴型の作成
では,貸し出し履歴が持つ情報それぞれを,どのような型で表せば良いかを考えましょう.
日時を表すには,Date型が利用できます.
本の情報と,ユーザの情報はどのように表せば良いでしょうか.
本の情報を表すために,String型の本のタイトルを使えば良いでしょうか.
そのような情報よりも,本そのものを表す型を先週までに作成しました.
Book型です.このBook型を本の情報として扱いましょう.
最後にユーザの情報です.ユーザという概念は今まで出てきませんでした.
そのため,新たに作成する必要があります.
とはいえ,まずは,貸し出し履歴を表す型,Historyを作成しましょう.
import java.util.Date;
public class History{
}
まずは,左図のように,Historyクラスのクラス宣言を行ってください.
Dateクラスを利用する場合は,クラス宣言の外側に,
import java.util.Date;もしくは,
import java.util.*;という記述が必要です.
import java.util.*;という記述は,Date
以外のクラス(例えば,ArrayListやListなど)
もインポートします.一方のimport java.util.Date;
は,Dateのみをインポートします.
インポートと言っても,コンパイル時のクラス検索の対象が変わるだけですので,
import文の違いは,実行時の速度に全く影響を与えません.
HistoryクラスはBookクラスと同じく,
他のクラスから利用されることが前提のクラスですから,
mainメソッドは作成しなくても構いません.
では,このクラスに4つのフィールド(変数)を追加しましょう.次の4つです.
- 貸し出し日を表す
Date型のlendDate - 返却日を表す
Date型のreturnDate - 借りた本を表す
Book型のbook - ユーザ情報を表す
User型のuser
ここで,Userはまだ作成していないクラスですので,
このままではコンパイルできません.引き続き,User情報を作成しましょう.
9. 利用者の情報を表す型を作成する.
ここでは,ユーザの情報を表す型を作成します. 先ほどの貸し出し履歴の情報を表す型の作成の時と同じように, どのような情報が必要か考えてみましょう.
ユーザの情報は,名前と性別,年齢があれば十分でしょう. もちろん,住所や電話番号も場合によっては必要かもしれません. 必要に応じて追加してください.
では,ユーザを表す型Userクラスを作成し,次の3つのフィールドを追加してください.
- ユーザの名前を表す
String型のname. - ユーザの性別を表す
String型のgender. - ユーザの年齢を表す
Integer型のage.
Userクラスが作成できれば,先ほど作成したHistory
がコンパイルできるようになりました.コンパイルできるか確認してみましょう.
10. ユーザを管理するプログラムを作成する.
10-1. ユーザ情報の登録,検索,更新,削除
ユーザ情報を登録,検索,更新,削除できるプログラムを作成しましょう.
なお,この登録,検索,更新,削除をまとめて,CRUD
と言う場合もあります.このプログラムは,まずは,Library
のことは忘れて作成してみましょう.
クラス名はUserManagerとし,次の6つのメソッドを持ちます.
updateは,扱うデータであるUser
が更新する情報をあまり持たないため,今回は作成しないものとします.
なお,下に挙げたメソッド以外にも,mainメソッドと
runメソッドも作成しましょう.
runメソッドの中身は10-2で作成しますので,ここでは空にしておいてください.
-
createメソッド.引数は,String型の名前,性別,そして,Integer型の年齢としてください.Userオブジェクトを作成し,引数の値を作成したオブジェクトに代入して返すメソッドです. -
printメソッド.引数に受け取ったUserオブジェクトの情報を画面に出力するメソッド. -
sizeメソッド.引数はありません.返り値は,Integerとし,UserManagerが管理するUserの数を返します. -
addメソッド.引数にUser型の変数を受け取ります. 返り値はvoidです. 受け取ったUserオブジェクトをユーザ型の集合に追加します. -
findメソッド. 返り値は検索結果のUserを格納したListとします. 引数は,String型の名前,性別,Integer型の年齢です. いずれかの条件に一致すれば検索結果に含めましょう.addメソッドで登録したUserオブジェクトの集合から検索を行います. -
deleteメソッド.User型の変数を受け取り,削除できたか否かをBooleanで返します. 引数に受け取ったUserオブジェクトを登録から削除します.List型のremoveメソッドが削除の成否をBooleanで返しますので,その返り値をそのまま返せば良いでしょう.
なお,上記のメソッドの処理を実装するために,ユーザ情報を格納するフィールドの定義も必要となります.
扱うデータはBookとUserの違いはありますが,
基本的なプログラムはLibraryと同じです.
10-2. ユーザ情報の登録,検索,更新,削除
ここでは,10-1で作成した6つのメソッドをrun
メソッドから呼び出してユーザ情報を操作していきます.
runメソッドで,次の操作を行ってください.
- 自分の名前,年齢,性別を持つ
User型の変数myselfを作成します. - 先ほど作成したオブジェクトを
addメソッドにより追加します. - 同じように,更に5つの
Userオブジェクトを作成し,登録してください. - ユーザを検索しましょう.検索に成功する場合,失敗する場合の2通りの検索を行ってください.
-
上の処理での検索結果のユーザ一覧を
printメソッドで出力してください.
10-3. ユーザ情報の一覧.
ユーザ情報が扱えるようになると,全てのユーザに対して何らかの処理を加えたい場合が出てきます.
例えば,全ユーザ情報を画面に出力する,などです.そのような場合,どのようにすれば良いでしょうか.
もちろん,printAllUsersのようなメソッドを作成し,
そのメソッド内で処理を行えば,目的は達成できます.
しかし,そのような処理が必要になった時に,メソッドを追加するのは現実的ではありません.
完成したプログラムを変更する,というのは,実は非常にコストの高い行為であるためです. プログラムを変更した場合,その変更がどこまで影響を及ぼすのかわかりません. そのため,プログラムの全ての箇所が思い通りに動いているのか,再度確認する必要があるのです. 変更箇所だけの確認だけだと,その影響を受けてバグが発生している箇所を見逃す可能性が残ることになります.
以上のことから,UserManagerを変更せずに,
全ユーザに対する何らかの処理を追加する方法を考えなくてはいけません.
どのようにすれば良いでしょうか.
一つの回答は,Userを格納する集合であるList
型の変数を返すメソッド(List<User> getUserList()のようなメソッド)
を用意することです.Userの集合自体を返すメソッドを用意することで,
UserManager以外のプログラムが全ユーザを参照できるようになります.
しかし,これも良い回答ではありません.UserManager
のあずかり知らぬところで集合に対する更新操作が行えるためです.つまり,
List<User>を返すということは,addや
deleteメソッドを用意する必要がないためです.仮に,
addで重複のチェックを行っていたとしても,
そのチェックをくぐり抜けて登録できてしまうようになります.
public Iterator<User> iterator(){
return userList.iterator();
}
一番良い回答は,参照しかできないUserの集合を返すことです.
参照専門の集合を表す型がJavaで標準的に用意されています.
Iterator型です.日本語では,
列挙子とも言われます.
ListからIteratorに変換する方法も簡単です.左図のように,
List型の変数userListのiterator()
メソッドを呼び出すことで,ListからIteratorに変換できます.
なお,IteratorもListと同じように,
格納されている型が何かを明示する必要があります.
Iterator<格納されている型>のように,型名の後ろに格納されている型を指定します.
ここでは,List<User>からIteratorを取り出すわけですから,
Iteratorが保持する型もListが保持する型と同じでなければいけません.
ですから,Iterator<User>です.
では,UserManagerのプログラムに,iteratorメソッドを追加しましょう.
iteratorメソッドの内容は,Userを格納するList
型変数に対して,iteratorメソッドを呼び出すだけです.
なお,Iteratorを使う場合は,プログラムの先頭に,
import java.util.Iteratorの一行が必要になります.
これにより,UserManager以外のプログラムで,ユーザ一覧に対する処理が可能になりました.
10-4. 列挙子を使った繰り返し
ここでは,UserManagerのrunメソッドの最後に,
列挙子を使ってユーザの一覧を順に表示していく処理を書いていきます.
10-3で書いた処理の後ろに,次の手順での処理を追加しましょう.
Iterator<User>型の変数iteratorを宣言しましょう.-
this.iterator()メソッドを呼び出して, 返り値をiteratorに代入してください. -
繰り返しを行いましょう.終了条件は,
iterator.hasNext()です.whileでも,forでも終了条件のところに,iterator.hasNext()を書いてください. -
繰り返し内で,
iterator.next()メソッドを呼び出してください. 返り値として,User型の変数が返されます. - 上の手順で返ってきた値を
printメソッドに渡しましょう.
Iteratorを使った繰り返しは,Java言語では頻繁に出てきます.
Integer型を使う繰り返しよりも,頻繁に利用します.
是非,書き方を覚えてください.
練習問題
1. Libraryからのユーザ登録
Libraryからユーザを扱えるようにしましょう.
具体的には,次に示す処理をLibraryに書き加えてください.
-
フィールドに
UserManager型の変数managerを宣言してください. managerの初期値に,UserManagerオブジェクトを代入しましょう.-
registerUsersメソッドを追加してください. 引数なし,返り値はvoidです. -
registerUsersメソッド内で,managerに5名のユーザを作成して,追加してください. ユーザの情報は,自由に決めてください. -
Libraryクラスのrunメソッドで,addBooksメソッドを呼び出した直後に,registerUsersを呼び出してください. -
Libraryクラスのrunメソッドでの,registerUsersの呼び出し後に,UserManagerが持つユーザを出力しましょう.UserManagerに対して,iteratorメソッドを呼び出し, 繰り返しを作成してください. 繰り返しの中で,UserManagerのprintメソッドを用いてユーザ情報を一つずつ出力してください. -
上記の処理が書ければ,
Libraryクラスを実行して,実行結果を確認しましょう.
さて,最後の確認で,得られた結果は,予想通りでしたか? 予想と違っていましたか?
UserManagerのrunメソッドで追加したユーザはどこに行ったのでしょうか.
なぜ追加されないのか,考えてみてください.
2. 貸し出し中であるかを返すメソッドの定義
8-3. 貸し出し履歴型の作成において,
History型を作成しました.この型は内部に4つのフィールドを宣言しています.
このHistoryクラスに新たに,貸し出し中であるかを判定するメソッドを追加しましょう.
メソッド名は,isLentとし,引数はなし,返り値として
Booleanを返すようにします.貸し出し中であれば,true,
貸し出し中でなければfalseを返すものとします.
メソッドの処理をどのように書けば良いかを考えてみましょう.
貸し出し中である,ということはまだ返却されていないということです.
これはつまり,returnDateにまだ値が代入されていないことを表します.
つまり,returnDateがnullであれば,
貸し出し中であり,returnDateに値が代入されていれば返却されていることを表します.
また,貸し出された時に初めてHistory型のオブジェクトが生成されるために,
このオブジェクトについては,貸し出し前の状態を考える必要はありません.
実装できたかの確認は,次の練習問題3に示すHistoryCreator.javaを利用してください.
3. History型の情報を出力するメソッド.
8-3. 貸し出し履歴型の作成で作成したHistory
型に新たにprintメソッドを追加しましょう.
履歴の情報を画面に出力するためのメソッドです.
貸し出し中の出力例は次の通りです.
セロ弾きのゴーシュ, 宮沢賢治, 青空文庫, 1999 (貸し出し中) 玉田春昭(男; 38) Wed May 08 20:09:17 JST 2016 〜
返却済みの出力例は次の通りです.
セロ弾きのゴーシュ, 宮沢賢治, 青空文庫, 1999 (配架中) 玉田春昭(男; 38) Wed May 08 20:09:17 JST 2016 〜 Wed May 11 20:09:17 JST 2016
上記のように,最初にBook情報の出力,その後,履歴の状態を出力し,
ユーザの情報,最後に貸し出し期間を出力してください.
ただし,日付のフォーマットは上記と異なっていても構いません.
日付をより簡易な表記にしたい場合は,DateFormatを調べてください.
Historyにprintメソッドが定義できれば,
HistoryCreator.javaをダウンロードし,
History.javaと同じ場所に置いてください.
HistoryCreator.javaをコンパイル,実行すれば,
貸し出し中,配架中の2行が出力されるはずです.
これで実行結果を確認してください.
本日のまとめ
今日,学んだ内容は次の通りです.
- 型の作成方法(復習).
- 型の実体の方法(復習).
- 集合を扱う型(復習).
- 列挙子の取得方法.
- 列挙子の利用方法.