C#でのオブジェクト指向学習サンプル7(レジ、賞味期限があるグループは限定の表示をしたい)

魚やお肉などをグループ化して、生鮮品としたい
賞味期限が切れていると期限切れ表示する特別な表示をしたい

前回までのサンプル

ベースとなる基本レジ

シンプルで、これはこれでいいと思いますが、さらに現実のレジに近づけてみましょう

レジ、マイナスの商品は買えないようにする

C#のプロパティの機能を採用して、堅牢なコードに変更します
これは、オブジェクト指向の3大要素と呼ばれるカプセル化にあたります

レジ、購入商品数を最初に入力しなくてもよくする

Listクラスの機能を採用して、要素数が実行中に変更可能な配列のように変更します
これにより最初に買い物する商品数の入力が不要になりました

レジ、購入はバーコード番号入力だけにする

商品一覧をレジクラスに持たせることで、お客はバーコードだけをレジに通すと精算ができるようにしました

レジ、商品の種類によってレシート表示を変えて欲しい

継承、ポリモーフィズムの機能を使って、まとめて管理しやすく、また変更もしやすいようにしましたね

レジ、商品情報は、必ずどの種類かに限定してほしいし、必ず限定の表示をしてほしい

抽象クラスを使った継承ポリモーフィズムの機能を使って、制約も持てるようにしました

新たに実現したい機能

現在、商品は、お肉グループと本グループに分類されています
C#的には、お肉クラスと本クラスを商品クラスから継承することで実現しています

つまり、お肉も本も商品である(is aの関係)で構成されています
その関係でポリモーフィズムの機能を使って管理をしやすくしています

今回は、賞味期限があるグループで分けるようにすれば良さそうですが、いくつかの問題があります

  • 派生クラスでは、継承元は1つだけ
  • is aの関係ではない
    たとえば、お肉は「賞味期限(がある)」であると考えるのは無理があります

これらの制約をクリアするためにインターフェースを使います

インターフェースを適用すると次のようになります

  • 派生クラスでは、継承元は1つだけだが、インターフェースは複数実装できる
  • is aの関係ではなくてもOK

C#のバージョン8から、それまでダメだったメソッドの実装(ブロック内にコードが記述できる)ができるようになりました

実行結果

前回の商品の種類による実行結果を全く同じです(なので、結果もコピペしています)

お客(山田さん)が買い物かごに買いたい商品を入れていきます
必要なものを入れたらレジで精算(レシート表示)します

最初に商品のバーコードを入力してもらっています
全ての買い物を終わったら単にエンターキーを押して買い物を終えたことにします

賞味期限が切れているとその旨が表示されます

クラス図

イラスト

全体システム

商品クラスについての追加・更新

商品一覧には、商品が複数登録されています
今回はお肉を賞味期限があるグループに所属させています
このグループに所属すると賞味期限を必ず実装しなければなりません

レシートにどのように表示するかを表したものです
種類別で表示を変更(呼び出されるメソッドが選択される)様子を示しています

UML図

C#の初学であれば、次のポイントに注意して確認してください

クラスはブロック内を2つの部分に分けて分析してみます

属性振る舞いの2つです

クラスの形

2つの部分他の一般的な呼ばれ方プログラム言語での呼び方
属性データ部分、名詞部分、情報、性質の情報、パラメータ(雰囲気で)、特徴、などプロパティ、フィールド、など
静的型付け言語(JavaやC#など)の場合、
型名と名前で宣言されています
振る舞い操作、動作、動きの部分、アクション、動詞部分、などメソッド、関数、プロシジャーなどとも呼ばれています
ブロック内の手続型のコードを記述します
(手続型は、上から下に順次実行が基本。条件分岐や繰り返し処理など組み合わせます)

クラス図のサンプル

サンプルクラスの読み方は、
「購入済み商品クラスで、属性としてバーコードを持っている、振る舞いとしてレシートを表示する」と考えます
コンストラクタとは、newキーワードで新しくレジ(1号機、2号機のあるスーパーを想定)を導入した当時、1回だけ実行される振る舞いと考えます

前回のクラス図

今回の変更箇所を取り込んだ最終形を示しています
抽象クラス(抽象的)なので、これからインスタンスを作成できない機能?があります
抽象メソッド(抽象的)なので、ここにメソッドのブロックは書けない機能?があります
代わりに派生クラスに絶対記述するように強制します

今回のクラス図

シーケンス図

変更点はありません

図中の表現

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つの商品のクラスになります
商品マスターのデータの構造を定義しています
商品マスターは、レジが保持しています(持っています)

属性

バーコード商品名価格の3つで考えます

振る舞い

商品名と価格を表示するのと派生クラスに種類ごとの表示を強制する抽象メソッドになります

// Itemクラス(設計図)
/// <summary>
/// スーパーの商品クラス
/// </summary>
abstract 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 void ShowBaseInfo()
    {
        Console.WriteLine($"{name}の価格は{price}円です");
    }

    // 派生クラスにShowメソッドの実装を強制する
    public abstract void Show();
}

