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行が出力されるはずです.
これで実行結果を確認してください.
本日のまとめ
今日,学んだ内容は次の通りです.
- 型の作成方法(復習).
- 型の実体の方法(復習).
- 集合を扱う型(復習).
- 列挙子の取得方法.
- 列挙子の利用方法.