システム開発入門の第20週目では、図書館貸出システムを題材に、小規模なオブジェクト指向プログラミングの開発に取り組みました。
実習の流れは、UMLクラス図で役割を整理し、その設計をPythonコードへ移し、最後に貸出と返却の動作を確かめるというものです。
オブジェクト指向プログラミング(OOP)は、データとそのデータを扱う処理を、役割ごとのオブジェクトとして組み立てる考え方です。
クラスとインスタンスの基礎を、設計、実装、動作確認までつなげることが今回の課題です。
設計から動作確認までの流れ
- UMLクラス図で、必要なクラスと役割を整理する
- クラスごとにPythonファイルを分けて実装する
- テストコードで貸出と返却の状態変化を確認する
先に役割を分けておくと、コードを書き始める前に「どのクラスが何を知り、何を行うのか」を確認できます。
今回の図書館貸出システムでは、本、会員、図書館という三つの対象に分けました。
UMLクラス図で三つの役割を整理する
UMLクラス図は、クラスが持つデータと処理、クラス同士の関係を図で整理する表現です。
ここでいう属性はオブジェクトが保持するデータ、メソッドはオブジェクトが行う処理を指します。
| クラス | 主な属性 | 主なメソッド | 担当する役割 |
|---|---|---|---|
Book |
title、author、is_available |
lend()、return_book() |
本の情報と貸出可能な状態を持つ |
Member |
name、member_id、borrowed |
borrow(book)、give_back(book) |
会員情報と借りている本を管理する |
Library |
books |
find_book(title)、register_book(book) |
蔵書の登録と検索を受け持つ |
この表では、同じ処理を複数のクラスへ重ねていません。
たとえば貸出可能かどうかはBookが判断し、Memberはその結果を受けて借りた本の一覧を更新します。
クラス図をPythonコードへ対応させる
クラス図のクラス名はPythonのclass、属性は主に__init__で設定する値、メソッドはdefで定義する処理に対応します。
実習では、三つのクラスを別々のファイルに置きました。
book.py
class Book:
def __init__(self, title, author):
self.title = title
self.author = author
self.is_available = True
def lend(self):
if self.is_available:
self.is_available = False
return True
return False
def return_book(self):
self.is_available = True
Bookは、書名と著者名に加えて、貸出可能かどうかをis_availableで保持します。
lend()が成功すると状態をFalseへ変え、return_book()でTrueへ戻します。
member.py
from book import Book
class Member:
def __init__(self, name, member_id):
self.name = name
self.member_id = member_id
self.borrowed = []
def borrow(self, book: Book):
if book.lend():
self.borrowed.append(book)
return True
return False
def give_back(self, book: Book):
if book in self.borrowed:
book.return_book()
self.borrowed.remove(book)
return True
return False
MemberはBookのlend()を呼び出し、貸出に成功した本だけをborrowedへ追加します。
返却時は、その本がborrowedに含まれていることを確かめてから、本の状態と会員の一覧を更新します。
library.py
from book import Book
class Library:
def __init__(self):
self.books = []
def register_book(self, book: Book):
self.books.append(book)
def find_book(self, title: str):
return [b for b in self.books if b.title == title and b.is_available]
Libraryは蔵書一覧を持ち、登録と検索を担当します。
find_book()は、指定した書名と一致し、現在貸出可能な本だけを一覧で返します。
ファイルを役割ごとに分ける考え方は、Pythonのモジュール化とファイル分割の実習にもつながります。
分割しただけでは連携できないため、必要なクラスをimportして利用します。
assertで貸出と返却を確認する
assertは、指定した条件が成り立つかを確かめるために使います。
実習の後半では、図書館への本の登録、会員による貸出、返却後の状態までを一続きのコードで確認しました。
from book import Book
from member import Member
from library import Library
lib = Library()
b1 = Book("ハリーポッター", "J.K.ローリング")
lib.register_book(b1)
m = Member("田中", "M001")
assert m.borrow(b1) == True
assert b1.is_available == False
assert m.give_back(b1) == True
assert b1.is_available == True
print("テストOK!")
四つのassertは、次の順番で状態を確かめています。
- 会員が本を借りられたか
- 貸出後に本が貸出不可になったか
- 会員が本を返却できたか
- 返却後に本が再び貸出可能になったか
このテストで確認できるのは、用意した一連の条件で貸出と返却が期待どおりに動くことです。
テストが通ることは確認した条件の結果を示しますが、すべての入力や操作で不具合がないことまで意味するわけではありません。
設計と実装を往復して確かめる
今回の実習では、クラス図、Python実装、テストコードが別々の作業ではなく、同じ設計を異なる形で確かめる工程としてつながりました。
クラス図で役割が曖昧なら実装時に迷いが生じ、実装と図が合っているかは動作確認を通して見直せます。
次回は、ReferenceBookやMagazineを例にした継承によるクラス拡張と、コンソールメニューによる簡易UIの実装に進む予定です。
関連する基礎はPythonの継承とポリモーフィズムで確認できます。
より広いUML表現を学ぶ際は、クラス図とシーケンス図によるUML設計も参考になります。
この記事に関連する株式会社greedenの取り組み
設計、実装、テストをつなげるには、要件をクラスの役割へ落とし込み、動作を一つずつ確かめる工程が必要です。株式会社greedenは、要件定義から設計、実装、テスト、保守運用まで一気通貫でシステム開発を支援しています。
