基礎から学ぶC#入門 90日コース | クラス・インスタンス・設計 - Day 38:クラス設計演習

Web APP 90日で身につけるC#
スポンサーリンク
スポンサーリンク

Day 38 のゴールと全体像

Day 38 のテーマは クラス設計演習 です。 ここまで学んできた「フィールド」「プロパティ」「コンストラクタ」「メソッド」「カプセル化」などを、 ひとつのクラスにギュッと詰め込んで、実際に設計してみる回になります。

題材は 商品クラス Product

  • Id
  • Name
  • Price
  • Stock
  • Category

という 5 つのメンバーを持つクラスを、 ステップバイステップで組み立てていきます。

ステップ 1:商品クラスのイメージを言葉で整理する

まずは、コードを書く前に「商品とは何か」を言葉で整理してみます。

商品クラス Product のイメージ:

  • データ
    • Id:商品を一意に識別する番号
    • Name:商品名
    • Price:価格(税抜きなど)
    • Stock:在庫数
    • Category:カテゴリ(文房具、食品、家電…など)
  • 処理(責務)
    • 在庫を増やす(入荷)
    • 在庫を減らす(販売)
    • 税込み価格を計算する
    • 商品情報を表示する

この「データ」と「処理」を、 ひとつのクラスにまとめていくのが、今日のゴールです。

ステップ 2:まずは素朴な Product クラスを書いてみる

最初は、あまり難しく考えずに「形」を作ってみます。

using System;

class Product
{
    // 商品ID
    public int Id { get; set; }

    // 商品名
    public string Name { get; set; }

    // 価格(税抜き)
    public int Price { get; set; }

    // 在庫数
    public int Stock { get; set; }

    // カテゴリ
    public string Category { get; set; }

    // 商品情報を表示するメソッド
    public void PrintInfo()
    {
        Console.WriteLine($"ID: {Id}, 名前: {Name}, 価格: {Price} 円, 在庫: {Stock}, カテゴリ: {Category}");
    }
}
C#

使う側のコードは、こんな感じになります。

class Program
{
    static void Main(string[] args)
    {
        Product p = new Product
        {
            Id = 1,
            Name = "ノート",
            Price = 100,
            Stock = 50,
            Category = "文房具"
        };

        p.PrintInfo();

        Console.ReadLine();
    }
}
C#

この段階では、

  • 5 つのプロパティを持つ
  • 情報を表示するメソッドを 1 つ持つ

という、シンプルな形になっています。

ここから、「設計としてもう少しちゃんとした形」に育てていきます。

ステップ 3:在庫操作のメソッドを追加する

商品クラスの責務として、

  • 在庫を増やす(入荷)
  • 在庫を減らす(販売)

という処理を持たせてみます。

class Product
{
    public int Id { get; set; }
    public string Name { get; set; }
    public int Price { get; set; }

    // 在庫数
    public int Stock { get; private set; } // 外から直接書き換えさせない

    public string Category { get; set; }

    // コンストラクタで初期値を設定
    public Product(int id, string name, int price, int initialStock, string category)
    {
        Id = id;
        Name = name;
        Price = price;
        Category = category;

        if (initialStock < 0)
        {
            Console.WriteLine("初期在庫がマイナスのため、0 に補正します。");
            Stock = 0;
        }
        else
        {
            Stock = initialStock;
        }
    }

    // 在庫を増やす(入荷)
    public void AddStock(int amount)
    {
        if (amount <= 0)
        {
            Console.WriteLine("入荷数は正の値でなければなりません。");
            return;
        }

        Stock += amount;
        Console.WriteLine($"{Name} を {amount} 個入荷しました。現在の在庫: {Stock} 個");
    }

