XP (エクストリーム プログラミング)


eXtreme Training

「『リファクタリング』より RentalVideo の演習」(C# + NUnit 版)

eXtreme Training「『リファクタリング』より RentalVideo の演習」(C# + NUnit 版)


ここでは,書籍『リファクタリング ―プログラミングの体質改善テクニック』の中の例題『RentalVideo』(Java 言語による) の C# + NUnit 移植版をご紹介します.


○ 補足 1:
"eXtreme Training" は,Y.Terada 氏が考案したトレーニング方法です.
武芸やスポーツ等と同じように,『型』を反復練習することで,TDD (テスト駆動開発) 等を効果的に習得しようとするものです.

オブジェクト指向や TDD 等によるプログラミングを習得しよう,或るいは,収得させようととしても,初めは中々難しくうまく行かないことがあります.例えば,リファクタリングの書籍などを読み,自分なりに理解をして,さて実際のプログラミングでやってみようとすると,どうしたら良いか判らない.身に付いていない訳です.

そんなとき,先ずは,習うより慣れろでプログラミングの手本となる流れを体験してみる.そして体が覚えるまでそれを繰り返す.それによって身に付けてしまおう,というのが "eXtreme Training" です.

詳しくは,Y.Terada 氏のサイト内の『エクストリーム・トレーニング』 <http://www.geocities.jp/u_1roh/columns/training.html> をご参照ください.
○ 補足 2:
NUnit は,JUnit 等と同様のユニットテスト用のツールです.詳しくは,以下のサイトを参照ください.

参考になるサイト:

○ 補足 3:
リファクタリング (refactoring) は,ソフトウェアの外部的振る舞いを保ったままで内部の構造を改善していく作業のことで,現存コードを効率良く安全に改良することが出来ます.XP のプラクティスの一つにもなっています.

参考になる書籍・サイト:


テスト成功
テスト失敗
コンパイルエラー
凡例:

クラス作成


RentalVideoTest.cs (新規作成)

using System;
using NUnit.Framework;

[TestFixture]
public class TestMovie
{
    [Test]
    public void TestCreateMovies()
    {
        Movie movie1 = new Movie("七人の侍"        , Movie.Kind.REGULAR    );
        Assert.AreEqual("七人の侍"        , movie1.Title);
        Assert.AreEqual(Movie.Kind.REGULAR    , movie1.PriceCode);

        Movie movie2 = new Movie("ラスト サムライ" , Movie.Kind.NEW_RELEASE);
        Assert.AreEqual("ラスト サムライ" , movie2.Title);
        Assert.AreEqual(Movie.Kind.NEW_RELEASE, movie2.PriceCode);

        Movie movie3 = new Movie("フランダースの犬", Movie.Kind.CHILDRENS  );
        Assert.AreEqual("フランダースの犬", movie3.Title);
        Assert.AreEqual(Movie.Kind.CHILDRENS  , movie3.PriceCode);
    }
}

[TestFixture]
public class TestRental
{
    [Test]
    public void TestCreateRentals()
    {
        Rental rental1 = new Rental(new Movie("E.T."                             , Movie.Kind.REGULAR    ), 8);
        Assert.AreEqual("E.T."                             , rental1.GetMovie().Title);
        Assert.AreEqual(Movie.Kind.REGULAR    , rental1.GetMovie().PriceCode);
        Assert.AreEqual(8, rental1.DaysRented);
        Rental rental2 = new Rental(new Movie("ロード・オブ・ザ・リング 王の帰還", Movie.Kind.NEW_RELEASE), 3);
        Assert.AreEqual("ロード・オブ・ザ・リング 王の帰還", rental2.GetMovie().Title);
        Assert.AreEqual(Movie.Kind.NEW_RELEASE, rental2.GetMovie().PriceCode);
        Assert.AreEqual(3, rental2.DaysRented);
        Rental rental3 = new Rental(new Movie("トイ・ストーリー2"                , Movie.Kind.CHILDRENS  ), 2);
        Assert.AreEqual("トイ・ストーリー2"                , rental3.GetMovie().Title);
        Assert.AreEqual(Movie.Kind.CHILDRENS  , rental3.GetMovie().PriceCode);
        Assert.AreEqual(2, rental3.DaysRented);
    }
}

[TestFixture]
public class TestCustomer
{
    [Test]
    public void TestCreateCustomer()
    {
        Customer customer = new Customer("福井 太郎");
        Assert.AreEqual("福井 太郎", customer.Name);
    }

    [Test]
    public void TestCustomerStatement()
    {
        Customer customer = new Customer("福井 太郎");
        customer.Add(new Rental(new Movie("マトリックス"                   , Movie.Kind.REGULAR    ), 8));
        customer.Add(new Rental(new Movie("マトリックス リボリューションズ", Movie.Kind.NEW_RELEASE), 2));
        customer.Add(new Rental(new Movie("ファインディング ニモ"          , Movie.Kind.CHILDRENS  ), 4));
        string statement = customer.Statement();
        Assert.AreEqual("レンタル記録: 福井 太郎\n\tマトリックス\t1100円\n\tマトリックス リボリューションズ\t600円\n\tファインディング ニモ\t300円\n総額 2000円\n今回の獲得レンタル ポイントは 4 ポイントです.", statement);
    }
}

RentalVideo.cs (新規作成)

using System;
using System.Collections;

class Movie
{
    public enum Kind
    {
        REGULAR    ,
        NEW_RELEASE,
        CHILDRENS
    }

    private string title;
    private Kind   priceCode;

    public Movie(string title, Kind priceCode)
    {
        this.title      = title;
        this.priceCode  = priceCode;
    }

    public Kind PriceCode
    {
        get { return priceCode; }
        set { priceCode = value; }
    }

    public string Title
    {
        get { return title; }
    }
}

class Rental
{
    private Movie   movie;
    private int     daysRented;

    public Rental(Movie movie, int daysRented)
    {
        this.movie      = movie;
        this.daysRented = daysRented;
    }

    public int DaysRented
    {
        get { return daysRented; }
    }

    public Movie GetMovie()
    { return movie; }
}

class Customer
{
    private string      name;
    private ArrayList   rentals = new ArrayList();

    public Customer(string name)
    {
        this.name = name;
    }

    public string Name
    {
        get { return name; }
    }

    public void Add(Rental rental)
    {
        rentals.Add(rental);
    }

    // レシート生成
    public string Statement()
    {
        int     totalAmount             = 0;
        int     frequentRenterPoints    = 0;
        string  result                  = "レンタル記録: " + Name + "\n";

        foreach (Rental each in rentals)
        {
            int thisAmount  = 0;

            // 一行ごとに金額を計算
            switch (each.GetMovie().PriceCode)
            {
                case Movie.Kind.REGULAR    :
                    thisAmount += 200;
                    if (each.DaysRented > 2)
                        thisAmount += (each.DaysRented - 2) * 150;
                    break;

                case Movie.Kind.NEW_RELEASE:
                    thisAmount += each.DaysRented * 300;
                    break;

                case Movie.Kind.CHILDRENS  :
                    thisAmount += 150;
                    if (each.DaysRented > 3)
                        thisAmount += (each.DaysRented - 3) * 150;
                    break;
            }

            // レンタル ポイントを加算
            frequentRenterPoints++;
            // 新作を二日以上借りた場合はボーナス ポイント
            if ((each.GetMovie().PriceCode == Movie.Kind.NEW_RELEASE) && each.DaysRented > 1)
                frequentRenterPoints++;

            // この貸し出しに関する数値の表示
            result      += "\t" + each.GetMovie().Title + "\t" + thisAmount + "円\n";

            totalAmount += thisAmount;
        }
        // フッタ部分の追加
        result += "総額 " + totalAmount + "円\n";
        result += "今回の獲得レンタル ポイントは " + frequentRenterPoints + " ポイントです.";

        return result;
    }
}

○ 補足 4:
リファクタリング では,新たなバグを作り出すことなく設計の改良を行うことを目指します.
以下では,コンパイル エラーの出ている時間やテストに失敗している時間を,なるべく長時間作らないように進めて行きます.
○ 補足 5:
この例題は,レンタルビデオ屋のシステムです.
将来的に以下の変更が予定されています.


「メソッドの抽出」 - I - 1


RentalVideo.cs

class Customer
{
    …

    // レシート生成
    public string Statement()
    {
        int     totalAmount             = 0;
        int     frequentRenterPoints    = 0;
        string  result                  = "レンタル記録: " + Name + "\n";

        foreach (Rental each in rentals)
        {
            int thisAmount  = 0;

            // 一行ごとに金額を計算
            switch (each.GetMovie().PriceCode)
            {
                case Movie.Kind.REGULAR    :
                    thisAmount += 200;
                    if (each.DaysRented > 2)
                        thisAmount += (each.DaysRented - 2) * 150;
                    break;

                case Movie.Kind.NEW_RELEASE:
                    thisAmount += each.DaysRented * 300;
                    break;

                case Movie.Kind.CHILDRENS  :
                    thisAmount += 150;
                    if (each.DaysRented > 3)
                        thisAmount += (each.DaysRented - 3) * 150;
                    break;
            }

            // レンタル ポイントを加算
            frequentRenterPoints++;
            // 新作を二日以上借りた場合はボーナス ポイント
            if ((each.GetMovie().PriceCode == Movie.Kind.NEW_RELEASE) && each.DaysRented > 1)
                frequentRenterPoints++;

            // この貸し出しに関する数値の表示
            result      += "\t" + each.GetMovie().Title + "\t" + thisAmount + "円\n";

            totalAmount += thisAmount;
        }
        // フッタ部分の追加
        result += "総額 " + totalAmount + "円\n";
        result += "今回の獲得レンタル ポイントは " + frequentRenterPoints + " ポイントです.";

        return result;
    }

    private int AmountFor(Rental each)
    {
        int thisAmount = 0;

        switch (each.GetMovie().PriceCode)
        {
            case Movie.Kind.REGULAR    :
                thisAmount += 200;
                if (each.DaysRented > 2)
                    thisAmount += (each.DaysRented - 2) * 150;
                break;

            case Movie.Kind.NEW_RELEASE:
                thisAmount += each.DaysRented * 300;
                break;

            case Movie.Kind.CHILDRENS  :
                thisAmount += 150;
                if (each.DaysRented > 3)
                    thisAmount += (each.DaysRented - 3) * 150;
                break;
        }
        return thisAmount;
    }
}

「メソッドの抽出」 - I - 2


RentalVideo.cs

class Customer
{
    …

    // レシート生成
    public string Statement()
    {
        int     totalAmount             = 0;
        int     frequentRenterPoints    = 0;
        string  result                  = "レンタル記録: " + Name + "\n";

        foreach (Rental each in rentals)
        {
            int thisAmount  = 0AmountFor(each);

            // 一行ごとに金額を計算
            switch (each.GetMovie().PriceCode)
            {
                case Movie.Kind.REGULAR    :
                    thisAmount += 200;
                    if (each.DaysRented > 2)
                        thisAmount += (each.DaysRented - 2) * 150;
                    break;

                case Movie.Kind.NEW_RELEASE:
                    thisAmount += each.DaysRented * 300;
                    break;

                case Movie.Kind.CHILDRENS  :
                    thisAmount += 150;
                    if (each.DaysRented > 3)
                        thisAmount += (each.DaysRented - 3) * 150;
                    break;
            }

            // レンタル ポイントを加算
            frequentRenterPoints++;
            // 新作を二日以上借りた場合はボーナス ポイント
            if ((each.GetMovie().PriceCode == Movie.Kind.NEW_RELEASE) && each.DaysRented > 1)
                frequentRenterPoints++;

            // この貸し出しに関する数値の表示
            result      += "\t" + each.GetMovie().Title + "\t" + thisAmount + "円\n";

            totalAmount += thisAmount;
        }
        // フッタ部分の追加
        result += "総額 " + totalAmount + "円\n";
        result += "今回の獲得レンタル ポイントは " + frequentRenterPoints + " ポイントです.";

        return result;
    }

    …
}

「メソッドの抽出」 - I - 3 (変数名の変更)


RentalVideo.cs

    private int AmountFor(Rental eachaRental)
    {
        int thisAmountresult = 0;

        switch (eachaRental.GetMovie().PriceCode)
        {
            case Movie.Kind.REGULAR    :
                thisAmountresult += 200;
                if (eachaRental.DaysRented > 2)
                    thisAmountresult += (eachaRental.DaysRented - 2) * 150;
                break;

            case Movie.Kind.NEW_RELEASE:
                thisAmountresult += eachaRental.DaysRented * 300;
                break;

            case Movie.Kind.CHILDRENS  :
                thisAmountresult += 150;
                if (eachaRental.DaysRented > 3)
                    thisAmountresult += (eachaRental.DaysRented - 3) * 150;
                break;
        }
        return thisAmountresult;
    }

「メソッドの移動」 - I - 1


RentalVideo.cs

class Rental
{
    …

    public int GetCharge()
    {
        int result = 0;

        switch (aRental.GetMovie().PriceCode)
        {
            case Movie.Kind.REGULAR    :
                result += 200;
                if (aRental.DaysRented > 2)
                    result += (aRental.DaysRented - 2) * 150;
                break;

            case Movie.Kind.NEW_RELEASE:
                result += aRental.DaysRented * 300;
                break;

            case Movie.Kind.CHILDRENS  :
                result += 150;
                if (aRental.DaysRented > 3)
                    result += (aRental.DaysRented - 3) * 150;
                break;
        }
        return result;
    }
}

class Customer
{
    …

    private int AmountFor(Rental aRental)
    {
        int result = 0;

        switch (aRental.GetMovie().PriceCode)
        {
            case Movie.Kind.REGULAR    :
                result += 200;
                if (aRental.DaysRented > 2)
                    result += (aRental.DaysRented - 2) * 150;
                break;

            case Movie.Kind.NEW_RELEASE:
                result += aRental.DaysRented * 300;
                break;

            case Movie.Kind.CHILDRENS  :
                result += 150;
                if (aRental.DaysRented > 3)
                    result += (aRental.DaysRented - 3) * 150;
                break;
        }
        return result;
    }
}

「メソッドの移動」 - I - 2


RentalVideo.cs

class Rental
{
    …

    public int GetCharge()
    {
        int result = 0;

        switch (aRental.GetMovie().PriceCode)
        {
            case Movie.Kind.REGULAR    :
                result += 200;
                if (aRental.DaysRented > 2)
                    result += (aRental.DaysRented - 2) * 150;
                break;

            case Movie.Kind.NEW_RELEASE:
                result += aRental.DaysRented * 300;
                break;

            case Movie.Kind.CHILDRENS  :
                result += 150;
                if (aRental.DaysRented > 3)
                    result += (aRental.DaysRented - 3) * 150;
                break;
        }
        return result;
    }
}

「メソッドの移動」 - I - 3


RentalVideo.cs

class Customer
{
    …

    private int AmountFor(Rental aRental)
    {
        int result = 0;

        switch (aRental.GetMovie().PriceCode)
        {
            case Movie.Kind.REGULAR    :
                result += 200;
                if (aRental.DaysRented > 2)
                    result += (aRental.DaysRented - 2) * 150;
                break;

            case Movie.Kind.NEW_RELEASE:
                result += aRental.DaysRented * 300;
                break;

            case Movie.Kind.CHILDRENS  :
                result += 150;
                if (aRental.DaysRented > 3)
                    result += (aRental.DaysRented - 3) * 150;
                break;
        }
        return result;
        return aRental.GetCharge();
    }
}

「メソッドの移動」 - I - 4


RentalVideo.cs

class Customer
{
    …

    // レシート生成
    public string Statement()
    {
        int     totalAmount             = 0;
        int     frequentRenterPoints    = 0;
        string  result                  = "レンタル記録: " + Name + "\n";

        foreach (Rental each in rentals)
        {
            int thisAmount  = AmountFor(each)each.GetCharge();

            // レンタル ポイントを加算
            frequentRenterPoints++;
            // 新作を二日以上借りた場合はボーナス ポイント
            if ((each.GetMovie().PriceCode == Movie.Kind.NEW_RELEASE) && each.DaysRented > 1)
                frequentRenterPoints++;

            // この貸し出しに関する数値の表示
            result      += "\t" + each.GetMovie().Title + "\t" + thisAmount + "円\n";

            totalAmount += thisAmount;
        }
        // フッタ部分の追加
        result += "総額 " + totalAmount + "円\n";
        result += "今回の獲得レンタル ポイントは " + frequentRenterPoints + " ポイントです.";

        return result;
    }

    …
}

「メソッドの移動」 - I - 5


RentalVideo.cs

class Customer
{
    …

    private int AmountFor(Rental aRental)
    {
        return aRental.GetCharge();
    }
}

「問い合わせによる一時変数の置き換え」 - I - 1


RentalVideo.cs

class Customer
{
    …

    // レシート生成
    public string Statement()
    {
        int     totalAmount             = 0;
        int     frequentRenterPoints    = 0;
        string  result                  = "レンタル記録: " + Name + "\n";

        foreach (Rental each in rentals)
        {
            int thisAmount  = each.GetCharge();

            // レンタル ポイントを加算
            frequentRenterPoints++;
            // 新作を二日以上借りた場合はボーナス ポイント
            if ((each.GetMovie().PriceCode == Movie.Kind.NEW_RELEASE) && each.DaysRented > 1)
                frequentRenterPoints++;

            // この貸し出しに関する数値の表示
            result      += "\t" + each.GetMovie().Title + "\t" + thisAmounteach.GetCharge() + "円\n";

            totalAmount += thisAmounteach.GetCharge();
        }
        // フッタ部分の追加
        result += "総額 " + totalAmount + "円\n";
        result += "今回の獲得レンタル ポイントは " + frequentRenterPoints + " ポイントです.";

        return result;
    }
}

「問い合わせによる一時変数の置き換え」 - I - 2


RentalVideo.cs

class Customer
{
    …

    // レシート生成
    public string Statement()
    {
        int     totalAmount             = 0;
        int     frequentRenterPoints    = 0;
        string  result                  = "レンタル記録: " + Name + "\n";

        foreach (Rental each in rentals)
        {
            int thisAmount  = each.GetCharge();

            // レンタル ポイントを加算
            frequentRenterPoints++;
            // 新作を二日以上借りた場合はボーナス ポイント
            if ((each.GetMovie().PriceCode == Movie.Kind.NEW_RELEASE) && each.DaysRented > 1)
                frequentRenterPoints++;

            // この貸し出しに関する数値の表示
            result      += "\t" + each.GetMovie().Title + "\t" + each.GetCharge() + "円\n";

            totalAmount += each.GetCharge();
        }
        // フッタ部分の追加
        result += "総額 " + totalAmount + "円\n";
        result += "今回の獲得レンタル ポイントは " + frequentRenterPoints + " ポイントです.";

        return result;
    }
}

「メソッドの抽出」 - II - 1


RentalVideo.cs

class Customer
{
    …

    // レシート生成
    public string Statement()
    {
        int     totalAmount             = 0;
        int     frequentRenterPoints    = 0;
        string  result                  = "レンタル記録: " + Name + "\n";

        foreach (Rental each in rentals)
        {
            // レンタル ポイントを加算
            frequentRenterPoints++;
            // 新作を二日以上借りた場合はボーナス ポイント
            if ((each.GetMovie().PriceCode == Movie.Kind.NEW_RELEASE) && each.DaysRented > 1)
                frequentRenterPoints++;

            // この貸し出しに関する数値の表示
            result      += "\t" + each.GetMovie().Title + "\t" + each.GetCharge() + "円\n";

            totalAmount += each.GetCharge();
        }
        // フッタ部分の追加
        result += "総額 " + totalAmount + "円\n";
        result += "今回の獲得レンタル ポイントは " + frequentRenterPoints + " ポイントです.";

        return result;
    }
}

class Rental
{
    …

    public int GetFrequentRenterPoints()
    {
        int frequentRenterPoints = 0;
        // レンタル ポイントを加算
        frequentRenterPoints++;
        // 新作を二日以上借りた場合はボーナス ポイント
        if ((each.GetMovie().PriceCode == Movie.Kind.NEW_RELEASE) && each.DaysRented > 1)
            frequentRenterPoints++;
        return frequentRenterPoints;
    }
}

「メソッドの抽出」 - II - 2


RentalVideo.cs

class Rental
{
    …

    public int GetFrequentRenterPoints()
    {
        int frequentRenterPoints = 0;
        // レンタル ポイントを加算
        frequentRenterPoints++;
        // 新作を二日以上借りた場合はボーナス ポイント
        if ((each.GetMovie().PriceCode == Movie.Kind.NEW_RELEASE) && each.DaysRented > 1)
            frequentRenterPoints++;
        return frequentRenterPoints;
    }
}

「メソッドの抽出」 - II - 3


RentalVideo.cs

class Rental
{
    …

    public int GetFrequentRenterPoints()
    {
        int frequentRenterPoints = 0;
        // レンタル ポイントを加算
        frequentRenterPoints++;
        // 新作を二日以上借りた場合はボーナス ポイント
        ifreturn ((GetMovie().PriceCode == Movie.Kind.NEW_RELEASE) && DaysRented > 1) ? 2 : 1;
            frequentRenterPoints++;
        return frequentRenterPoints;
    }
}

「メソッドの抽出」 - II - 4


RentalVideo.cs

class Customer
{
    …

    // レシート生成
    public string Statement()
    {
        int     totalAmount             = 0;
        int     frequentRenterPoints    = 0;
        string  result                  = "レンタル記録: " + Name + "\n";

        foreach (Rental each in rentals)
        {
            // レンタル ポイントを加算
            frequentRenterPoints++;
            // 新作を二日以上借りた場合はボーナス ポイント
            if ((each.GetMovie().PriceCode == Movie.Kind.NEW_RELEASE) && each.DaysRented > 1)
                frequentRenterPoints++;
            frequentRenterPoints += each.GetFrequentRenterPoints();

            // この貸し出しに関する数値の表示
            result      += "\t" + each.GetMovie().Title + "\t" + each.GetCharge() + "円\n";

            totalAmount += each.GetCharge();
        }
        // フッタ部分の追加
        result += "総額 " + totalAmount + "円\n";
        result += "今回の獲得レンタル ポイントは " + frequentRenterPoints + " ポイントです.";

        return result;
    }
}

「問い合わせによる一時変数の置き換え」 - II - 1


RentalVideo.cs

class Customer
{
    …

    // レシート生成
    public string Statement()
    {
        int     totalAmount             = 0;
        int     frequentRenterPoints    = 0;
        string  result                  = "レンタル記録: " + Name + "\n";

        foreach (Rental each in rentals)
        {
            // レンタル ポイントを加算
            frequentRenterPoints += each.GetFrequentRenterPoints();

            // この貸し出しに関する数値の表示
            result      += "\t" + each.GetMovie().Title + "\t" + each.GetCharge() + "円\n";

            totalAmount += each.GetCharge();
        }
        // フッタ部分の追加
        result += "総額 " + totalAmount + "円\n";
        result += "今回の獲得レンタル ポイントは " + frequentRenterPoints + " ポイントです.";

        return result;
    }

    private int GetTotalCharge()
    {
        int totalAmount = 0;
        foreach (Rental each in rentals)
        {
            totalAmount += each.GetCharge();
        }
        return totalAmount;
    }
}

「問い合わせによる一時変数の置き換え」 - II - 2


RentalVideo.cs

class Customer
{
    …

    private int GetTotalCharge()
   {
        int totalAmountresult = 0;
        foreach (Rental each in rentals)
        {
            totalAmountresult += each.GetCharge();
        }
        return totalAmountresult;
    }
}

「問い合わせによる一時変数の置き換え」 - II - 3


RentalVideo.cs

class Customer
{
    …

    // レシート生成
    public string Statement()
    {
        int     totalAmount             = 0;
        int     frequentRenterPoints    = 0;
        string  result                  = "レンタル記録: " + Name + "\n";

        foreach (Rental each in rentals)
        {
            // レンタル ポイントを加算
            frequentRenterPoints += each.GetFrequentRenterPoints();

            // この貸し出しに関する数値の表示
            result      += "\t" + each.GetMovie().Title + "\t" + each.GetCharge() + "円\n";

            totalAmount += each.GetCharge();
        }
        // フッタ部分の追加
        result += "総額 " + totalAmountGetTotalCharge() + "円\n";
        result += "今回の獲得レンタル ポイントは " + frequentRenterPoints + " ポイントです.";

        return result;
    }

    …
}

「問い合わせによる一時変数の置き換え」 - II - 4


RentalVideo.cs

class Customer
{
    …

    // レシート生成
    public string Statement()
    {
        int     frequentRenterPoints    = 0;
        string  result                  = "レンタル記録: " + Name + "\n";

        foreach (Rental each in rentals)
        {
            // レンタル ポイントを加算
            frequentRenterPoints += each.GetFrequentRenterPoints();

            // この貸し出しに関する数値の表示
            result      += "\t" + each.GetMovie().Title + "\t" + each.GetCharge() + "円\n";
        }
        // フッタ部分の追加
        result += "総額 " + GetTotalCharge() + "円\n";
        result += "今回の獲得レンタル ポイントは " + frequentRenterPoints + " ポイントです.";

        return result;
    }

    …

    private int GetTotalFrequentRenterPoints()
    {
        int frequentRenterPoints = 0;
        foreach (Rental each in rentals)
        {
            // レンタル ポイントを加算
            frequentRenterPoints += each.GetFrequentRenterPoints();
        }
        return frequentRenterPoints;
    }
}

「問い合わせによる一時変数の置き換え」 - II - 5


RentalVideo.cs

class Customer
{
    …

    private int GetTotalFrequentRenterPoints()
    {
        int frequentRenterPointsresult = 0;
        foreach (Rental each in rentals)
        {
            // レンタル ポイントを加算
            frequentRenterPointsresult += each.GetFrequentRenterPoints();
        }
        return frequentRenterPointsresult;
    }
}

「問い合わせによる一時変数の置き換え」 - II - 6


RentalVideo.cs

class Customer
{
    …

    // レシート生成
    public string Statement()
    {
        int     frequentRenterPoints    = 0;
        string  result                  = "レンタル記録: " + Name + "\n";

        foreach (Rental each in rentals)
        {
            // レンタル ポイントを加算
            frequentRenterPoints += each.GetFrequentRenterPoints();

            // この貸し出しに関する数値の表示
            result      += "\t" + each.GetMovie().Title + "\t" + each.GetCharge() + "円\n";
        }
        // フッタ部分の追加
        result += "総額 " + GetTotalCharge() + "円\n";
        result += "今回の獲得レンタル ポイントは " + frequentRenterPointsGetTotalFrequentRenterPoints() + " ポイントです.";

        return result;
    }

    …
}

レシート生成 (HTML版) の追加


RentalVideoTest.cs

public class TestCustomer
{
    …

    [Test]
    public void TestCustomerHtmlStatement()
    {
        Customer customer = new Customer("福井 太郎");
        customer.Add(new Rental(new Movie("マトリックス"                   , Movie.Kind.REGULAR    ), 8));
        customer.Add(new Rental(new Movie("マトリックス リボリューションズ", Movie.Kind.NEW_RELEASE), 2));
        customer.Add(new Rental(new Movie("ファインディング ニモ"          , Movie.Kind.CHILDRENS  ), 4));
        string statement = customer.HtmlStatement();
        Assert.AreEqual("<h1>レンタル記録: <em>福井 太郎</em></h1>\n<p>\nマトリックス: 1100円<br>\nマトリックス リボリューションズ: 600円<br>\nファインディング ニモ: 300円<br>\n<p>総額 <em>2000</em>円<br>\n今回の獲得レンタル ポイントは <em>4</em> ポイントです.\n</p>\n", statement);
    }
}

RentalVideo.cs

class Customer
{
    …

    // レシート生成 (HTML版)
    public string HtmlStatement()
    {
        string  result = "<h1>レンタル記録: <em>" + Name + "</em></h1>\n<p>\n";

        foreach (Rental each in rentals)
        {
            // この貸し出しに関する数値の表示
            result += each.GetMovie().Title + ": " + each.GetCharge() + "円<br>\n";
        }
        // フッタ部分の追加
        result += "<p>総額 <em>" + GetTotalCharge() + "</em>円<br>\n";
        result += "今回の獲得レンタル ポイントは <em>" + GetTotalFrequentRenterPoints() + "</em> ポイントです.\n</p>\n";

        return result;
    }

    …
}

「メソッドの移動」- II - 1


RentalVideo.cs

class Movie
{
    …

    public int GetCharge()
    {
        int result = 0;

        switch (GetMovie().PriceCode)
        {
            case Movie.Kind.REGULAR    :
                result += 200;
                if (DaysRented > 2)
                    result += (DaysRented - 2) * 150;
                break;

            case Movie.Kind.NEW_RELEASE:
                result += DaysRented * 300;
                break;

            case Movie.Kind.CHILDRENS  :
                result += 150;
                if (DaysRented > 3)
                    result += (DaysRented - 3) * 150;
                break;
        }
        return result;
    }
}

class Rental
{
    …

    public int GetCharge()
    {
        int result = 0;

        switch (GetMovie().PriceCode)
        {
            case Movie.Kind.REGULAR    :
                result += 200;
                if (DaysRented > 2)
                    result += (DaysRented - 2) * 150;
                break;

            case Movie.Kind.NEW_RELEASE:
                result += DaysRented * 300;
                break;

            case Movie.Kind.CHILDRENS  :
                result += 150;
                if (DaysRented > 3)
                    result += (DaysRented - 3) * 150;
                break;
        }
        return result;
    }

    …
}

「メソッドの移動」- II - 2


RentalVideo.cs

class Movie
{
    …

    public int GetCharge(int daysRented)
    {
        int result = 0;

        switch (GetMovie().PriceCode)
        {
            case Movie.Kind.REGULAR    :
                result += 200;
                if (DaysRenteddaysRented > 2)
                    result += (DaysRenteddaysRented - 2) * 150;
                break;

            case Movie.Kind.NEW_RELEASE:
                result += DaysRenteddaysRented * 300;
                break;

            case Movie.Kind.CHILDRENS  :
                result += 150;
                if (DaysRenteddaysRented > 3)
                    result += (DaysRenteddaysRented - 3) * 150;
                break;
        }
        return result;
    }
}

「メソッドの移動」- II - 3


RentalVideo.cs

class Rental
{
    …

    public int GetCharge()
    {
        int result = 0;

        switch (GetMovie().PriceCode)
        {
            case Movie.Kind.REGULAR    :
                result += 200;
                if (DaysRented > 2)
                    result += (DaysRented - 2) * 150;
                break;

            case Movie.Kind.NEW_RELEASE:
                result += DaysRented * 300;
                break;

            case Movie.Kind.CHILDRENS  :
                result += 150;
                if (DaysRented > 3)
                    result += (DaysRented - 3) * 150;
                break;
        }
        return result;
        return GetMovie().GetCharge(DaysRented);
    }

    …
}

「メソッドの移動」- III - 1


RentalVideo.cs

class Movie
{
    …

    public int GetFrequentRenterPoints()
    {
        // 新作を二日以上借りた場合はボーナス ポイント
        return ((GetMovie().PriceCode == Movie.Kind.NEW_RELEASE) && DaysRented > 1) ? 2 : 1;
    }
}

class Rental
{
    …

    public int GetFrequentRenterPoints()
    {
        // 新作を二日以上借りた場合はボーナス ポイント
        return ((GetMovie().PriceCode == Movie.Kind.NEW_RELEASE) && DaysRented > 1) ? 2 : 1;
    }
}

「メソッドの移動」- III - 2


RentalVideo.cs

class Movie
{
    …

    public int GetFrequentRenterPoints(int daysRented)
    {
        // 新作を二日以上借りた場合はボーナス ポイント
        return ((GetMovie().PriceCode == Movie.Kind.NEW_RELEASE) && DaysRenteddaysRented > 1) ? 2 : 1;
    }
}

「メソッドの移動」- III - 3


RentalVideo.cs

class Rental
{
    …

    public int GetFrequentRenterPoints()
    {
        // 新作を二日以上借りた場合はボーナス ポイント
        return ((GetMovie().PriceCode == Movie.Kind.NEW_RELEASE) && DaysRented > 1) ? 2 : 1;
        GetMovie().GetFrequentRenterPoints(DaysRented);
    }
}

「自己カプセル化フィールド」- I - 1


RentalVideo.cs

class Movie
{
    …

    public Movie(string title, Kind priceCode)
    {
        this.title = title;
        this.priceCodePriceCode  = priceCode;
    }

    …
}

「State/Strategy によるタイプコードの置き換え」- I - 1


RentalVideo.cs

abstract class Price
{
    public abstract Movie.Kind GetPriceCode();
}

class RegularPrice : Price
{
    public override Movie.Kind GetPriceCode()
    { return Movie.Kind.REGULAR; }
}

class NewReleasePrice : Price
{
    public override Movie.Kind GetPriceCode()
    { return Movie.Kind.NEW_RELEASE; }
}

class ChildrensPrice : Price
{
    public override Movie.Kind GetPriceCode()
    { return Movie.Kind.CHILDRENS; }
}

「State/Strategy によるタイプコードの置き換え」- I - 2


RentalVideo.cs

abstract class Price
{
    public enum Kind
    {
        REGULAR    ,
        NEW_RELEASE,
        CHILDRENS
    }

    …
}

…

class Movie
{
    public enum Kind
    {
        REGULAR    ,
        NEW_RELEASE,
        CHILDRENS
    }

    …
}

「State/Strategy によるタイプコードの置き換え」- I - 3


abstract class Price
{
    public abstract Movie.Kind GetPriceCode();
}

class RegularPrice : Price
{
    public override Movie.Kind GetPriceCode()
    { return Movie.Kind.REGULAR; }
}

class NewReleasePrice : Price
{
    public override Movie.Kind GetPriceCode()
    { return Movie.Kind.NEW_RELEASE; }
}

class ChildrensPrice : Price
{
    public override Movie.Kind GetPriceCode()
    { return Movie.Kind.CHILDRENS; }
}

RentalVideo.cs

abstract class Price
{
    …
}

「State/Strategy によるタイプコードの置き換え」- I - 4


RentalVideo.cs

class Movie
{
    public enum Kind
    {
        REGULAR    ,
        NEW_RELEASE,
        CHILDRENS
    }

    …
}

「State/Strategy によるタイプコードの置き換え」- I - 5


RentalVideoTest.cs

[TestFixture]
public class TestMovie
{
    [Test]
    public void TestCreateMovies()
    {
        Movie movie1 = new Movie("七人の侍"        , Movie.Price.Kind.REGULAR    );
        Assert.AreEqual("七人の侍"        , movie1.Title);
        Assert.AreEqual(Movie.Price.Kind.REGULAR    , movie1.PriceCode);

        Movie movie2 = new Movie("ラスト サムライ" , Movie.Price.Kind.NEW_RELEASE);
        Assert.AreEqual("ラスト サムライ" , movie2.Title);
        Assert.AreEqual(Movie.Price.Kind.NEW_RELEASE, movie2.PriceCode);

        Movie movie3 = new Movie("フランダースの犬", Movie.Price.Kind.CHILDRENS  );
        Assert.AreEqual("フランダースの犬", movie3.Title);
        Assert.AreEqual(Movie.Price.Kind.CHILDRENS  , movie3.PriceCode);
    }
}

[TestFixture]
public class TestRental
{
    [Test]
    public void TestCreateRentals()
    {
        Rental rental1 = new Rental(new Movie("E.T."                             , Movie.Price.Kind.REGULAR    ), 8);
        Assert.AreEqual("E.T."                             , rental1.GetMovie().Title);
        Assert.AreEqual(Movie.Price.Kind.REGULAR    , rental1.GetMovie().PriceCode);
        Assert.AreEqual(8, rental1.DaysRented);
        Rental rental2 = new Rental(new Movie("ロード・オブ・ザ・リング 王の帰還", Movie.Price.Kind.NEW_RELEASE), 3);
        Assert.AreEqual("ロード・オブ・ザ・リング 王の帰還", rental2.GetMovie().Title);
        Assert.AreEqual(Movie.Price.Kind.NEW_RELEASE, rental2.GetMovie().PriceCode);
        Assert.AreEqual(3, rental2.DaysRented);
        Rental rental3 = new Rental(new Movie("トイ・ストーリー2"                , Movie.Price.Kind.CHILDRENS  ), 2);
        Assert.AreEqual("トイ・ストーリー2"                , rental3.GetMovie().Title);
        Assert.AreEqual(Movie.Price.Kind.CHILDRENS  , rental3.GetMovie().PriceCode);
        Assert.AreEqual(2, rental3.DaysRented);
    }
}

[TestFixture]
public class TestCustomer
{
    …

    [Test]
    public void TestCustomerStatement()
    {
        Customer customer = new Customer("福井 太郎");
        customer.Add(new Rental(new Movie("マトリックス"                   , Movie.Price.Kind.REGULAR    ), 8));
        customer.Add(new Rental(new Movie("マトリックス リボリューションズ", Movie.Price.Kind.NEW_RELEASE), 2));
        customer.Add(new Rental(new Movie("ファインディング ニモ"          , Movie.Price.Kind.CHILDRENS  ), 4));
        string statement = customer.Statement();
        Assert.AreEqual("レンタル記録: 福井 太郎\n\tマトリックス\t1100円\n\tマトリックス リボリューションズ\t600円\n\tファインディング ニモ\t300円\n総額 2000円\n今回の獲得レンタル ポイントは 4 ポイントです.", statement);
    }

    [Test]
    public void TestCustomerHtmlStatement()
    {
        Customer customer = new Customer("福井 太郎");
        customer.Add(new Rental(new Movie("マトリックス"                   , Movie.Price.Kind.REGULAR    ), 8));
        customer.Add(new Rental(new Movie("マトリックス リボリューションズ", Movie.Price.Kind.NEW_RELEASE), 2));
        customer.Add(new Rental(new Movie("ファインディング ニモ"          , Movie.Price.Kind.CHILDRENS  ), 4));
        string statement = customer.HtmlStatement();
        Assert.AreEqual("<h1>レンタル記録: <em>福井 太郎</em></h1>\n<p>\nマトリックス: 1100円<br>\nマトリックス リボリューションズ: 600円<br>\nファインディング ニモ: 300円<br>\n<p>総額 <em>2000</em>円<br>\n今回の獲得レンタル ポイントは <em>4</em> ポイントです.\n</p>\n", statement);
    }
}

RentalVideo.cs

class Movie
{
    …

    public Movie(string title, Price.Kind priceCode)
    {
        this.title = title;
        PriceCode  = priceCode;
    }

    private Price.Kind   priceCode;

    …

    public Price.Kind PriceCode
    {
        get { return priceCode; }
        set { priceCode = value; }
    }

    …

    public int GetCharge(int daysRented)
    {
        int result = 0;

        switch (PriceCode)
        {
            case Price.Kind.REGULAR    :
                result += 200;
                if (daysRented > 2)
                    result += (daysRented - 2) * 150;
                break;

            case Price.Kind.NEW_RELEASE:
                result += daysRented * 300;
                break;

            case Price.Kind.CHILDRENS  :
                result += 150;
                if (daysRented > 3)
                    result += (daysRented - 3) * 150;
                break;
        }
        return result;
    }

    public int GetFrequentRenterPoints(int daysRented)
    {
        // 新作を二日以上借りた場合はボーナス ポイント
        return ((PriceCode == Price.Kind.NEW_RELEASE) && daysRented > 1) ? 2 : 1;
    }
}

「State/Strategy によるタイプコードの置き換え」- I - 6


RentalVideo.cs

class NewReleasePrice : Price
{
    …
    private Price.Kind priceCode;
    private Price   price;
    …

    public Price.Kind PriceCode
    {
        get
        {
            return priceCode;
            return price.GetPriceCode();
        }
        set
        {
            priceCode = value;
            switch (value)
            {
                case Price.Kind.REGULAR    : price = new RegularPrice    (); break;
                case Price.Kind.NEW_RELEASE: price = new NewReleasePrice (); break;
                case Price.Kind.CHILDRENS  : price = new ChildrensPrice  (); break;
                default                    : throw new ArgumentException ();
            }
        }
    }
}

「メソッドの移動」- III - 1


RentalVideo.cs

abstract class Price
{
    …

    public int GetCharge(int daysRented)
    {
        int result = 0;

        switch (PriceCode)
        {
            case Price.Kind.REGULAR    :
                result += 200;
                if (daysRented > 2)
                    result += (daysRented - 2) * 150;
                break;

            case Price.Kind.NEW_RELEASE:
                result += daysRented * 300;
                break;

            case Price.Kind.CHILDRENS  :
                result += 150;
                if (daysRented > 3)
                    result += (daysRented - 3) * 150;
                break;
        }
        return result;
    }
}

…

class Movie
{
    …

    public int GetCharge(int daysRented)
    {
        int result = 0;

        switch (PriceCode)
        {
            case Price.Kind.REGULAR    :
                result += 200;
                if (daysRented > 2)
                    result += (daysRented - 2) * 150;
                break;

            case Price.Kind.NEW_RELEASE:
                result += daysRented * 300;
                break;

            case Price.Kind.CHILDRENS  :
                result += 150;
                if (daysRented > 3)
                    result += (daysRented - 3) * 150;
                break;
        }
        return result;
    }

    …
}

「メソッドの移動」- III - 2


RentalVideo.cs

abstract class Price
{
    …

    public int GetCharge(int daysRented)
    {
        int result = 0;

        switch (PriceCodeGetPriceCode())
        {
            case Price.Kind.REGULAR    :
                result += 200;
                if (daysRented > 2)
                    result += (daysRented - 2) * 150;
                break;

            case Price.Kind.NEW_RELEASE:
                result += daysRented * 300;
                break;

            case Price.Kind.CHILDRENS  :
                result += 150;
                if (daysRented > 3)
                    result += (daysRented - 3) * 150;
                break;
        }
        return result;
    }
}

「メソッドの移動」- III - 3


RentalVideo.cs

class Movie
{
    …

    public int GetCharge(int daysRented)
    {
        return price.GetCharge(daysRented);
        int result = 0;

        switch (PriceCode)
        {
            case Price.Kind.REGULAR    :
                result += 200;
                if (daysRented > 2)
                    result += (daysRented - 2) * 150;
                break;

            case Price.Kind.NEW_RELEASE:
                result += daysRented * 300;
                break;

            case Price.Kind.CHILDRENS  :
                result += 150;
                if (daysRented > 3)
                    result += (daysRented - 3) * 150;
                break;
        }
        return result;
    }

    …
}

「ポリモーフィズムによる条件記述の置き換え」- I - 1


RentalVideo.cs

abstract class Price
{
    …

    public int GetCharge(int daysRented)
    {
        int result = 0;

        switch (GetPriceCode())
        {
            case Kind.REGULAR    :
                result += 200;
                if (daysRented > 2)
                    result += (daysRented - 2) * 150;
                break;

            case Kind.NEW_RELEASE:
                result += daysRented * 300;
                break;

            case Kind.CHILDRENS  :
                result += 150;
                if (daysRented > 3)
                    result += (daysRented - 3) * 150;
                break;
        }
        return result;
    }
}

class RegularPrice : Price
{
    …

    public int GetCharge(int daysRented)
    {
        int result = 0;
        result += 200;
        if (daysRented > 2)
            result += (daysRented - 2) * 150;
        return result;
    }
}

class NewReleasePrice : Price
{
    …

    public int GetCharge(int daysRented)
    {
        int result = 0;
        result += daysRented * 300;
        return result;
    }
}

class ChildrensPrice : Price
{
    …

    public int GetCharge(int daysRented)
    {
        int result = 0;
        result += 150;
        if (daysRented > 3)
            result += (daysRented - 3) * 150;
        return result;
    }
}

「ポリモーフィズムによる条件記述の置き換え」- I - 2


RentalVideo.cs

class RegularPrice : Price
{
    …

    public int GetCharge(int daysRented)
    {
        int result = 0;
        result += 200;
        if (daysRented > 2)
            result += (daysRented - 2) * 150;
        return result;
    }
}

class NewReleasePrice : Price
{
    …

    public int GetCharge(int daysRented)
    {
        int result = 0;
        result += daysRented * 300;
        return result;
        return daysRented * 300;
    }
}

class ChildrensPrice : Price
{
    …

    public int GetCharge(int daysRented)
    {
        int result = 0;
        result += 150;
        if (daysRented > 3)
            result += (daysRented - 3) * 150;
        return result;
    }
}

「ポリモーフィズムによる条件記述の置き換え」- I - 3


RentalVideo.cs

abstract class Price
{
    …

    public abstract int GetCharge(int daysRented);
    {
        int result = 0;

        switch (GetPriceCode())
        {
            case Kind.REGULAR    :
                result += 200;
                if (daysRented > 2)
                    result += (daysRented - 2) * 150;
                break;

            case Kind.NEW_RELEASE:
                result += daysRented * 300;
                break;

            case Kind.CHILDRENS  :
                result += 150;
                if (daysRented > 3)
                    result += (daysRented - 3) * 150;
                break;
        }
        return result;
    }
}

「ポリモーフィズムによる条件記述の置き換え」- I - 4


RentalVideo.cs

class RegularPrice : Price
{
    …

    public override int GetCharge(int daysRented)
    …
}

class NewReleasePrice : Price
{
    …

    public override int GetCharge(int daysRented)
    …
}

class ChildrensPrice : Price
{
    …

    public override int GetCharge(int daysRented)
    …
}

「メソッドの移動」- IV - 1


RentalVideo.cs

abstract class Price
{
    …

    public int GetFrequentRenterPoints(int daysRented)
    {
        // 新作を二日以上借りた場合はボーナス ポイント
        return ((PriceCode == Price.Kind.NEW_RELEASE) && daysRented > 1) ? 2 : 1;
    }
}

…

class Movie
{
    …

    public int GetFrequentRenterPoints(int daysRented)
    {
        // 新作を二日以上借りた場合はボーナス ポイント
        return ((PriceCode == Price.Kind.NEW_RELEASE) && daysRented > 1) ? 2 : 1;
    }
}

「メソッドの移動」- IV - 2


RentalVideo.cs

abstract class Price
{
    …

    public int GetFrequentRenterPoints(int daysRented)
    {
        // 新作を二日以上借りた場合はボーナス ポイント
        return ((PriceCodeGetPriceCode() == Price.Kind.NEW_RELEASE) && daysRented > 1) ? 2 : 1;
    }
}

「メソッドの移動」- IV - 3


RentalVideo.cs

class Movie
{
    …

    public int GetFrequentRenterPoints(int daysRented)
    {
        // 新作を二日以上借りた場合はボーナス ポイント
        return ((PriceCode == Price.Kind.NEW_RELEASE) && daysRented > 1) ? 2 : 1;
        return price.GetFrequentRenterPoints(daysRented);
   }
}

「ポリモーフィズムによる条件記述の置き換え」- II - 1


RentalVideo.cs

abstract class Price
{
    …

    public int GetFrequentRenterPoints(int daysRented)
    {
        // 新作を二日以上借りた場合はボーナス ポイント
        return ((GetPriceCode() == Kind.NEW_RELEASE) && daysRented > 1) ? 2 : 1;
    }
}

class NewReleasePrice : Price
{
    …

    public int GetFrequentRenterPoints(int daysRented)
    {
        // 新作を二日以上借りた場合はボーナス ポイント
        return ((GetPriceCode() == Kind.NEW_RELEASE) && daysRented > 1) ? 2 : 1;
    }
}

「ポリモーフィズムによる条件記述の置き換え」- II - 2


RentalVideo.cs

abstract class Price
{
    …

    public int GetFrequentRenterPoints(int daysRented)
    {
        // 新作を二日以上借りた場合はボーナス ポイント
        return ((GetPriceCode() == Kind.NEW_RELEASE) && daysRented > 1) ? 2 : 1;
    }
}

class NewReleasePrice : Price
{
    …

    public int GetFrequentRenterPoints(int daysRented)
    {
        // 新作を二日以上借りた場合はボーナス ポイント
        return ((GetPriceCode() == Kind.NEW_RELEASE) && daysRented > 1) ? 2 : 1;
    }
}

「ポリモーフィズムによる条件記述の置き換え」- II - 3


RentalVideo.cs

abstract class Price
{
    …

    public virtual int GetFrequentRenterPoints(int daysRented)
    {
        return  1;
    }
}

class NewReleasePrice : Price
{
    …

    public override int GetFrequentRenterPoints(int daysRented)
    {
        // 二日以上借りた場合はボーナス ポイント
        return (daysRented > 1) ? 2 : 1;
    }
}

ビデオの種別の追加


RentalVideoTest.cs

[TestFixture]
public class TestMovie
{
    [Test]
    public void TestCreateMovies()
    {
        …

        Movie movie4 = new Movie("交響曲第9番ニ長調", Price.Kind.MUSIC     );
        Assert.AreEqual("交響曲第9番ニ長調", movie4.Title);
        Assert.AreEqual(Price.Kind.MUSIC      , movie4.PriceCode);
    }
}

[TestFixture]
public class TestRental
{
    [Test]
    public void TestCreateRentals()
    {
        …

        Rental rental4 = new Rental(new Movie("グレイテスト・ヒッツ 1"          , Price.Kind.MUSIC      ), 9);
        Assert.AreEqual("グレイテスト・ヒッツ 1"           , rental4.GetMovie().Title);
        Assert.AreEqual(Price.Kind.MUSIC      , rental4.GetMovie().PriceCode);
        Assert.AreEqual(9, rental4.DaysRented);
    }
}


[TestFixture]
public class TestCustomer
{
    …

    [Test]
    public void TestCustomerStatement()
    {
        …

        customer.Add(new Rental(new Movie("ザ・マイルス・デイビス・ストーリー", Price.Kind.MUSIC      ), 6));
        string statement = customer.Statement();
        Assert.AreEqual("レンタル記録: 福井 太郎\n\tマトリックス\t1100円\n\tマトリックス リボリューションズ\t600円\n\tファインディング ニモ\t300円\n総額 2000円\n今回の獲得レンタル ポイントは 4 ポイントです.", statement);
        Assert.AreEqual("レンタル記録: 福井 太郎\n\tマトリックス\t1100円\n\tマトリックス リボリューションズ\t600円\n\tファインディング ニモ\t300円\n\tザ・マイルス・デイビス・ストーリー\t900円\n総額 2900円\n今回の獲得レンタル ポイントは 5 ポイントです.", statement);
    }

    [Test]
    public void TestCustomerHtmlStatement()
    {
        …

        customer.Add(new Rental(new Movie("ザ・マイルス・デイビス・ストーリー", Price.Kind.MUSIC      ), 6));
        string statement = customer.HtmlStatement();
        Assert.AreEqual("<h1>レンタル記録: <em>福井 太郎</em></h1>\n<p>\nマトリックス: 1100円<br>\nマトリックス リボリューションズ: 600円<br>\nファインディング ニモ: 300円<br>\n<p>総額 <em>2000</em>円<br>\n今回の獲得レンタル ポイントは <em>4</em> ポイントです.\n</p>\n", statement);
        Assert.AreEqual("<h1>レンタル記録: <em>福井 太郎</em></h1>\n<p>\nマトリックス: 1100円<br>\nマトリックス リボリューションズ: 600円<br>\nファインディング ニモ: 300円<br>\nザ・マイルス・デイビス・ストーリー: 900円<br>\n<p>総額 <em>2900</em>円<br>\n今回の獲得レンタル ポイントは <em>5</em> ポイントです.\n</p>\n", statement);
    }
}

RentalVideo.cs

abstract class Price
{
    public enum Kind
    {
        REGULAR    ,
        NEW_RELEASE,
        CHILDRENS  ,
        MUSIC
    }

    …
}

…

class MusicPrice : Price
{
    public override Kind GetPriceCode()
    { return Kind.MUSIC; }

    public override int GetCharge(int daysRented)
    {
        return daysRented * 150;
    }
}

class Movie
{
…

    public Price.Kind PriceCode
    {
        get
        {
            return price.GetPriceCode();
        }
        set
        {
            switch (value)
            {
                case Price.Kind.REGULAR    : price = new RegularPrice    (); break;
                case Price.Kind.NEW_RELEASE: price = new NewReleasePrice (); break;
                case Price.Kind.CHILDRENS  : price = new ChildrensPrice  (); break;
                case Price.Kind.MUSIC      : price = new MusicPrice      (); break;
                default                    : throw new ArgumentException ();
            }
        }
    }

…
}

最終コード


RentalVideoTest.cs

using System;
using NUnit.Framework;

[TestFixture]
public class TestMovie
{
    [Test]
    public void TestCreateMovies()
    {
        Movie movie1 = new Movie("七人の侍"        , Price.Kind.REGULAR    );
        Assert.AreEqual("七人の侍"         , movie1.Title);
        Assert.AreEqual(Price.Kind.REGULAR    , movie1.PriceCode);

        Movie movie2 = new Movie("ラスト サムライ" , Price.Kind.NEW_RELEASE);
        Assert.AreEqual("ラスト サムライ"  , movie2.Title);
        Assert.AreEqual(Price.Kind.NEW_RELEASE, movie2.PriceCode);

        Movie movie3 = new Movie("フランダースの犬", Price.Kind.CHILDRENS  );
        Assert.AreEqual("フランダースの犬" , movie3.Title);
        Assert.AreEqual(Price.Kind.CHILDRENS  , movie3.PriceCode);

        Movie movie4 = new Movie("交響曲第9番ニ長調", Price.Kind.MUSIC     );
        Assert.AreEqual("交響曲第9番ニ長調", movie4.Title);
        Assert.AreEqual(Price.Kind.MUSIC      , movie4.PriceCode);
    }
}

[TestFixture]
public class TestRental
{
    [Test]
    public void TestCreateRentals()
    {
        Rental rental1 = new Rental(new Movie("E.T."                           , Price.Kind.REGULAR    ), 8);
        Assert.AreEqual("E.T."                             , rental1.GetMovie().Title);
        Assert.AreEqual(Price.Kind.REGULAR    , rental1.GetMovie().PriceCode);
        Assert.AreEqual(8, rental1.DaysRented);
        Rental rental2 = new Rental(new Movie("ロード・オブ・ザ・リング 王の帰還", Price.Kind.NEW_RELEASE), 3);
        Assert.AreEqual("ロード・オブ・ザ・リング 王の帰還", rental2.GetMovie().Title);
        Assert.AreEqual(Price.Kind.NEW_RELEASE, rental2.GetMovie().PriceCode);
        Assert.AreEqual(3, rental2.DaysRented);
        Rental rental3 = new Rental(new Movie("トイ・ストーリー2"               , Price.Kind.CHILDRENS  ), 2);
        Assert.AreEqual("トイ・ストーリー2"                , rental3.GetMovie().Title);
        Assert.AreEqual(Price.Kind.CHILDRENS  , rental3.GetMovie().PriceCode);
        Assert.AreEqual(2, rental3.DaysRented);
        Rental rental4 = new Rental(new Movie("グレイテスト・ヒッツ 1"          , Price.Kind.MUSIC      ), 9);
        Assert.AreEqual("グレイテスト・ヒッツ 1"           , rental4.GetMovie().Title);
        Assert.AreEqual(Price.Kind.MUSIC      , rental4.GetMovie().PriceCode);
        Assert.AreEqual(9, rental4.DaysRented);
    }
}

[TestFixture]
public class TestCustomer
{
    [Test]
    public void TestCreateCustomer()
    {
        Customer customer = new Customer("福井 太郎");
        Assert.AreEqual("福井 太郎", customer.Name);
    }

    [Test]
    public void TestCustomerStatement()
    {
        Customer customer = new Customer("福井 太郎");
        customer.Add(new Rental(new Movie("マトリックス"                     , Price.Kind.REGULAR    ), 8));
        customer.Add(new Rental(new Movie("マトリックス リボリューションズ"   , Price.Kind.NEW_RELEASE), 2));
        customer.Add(new Rental(new Movie("ファインディング ニモ"            , Price.Kind.CHILDRENS  ), 4));
        customer.Add(new Rental(new Movie("ザ・マイルス・デイビス・ストーリー", Price.Kind.MUSIC      ), 6));
        string statement = customer.Statement();
        Assert.AreEqual("レンタル記録: 福井 太郎\n\tマトリックス\t1100円\n\tマトリックス リボリューションズ\t600円\n\tファインディング ニモ\t300円\n\tザ・マイルス・デイビス・ストーリー\t900円\n総額 2900円\n今回の獲得レンタル ポイントは 5 ポイントです.", statement);
    }

    [Test]
    public void TestCustomerHtmlStatement()
    {
        Customer customer = new Customer("福井 太郎");
        customer.Add(new Rental(new Movie("マトリックス"                   , Price.Kind.REGULAR    ), 8));
        customer.Add(new Rental(new Movie("マトリックス リボリューションズ", Price.Kind.NEW_RELEASE), 2));
        customer.Add(new Rental(new Movie("ファインディング ニモ"          , Price.Kind.CHILDRENS  ), 4));
        customer.Add(new Rental(new Movie("ザ・マイルス・デイビス・ストーリー", Price.Kind.MUSIC      ), 6));
        string statement = customer.HtmlStatement();
        Assert.AreEqual("<h1>レンタル記録: <em>福井 太郎</em></h1>\n<p>\nマトリックス: 1100円<br>\nマトリックス リボリューションズ: 600円<br>\nファインディング ニモ: 300円<br>\nザ・マイルス・デイビス・ストーリー: 900円<br>\n<p>総額 <em>2900</em>円<br>\n今回の獲得レンタル ポイントは <em>5</em> ポイントです.\n</p>\n", statement);
    }
}

RentalVideo.cs

using System;
using System.Collections;

abstract class Price
{
    public enum Kind
    {
        REGULAR    ,
        NEW_RELEASE,
        CHILDRENS  ,
        MUSIC
    }

    public abstract Kind GetPriceCode();

    public abstract int GetCharge(int daysRented);

    public virtual int GetFrequentRenterPoints(int daysRented)
    {
        return  1;
    }
}

class RegularPrice : Price
{
    public override Kind GetPriceCode()
    { return Kind.REGULAR; }

    public override int GetCharge(int daysRented)
    {
        int result = 200;
        if (daysRented > 2)
            result += (daysRented - 2) * 150;
        return result;
    }
}

class NewReleasePrice : Price
{
    public override Kind GetPriceCode()
    { return Kind.NEW_RELEASE; }

    public override int GetCharge(int daysRented)
    {
        return daysRented * 300;
    }

    public override int GetFrequentRenterPoints(int daysRented)
    {
        // 二日以上借りた場合はボーナス ポイント
        return (daysRented > 1) ? 2 : 1;
    }
}

class ChildrensPrice : Price
{
    public override Kind GetPriceCode()
    { return Kind.CHILDRENS; }

    public override int GetCharge(int daysRented)
    {
        int result = 150;
        if (daysRented > 3)
            result += (daysRented - 3) * 150;
        return result;
    }
}

class MusicPrice : Price
{
    public override Kind GetPriceCode()
    { return Kind.MUSIC; }

    public override int GetCharge(int daysRented)
    {
        return daysRented * 150;
    }
}

class Movie
{
    private string  title;
    private Price   price;

    public Movie(string title, Price.Kind priceCode)
    {
        this.title = title;
        PriceCode  = priceCode;
    }

    public Price.Kind PriceCode
    {
        get
        {
            return price.GetPriceCode();
        }
        set
        {
            switch (value)
            {
                case Price.Kind.REGULAR    : price = new RegularPrice    (); break;
                case Price.Kind.NEW_RELEASE: price = new NewReleasePrice (); break;
                case Price.Kind.CHILDRENS  : price = new ChildrensPrice  (); break;
                case Price.Kind.MUSIC      : price = new MusicPrice      (); break;
                default                    : throw new ArgumentException ();
            }
        }
    }

    public string Title
    {
        get { return title; }
    }

    public int GetCharge(int daysRented)
    {
        return price.GetCharge(daysRented);
    }

    public int GetFrequentRenterPoints(int daysRented)
    {
        return price.GetFrequentRenterPoints(daysRented);
    }
}

class Rental
{
    private Movie   movie;
    private int     daysRented;

    public Rental(Movie movie, int daysRented)
    {
        this.movie      = movie;
        this.daysRented = daysRented;
    }

    public int DaysRented
    {
        get { return daysRented; }
    }

    public Movie GetMovie()
    { return movie; }

    public int GetCharge()
    {
        return GetMovie().GetCharge(DaysRented);
    }

    public int GetFrequentRenterPoints()
    {
        return GetMovie().GetFrequentRenterPoints(DaysRented);
    }
}

class Customer
{
    private string      name;
    private ArrayList   rentals = new ArrayList();

    public Customer(string name)
    {
        this.name = name;
    }

    public string Name
    {
        get { return name; }
    }

    public void Add(Rental rental)
    {
        rentals.Add(rental);
    }

    // レシート生成
    public string Statement()
    {
        string  result = "レンタル記録: " + Name + "\n";

        foreach (Rental each in rentals)
        {
            // この貸し出しに関する数値の表示
            result      += "\t" + each.GetMovie().Title + "\t" + each.GetCharge() + "円\n";
        }
        // フッタ部分の追加
        result += "総額 " + GetTotalCharge() + "円\n";
        result += "今回の獲得レンタル ポイントは " + GetTotalFrequentRenterPoints() + " ポイントです.";

        return result;
    }

    // レシート生成 (HTML版)
    public string HtmlStatement()
    {
        string  result = "<h1>レンタル記録: <em>" + Name + "</em></h1>\n<p>\n";

        foreach (Rental each in rentals)
        {
            // この貸し出しに関する数値の表示
            result += each.GetMovie().Title + ": " + each.GetCharge() + "円<br>\n";
        }
        // フッタ部分の追加
        result += "<p>総額 <em>" + GetTotalCharge() + "</em>円<br>\n";
        result += "今回の獲得レンタル ポイントは <em>" + GetTotalFrequentRenterPoints() + "</em> ポイントです.\n</p>\n";

        return result;
    }

    private int GetTotalCharge()
    {
        int result = 0;
        foreach (Rental each in rentals)
        {
            result += each.GetCharge();
        }
        return result;
    }

    private int GetTotalFrequentRenterPoints()
    {
        int result = 0;
        foreach (Rental each in rentals)
        {
            // レンタル ポイントを加算
            result += each.GetFrequentRenterPoints();
        }
        return result;
    }
}

参考文献