IPerishable.cs(賞味期限がある)

追加

抽象プロパティ

賞味期限をこのインターフェースを実装したクラスに強制します
賞味期限はDateTime型とします(賞味期限切れの引き算ができるようにするため)
public修飾子は付けませんが、実装したクラスではpublic修飾子を矯正されます

デフォルトメソッド

賞味期限が切れているかのチェックメソッド
このインターフェースを実装したクラスでアクセスすることができます

/// <summary>
/// 賞味期限がある
/// </summary>
interface IPerishable
{
    DateTime ExpiryDate { get; set; }

    void CheckExpiryDate()
    {
        if (ExpiryDate < DateTime.Now)
        {
            Console.WriteLine("賞味期限が切れています");
        }
    }
}

MeatItem.cs(お肉商品)

賞味期限があるグループに登録します(インターフェースを実装)

Itemクラスを継承した、お肉商品クラスになります
Itemクラスのメンバーを全て網羅していて使うことができます
それにプラスして、このクラスのメンバーも使いことができます

メソッドにoverride修飾子は必要です
これは、派生クラスでメソッドを置き換えることができることを示しています

IPerishableインターフェース(賞味期限がある)を実装します

属性

賞味期限のみです
これは、IPerishableインターフェースで実装を強制するように宣言されています

振る舞い

賞味期限を表示します

商品名価格を表示したのち、賞味期限も表示します


class MeatItem : Item, IPerishable
{
    public DateTime ExpiryDate { get; set; }

    public override void Show()
    {
        // 基底クラスのメソッド呼び出し
        ShowBaseInfo();
        Console.WriteLine($"賞味期限 {ExpiryDate}");
    }
}

基底クラスで抽象メソッドが定義されているため、派生クラスでのShowメソッドの実装は強制になります

インターフェースでプロパティが定義されているため、実装クラスでのExpiryDateプロパティは強制になります

BookItem.cs(本商品)

変更点はありません

Itemクラスを継承した、本商品クラスになります
Itemクラスのメンバーを全て網羅していて使うことができます
それにプラスして、このクラスのメンバーも使いことができます

メソッドにoverride修飾子が追加されています
これは、派生クラスでメソッドを置き換えることができることを示しています

属性

著作者のみです

振る舞い

商品名価格を表示したのち、著作者も表示します

class BookItem : Item
{
    public string auther;

    public override void Show()
    {
        // 基底クラスのメソッドを呼び出す
        ShowBaseInfo();
        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(レジスター)

賞味期限がDataTime型に変更されたことに伴い、処置値の代入を変更します
インターフェースが実装されているオブジェクトの場合、メソッドが強制されているはずなので、実行

レジクラスになります

属性

商品マスター(商品一覧)を持ちます

振る舞い

レシートを表示するのみになります

class Reg
{
    // 商品マスター(商品一覧)
    List<Item> items = new List<Item>();

    public Reg()
    {
        // コンストラクタで商品マスターに商品を初期登録しています
        Item item1 = new MeatItem { name = "鶏肉", barCode = "1234", Price = 100, ExpiryDate = new DateTime(2023, 5, 10) };
        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();

            // Ipershableインターフェースを実装していれば キャストしてperishableItem変数に代入
            if (findItem is IPerishable perishableItem)
            {
                perishableItem.CheckExpiryDate();
            }

            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 <<get>> <<set>>
    + <<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)
}

interface IPerishable {
    +ExpiryDate : DateTime <<get>> <<set>>
    +CheckExpiryDate() : void
}

class RegSystem {
    +Run()
}

IPerishable <|-- MeatItem : < implementation

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