    // 在庫を減らす(販売)
    public void ReduceStock(int amount)
    {
        if (amount <= 0)
        {
            Console.WriteLine("販売数は正の値でなければなりません。");
            return;
        }

        if (amount > Stock)
        {
            Console.WriteLine($"{Name} の在庫が不足しています。現在の在庫: {Stock} 個, 販売数: {amount} 個");
            return;
        }

        Stock -= amount;
        Console.WriteLine($"{Name} を {amount} 個販売しました。現在の在庫: {Stock} 個");
    }

    public void PrintInfo()
    {
        Console.WriteLine($"ID: {Id}, 名前: {Name}, 価格: {Price} 円, 在庫: {Stock}, カテゴリ: {Category}");
    }
}
C#

ここでのポイントは、

  • Stocksetprivate にして、外から直接書き換えられないようにしている
  • 在庫の変更は、必ず AddStock / ReduceStock を通る
  • メソッドの中で「正の値か」「在庫不足でないか」をチェックしている

というところです。

重要ポイント:

  • 在庫という「壊れると困るデータ」は、 カプセル化(private set)+メソッド経由の変更で守るのが定番パターンです。

ステップ 4:税込価格を計算するメソッドを追加する

商品クラスの責務として、

「税込価格を計算する」

という処理も持たせてみます。

class Product
{
    // ここまでのメンバーは省略(前のコードと同じ)

    // 税込み価格を計算するメソッド
    public int GetPriceWithTax(double taxRate = 0.10)
    {
        // 税率を掛けて、整数に丸める
        return (int)(Price * (1 + taxRate));
    }

    public void PrintInfoWithTax()
    {
        int priceWithTax = GetPriceWithTax();
        Console.WriteLine($"ID: {Id}, 名前: {Name}, 価格(税抜): {Price} 円, 価格(税込): {priceWithTax} 円, 在庫: {Stock}, カテゴリ: {Category}");
    }
}
C#

使う側のコードは、こんな感じになります。

class Program
{
    static void Main(string[] args)
    {
        Product p = new Product(id: 1, name: "ノート", price: 100, initialStock: 50, category: "文房具");

        p.PrintInfo();          // 税抜き価格で表示
        p.PrintInfoWithTax();   // 税込み価格も含めて表示

        Console.ReadLine();
    }
}
C#

重要ポイント:

  • 「商品に関する計算」は、商品クラスのメソッドとして持たせると、 コードの見通しがよくなります。
  • 「このクラスは何を知っていて、何ができるべきか?」を考えると、 メソッドの形が自然と見えてきます。

ステップ 5:Category をもう少し「型安全」にする

今は Categorystring で持っていますが、 カテゴリが決まった種類しかないなら、列挙型(enum) を使うと、 より「設計っぽい」形になります。

// 商品カテゴリの列挙型
enum ProductCategory
{
    Stationery, // 文房具
    Food,       // 食品
    Electronics // 家電
}

class Product
{
    public int Id { get; }
    public string Name { get; }
    public int Price { get; private set; }
    public int Stock { get; private set; }
    public ProductCategory Category { get; }

    public Product(int id, string name, int price, int initialStock, ProductCategory category)
    {
        Id = id;
        Name = name;
        Category = category;

        // 価格の整合性チェック
        if (price < 0)
        {
            Console.WriteLine("価格がマイナスのため、0 に補正します。");
            Price = 0;
        }
        else
        {
            Price = price;
        }

        // 在庫の整合性チェック
        if (initialStock < 0)
        {
            Console.WriteLine("初期在庫がマイナスのため、0 に補正します。");
            Stock = 0;
        }
        else
        {
            Stock = initialStock;
        }
    }

    public void AddStock(int amount)
    {
        if (amount <= 0)
        {
            Console.WriteLine("入荷数は正の値でなければなりません。");
            return;
        }

        Stock += amount;
    }

    public void ReduceStock(int amount)
    {
        if (amount <= 0)
        {
            Console.WriteLine("販売数は正の値でなければなりません。");
            return;
        }

        if (amount > Stock)
        {
            Console.WriteLine($"{Name} の在庫が不足しています。現在の在庫: {Stock} 個, 販売数: {amount} 個");
            return;
        }

        Stock -= amount;
    }

