C#でのオブジェクト指向学習サンプル5(レジ、商品の種類によってレシート表示を変えて欲しい)
お肉や本まで品揃えしているスーパーで、それぞれの種類に応じてレシートの表示を変える仕組みを考えてみましょう
前回までのサンプル
ベースとなる基本レジ
シンプルで、これはこれでいいと思いますが、さらに現実のレジに近づけてみましょう
レジ、マイナスの商品は買えないようにする
C#のプロパティの機能を採用して、堅牢なコードに変更します
これは、オブジェクト指向の3大要素と呼ばれるカプセル化にあたります
レジ、購入商品数を最初に入力しなくてもよくする
Listクラスの機能を採用して、要素数が実行中に変更可能な配列のように変更します
これにより最初に買い物する商品数の入力が不要になりました
レジ、購入はバーコード番号入力だけにする
Listクラスの機能を採用して、要素数が実行中に変更可能な配列のように変更します
これにより最初に買い物する商品数の入力が不要になりました
新たに実現したい機能
今のコードでは、レシートは商品名と値段だけの表示でしたが、生鮮食品では賞味期限を、本では著作者を表示するようにしてみましょう
入力は、単にバーコードを入力することにします
これらの機能はこれまでの知識でも頑張ればできますが、楽に実現するために、新しくC#オブジェクト指向の継承とポリモーフィズムの仕組みを使ってみます
商品の種類による実行結果
お客(山田さん)が買い物かごに買いたい商品を入れていきます
必要なものを入れたらレジで精算(レシート表示)します
最初に商品のバーコードを入力してもらっています
全ての買い物を終わったら単にエンターキーを押して買い物を終えたことにします
クラス図
イラスト
全体システム
商品クラスについての追加・更新
商品一覧には、商品が複数登録されています
今回は商品をお肉と本に分けてそれぞれの特徴についてはそれぞれの固有の属性として持つようにします
レシートにどのように表示するかを表したものです
種類別で表示を変更(呼び出されるメソッドが選択される)様子を示しています
UML図
C#の初学であれば、次のポイントに注意して確認してください
クラスはブロック内を2つの部分に分けて分析してみます
属性と振る舞いの2つです
クラスの形
2つの部分 | 他の一般的な呼ばれ方 | プログラム言語での呼び方 |
属性 | データ部分、名詞部分、情報、性質の情報、パラメータ(雰囲気で)、特徴、など | プロパティ、フィールド、など 静的型付け言語(JavaやC#など)の場合、 型名と名前で宣言されています |
振る舞い | 操作、動作、動きの部分、アクション、動詞部分、など | メソッド、関数、プロシジャーなどとも呼ばれています ブロック内の手続型のコードを記述します (手続型は、上から下に順次実行が基本。条件分岐や繰り返し処理など組み合わせます) |
クラス図のサンプル
サンプルクラスの読み方は、
「購入済み商品クラスで、属性としてバーコードを持っている、振る舞いとしてレシートを表示する」と考えます
コンストラクタとは、newキーワードで新しくレジ(1号機、2号機のあるスーパーを想定)を導入した当時、1回だけ実行される振る舞いと考えます
前回のクラス図
今回のクラス図
レジクラスは商品マスター(商品一覧)を同様に保持しています
Item型のオブジェクトを複数持っているというのは変わらないのでですが、実際には、お肉商品と本商品のクラスからオブジェクトが作られています
C#的に表現すると、基底クラスの宣言された変数には、派生クラスのオブジェクトの代入が可能といいます
シーケンス図
図中の表現
loop | ループ(繰り返し)処理を表します |
alt(Alternative) | 分岐処理を表します |
前回のシーケンス図
実際のサンプルコード中には、見つからなかった場合についての処理は実装されていません
今回のシーケンス図
基本の形は変えていますので注意してください
クラス名の英単語化等、日本語だけの表記を更新しています
コード
メソッドは、ブロック内を全て理解することを目的にせず、メソッド名から何の動作をさせようとしているのかを分析対象とします
PurchasedItem.cs(購入済み商品)
変更点はありません
お客が購入する商品のクラスになります(購入済み商品)
スーパーでの商品に貼ってあるラベルのイメージです
買い物かご(items)に登録する1つ1つの購入済み商品のクラスになります
買い物かごは、お客が保持しています(持っています)
実際購入する場合、バーコードをスキャナで読ませればOKなので、そのような状況に合わせてみました
属性
バーコードのみ
コンストラクタで、バーコードが代入できるようにしています
振る舞い
ありません
class PurchasedItem
{
public string barCode;
public PurchasedItem(string barCode)
{
this.barCode = barCode;
}
}
Item.cs(商品マスターの登録用の商品)
商品マスター(商品一覧)に登録する1つ1つの商品のクラスになります
商品マスターのデータの構造を定義しています
商品マスターは、レジが保持しています(持っています)
変更点は、メソッドにvirtual修飾子の追加になります
これは、派生クラスでメソッドを置き換えることができることを示しています
属性
バーコード、商品名と価格の3つで考えます
振る舞い
商品名と価格を表示するのみになります
// Itemクラス(設計図)
/// <summary>
/// スーパーの商品クラス
/// </summary>
class Item
{
// (フィールド)
// バーコード
public string barCode;
// 商品名
public string name;
// 価格
// このクラス外からはアクセスできないフィールド(private修飾子)
private int price;
// (プロパティ)public修飾子なのでクラス外からアクセスできる
public int Price
{
get
{
// 読み込まれた場合の処理
return price;
}
set
{
// 書き込まれた場合の処理
if (value < 0)
{
price = 0;
}
else
{
price = value;
}
}
}
// ------------------------------------
// (メソッド、関数、プロシジャー、ファンクション)
// 商品名と価格を表示する
public virtual void Show()
{
Console.WriteLine($"{name}の価格は{price}円です");
}
}
MeatItem.cs(お肉商品)
追加
Itemクラスを継承した、お肉商品クラスになります
Itemクラスのメンバーを全て網羅していて使うことができます
それにプラスして、このクラスのメンバーも使いことができます
メソッドにoverride修飾子が追加されています
これは、派生クラスでメソッドを置き換えることができることを示しています
属性
賞味期限のみです
振る舞い
商品名と価格を表示したのち、賞味期限も表示します
class MeatItem : Item
{
public string expiryDate;
public override void Show()
{
// 基底クラスのメソッド呼び出し
base.Show();
Console.WriteLine( $"賞味期限 {expiryDate}");
}
}
BookItem.cs(本商品)
追加
Itemクラスを継承した、本商品クラスになります
Itemクラスのメンバーを全て網羅していて使うことができます
それにプラスして、このクラスのメンバーも使いことができます
メソッドにoverride修飾子が追加されています
これは、派生クラスでメソッドを置き換えることができることを示しています
属性
著作者のみです
振る舞い
商品名と価格を表示したのち、著作者も表示します
class BookItem : Item
{
public string auther;
public override void Show()
{
// 基底クラスのメソッド呼び出し
base.Show();
Console.WriteLine($"著者 {auther}");
}
}
Customer.cs(お客)
変更点はありません
お客クラスになります
購入済み商品を複数持っていることを表しています
属性
お客様名と複数の購入済み商品の2つで考えます
振る舞い
インスタンス作成時、名前を登録すると購入するの2つになります
class Customer
{
public string name;
// カートの商品
public List<PurchasedItem> items;
public Customer(string name)
{
this.name = name;
}
public void Buy()
{
// PurchasedItemクラス(購入済み商品)からインスタンスを作成する(オブジェクトを作成)
// =>インスタンス化ともいいます
items = new List<PurchasedItem>();
while (true)
{
Console.Write("バーコード: ");
string barCode = Console.ReadLine();
if (barCode == "")
{
break;
}
items.Add(new PurchasedItem(barCode));
}
}
}
Reg.cs(レジスター)
レジクラスになります
属性
商品マスター(商品一覧)を持ちます
振る舞い
レシートを表示するのみになります
変更点は、次のようになります
商品を購入(newでオブジェクト作成)するときに、お肉商品か本商品かで作成用のクラスを分けています
ただし、レジでは、どちらも商品として処理(レシート表示)したいので代入する変数の型はItem型とします
これにより、繰り返し処理で、お肉か本かで表示処理を分岐する必要がなくなります
どちらのメソッドが処理されるかはC#オブジェクト指向のポリモーフィズムで対処されます
この処理を実現するため、このクラス内でコンソール表示していたところを基底クラスのメソッドを呼ぶように変更しました
class Reg
{
// 商品マスター(商品一覧)
List<Item> items = new List<Item>();
public Reg()
{
// コンストラクタで商品マスターに商品を初期登録しています
Item item1 = new MeatItem { name = "鶏肉", barCode = "1234", Price = 100,expiryDate = "20231010" };
Item item2 = new BookItem { name = "三四郎", barCode = "2345", Price = 200,auther = "夏目漱石" };
items.Add(item1);
items.Add(item2);
}
// レシートを表示する
public void PrintReceipt(Customer customer)
{
//customer.items[0].Price = -100;
Console.WriteLine($"{customer.name}さんのレシート");
Console.WriteLine("----------------------------");
// 合計金額を入れる変数を用意する
int totalPrice = 0;
// レシートの表示
foreach (var item in customer.items)
{
// LINQを使って、お客が持っている購入済み商品のバーコードと商品マスター(一覧)のバーコードの一致を探し、
// 結果をfindItem変数に代入しています
var findItem = items.FirstOrDefault(itemMaster => item.barCode == itemMaster.barCode);
// 商品名を表示する
findItem.Show();
totalPrice += findItem.Price;
}
Console.WriteLine("----------------------------");
// 合計を表示する
Console.WriteLine($"合計金額は{totalPrice}円です");
}
}
RegSystem.cs(レジのアプリをコントロール)
変更点はありません(他の人の場合もコメントで追加はしています)
レジのシステム全体をコントロールするクラスになります
属性
ありません
振る舞い
Runメソッドからコードが始まります
ここでは、お客様の山田さんの作成、買い物をする、レジの作成、レシートを表示するを行なっています
class RegSystem
{
public void Run()
{
// レジオブジェクトを作成
Reg reg1 = new Reg();
// 山田さんを作成
Customer customer1 = new Customer("山田");
customer1.Buy();
reg1.PrintReceipt(customer1);
/*
Customer customer2 = new Customer("森本");
Customer customer3 = new Customer("太田");
// 森本さんが先に購入
customer2.Buy();
// 森本さんがそのまま清算
reg1.PrintReceipt(customer2);
customer3.Buy();
reg1.PrintReceipt(customer3);
*/
}
}
Program.cs(エントリーポイント。このクラスのMainメソッドからプログラムは開始されます)
変更点はありません
C#でプログラムが実行される最初のメソッドになります
属性
ありません
振る舞い
トップレベルステートメントとしてコードを記述していますので、Programクラスに記述されているMainメソッドと同じブロックになります
ここでは、レジシステムの作成、レジの実行を行なっています
このコードが実行されると、RegSystemクラスのRunメソッドが実行されます
RegSystem regSystem = new RegSystem();
regSystem.Run();
おまけ
PlantUML記法から図を作成するネットのサービス(無料)
シーケンス図
@startuml
actor Customer
participant "RegSystem" as RegSys
participant "Reg" as Reg
participant "Customer" as Cust
participant "MeatItem" as Meat
participant "BookItem" as Book
participant "Item" as Itm
database "items" as Items
Customer -> RegSys: Run()
RegSys -> Reg: new Reg()
RegSys --> Cust: new Customer("山田")
Cust -> Cust: Buy()
loop 購入商品入力
Cust -> Cust: 商品バーコード入力
Cust -> Cust: Enterキー入力
end
Cust -> Reg: PrintReceipt(customer1)
Reg -> Items: 商品一覧取得
loop 購入商品のループ
Reg -> Items: 商品マスター検索
Items --> Reg: 商品情報
Reg -> Itm: Show()
alt 商品がBookItemの場合
Itm -> Book: Show()
else 商品がMeatItemの場合
Itm -> Meat: Show()
end
Reg --> Cust: 商品情報表示
end
Reg --> Cust: 合計金額表示
@enduml
クラス図
@startuml
class Program{
-Main()
}
class Customer {
+name: string
+items: List<PurchasedItem>
+Customer(name: string)
+Buy()
}
class Item {
+ barCode : string
+ name : string
- price : int
+ Price : int <<get>> <<set>>
+ <<virtual>> Show() : void
}
class MeatItem {
+ expiryDate : string
+ <<override>> Show() : void
}
class BookItem {
+ auther : string
+ <<override>> Show() : void
}
class PurchasedItem {
+barCode: string
+PurchasedItem(barCode: string)
}
class Reg {
-items: List<Item>
+Reg()
+PrintReceipt(customer: Customer)
}
class RegSystem {
+Run()
}
Customer --> PurchasedItem : has 1..* >
Reg --> Item : has 1..* >
Program --> RegSystem : creates >
RegSystem --> Customer : create >
RegSystem --> Reg : creates >
RegSystem --> Customer : uses >
RegSystem --> Reg : uses >
Item <|-- BookItem : < inheritance
Item <|-- MeatItem : < inheritance
@enduml
ディスカッション
コメント一覧
まだ、コメントがありません