    public int GetPriceWithTax(double taxRate = 0.10)
    {
        return (int)(Price * (1 + taxRate));
    }

    public void PrintInfo()
    {
        Console.WriteLine($"ID: {Id}, 名前: {Name}, 価格(税抜): {Price} 円, 在庫: {Stock}, カテゴリ: {Category}");
    }
}
C#

使う側のコードは、こうなります。

class Program
{
    static void Main(string[] args)
    {
        Product notebook = new Product(
            id: 1,
            name: "ノート",
            price: 100,
            initialStock: 50,
            category: ProductCategory.Stationery);

        notebook.PrintInfo();
        Console.WriteLine($"税込価格: {notebook.GetPriceWithTax()} 円");

        notebook.AddStock(20);
        notebook.ReduceStock(10);

        notebook.PrintInfo();

        Console.ReadLine();
    }
}
C#

重要ポイント:

  • Categoryenum にすることで、 「ありえないカテゴリ名(タイポなど)」を防げます。
  • 型で表現できるルールは、なるべく型に任せると、 バグが減っていきます。

ステップ 6:設計の観点から Product クラスを眺めてみる

ここまでの Product クラスを、設計の観点から整理してみます。

  • カプセル化
    • PriceStocksetprivate にして、 外から勝手に書き換えられないようにしている
    • 変更はコンストラクタやメソッドを通して行う
  • データの整合性
    • 価格・在庫がマイナスにならないようにチェックしている
    • 在庫不足の販売を防いでいる
  • 責務の整理
    • 商品は「自分の情報を表示する」「自分の税込価格を計算する」「自分の在庫を管理する」 という仕事を持っている
  • 型の活用
    • カテゴリを enum にすることで、 「カテゴリの種類」を型で表現している

こうして眺めてみると、 Product クラスは単なる「データの入れ物」ではなく、

「商品という概念をコードの中に持ち込んだ、小さなオブジェクト」

になっていることが分かります。

Day 38 用の練習アイデア

今日の Product クラスをベースに、 次のような練習をしてみると、クラス設計の感覚がぐっと育ちます。

  • 練習1:割引機能を追加する
    • ApplyDiscount(int percent) メソッドを作り、 価格を割引する処理を追加してみる
    • 割引率が 0〜100 の範囲に収まるようにチェックする
  • 練習2:在庫ゼロ判定メソッドを追加する
    • IsOutOfStock() メソッドを作り、 在庫が 0 かどうかを返すようにしてみる
  • 練習3:複数の商品を List<Product> で管理する
    • 商品をいくつか作って List<Product> に入れ、 在庫が少ない商品だけ表示する処理を書いてみる

こうした練習を通して、

「クラスは、現実のものや概念をコードの中に持ち込むための器」

という感覚が、少しずつ自分の中に根付いていきます。

Day 38 のまとめ

Day 38 では、

  • 商品クラス Product を題材にして
  • データ(Id, Name, Price, Stock, Category)と
  • 処理(在庫管理・税込価格計算・情報表示)を
  • ひとつのクラスにまとめる設計を体験しました。

これまで学んできた、

  • プロパティ
  • コンストラクタ
  • メソッド
  • カプセル化(private set
  • データの整合性
  • 責務の整理

といった要素が、 ひとつのクラスの中で自然につながっているのを感じてもらえたらうれしいです。

ぜひ、

  • 自分の身近なもの(ユーザー、注文、タスク、イベントなど)を題材にして
  • 同じように「データ」と「処理」を整理し
  • クラスとして設計してみる

というチャレンジを通して、 クラス設計を 「難しい理論」ではなく、 自分の頭の中のイメージをコードにする楽しい作業として、 少しずつ体に馴染ませていってください。

タイトルとURLをコピーしました