ポリモーフィズムを使った図書館のサンプル

図書館のシステムを想定して、サンプルを考えてみましょう

継承を使う

new LibrarySystem().Run();

class Book
{
    public string Title { get; set; }
    public string Author { get; set; }
    public virtual void Print()
    {
        Console.WriteLine("Title: " + Title);
        Console.WriteLine("Author: " + Author);
    }
}

class PaperBook : Book
{
    public int Pages { get; set; }
    public override void Print()
    {
        base.Print();
        Console.WriteLine("Pages: " + Pages);
    }
}

class AudioBook : Book
{
    public double Duration { get; set; }
    public override void Print()
    {
        base.Print();
        Console.WriteLine("Duration: " + Duration + " minutes");
    }
}

class Library
{
    private List<Book> books = new List<Book>();
    public void AddBook(Book book)
    {
        books.Add(book);
    }
    public void PrintBooks()
    {
        foreach (var book in books)
        {
            book.Print();
        }
    }
}

class LibrarySystem
{
    Library library;

    public void Run()
    {
        library = new Library();
        library.AddBook(new PaperBook { Title = "The Catcher in the Rye", Author = "J.D. Salinger", Pages = 214 });
        library.AddBook(new AudioBook { Title = "The Catcher in the Rye", Author = "J.D. Salinger", Duration = 5.5 });
        library.PrintBooks();
    }
}

実行結果

Title: The Catcher in the Rye
Author: J.D. Salinger
Pages: 214
Title: The Catcher in the Rye
Author: J.D. Salinger
Duration: 5.5 minutes

このコードは、図書館のシステムを表しています。 Bookクラスが持つプロパティとメソッドを継承し、それぞれの本の特徴を表すPaperBookクラスとAudioBookクラスが定義されています。 Print()メソッドは、オーバーライドされて、本の特徴を出力するようになっています。 Libraryクラスは、本のリストを保持し、追加や出力を行うメソッドを持っています

インターフェイスを使う

new LibrarySystem().Run();

interface IPrintable
{
    void Print();
}

class Book : IPrintable
{
    public string Title { get; set; }
    public string Author { get; set; }
    public void Print()
    {
        Console.WriteLine("Title: " + Title);
        Console.WriteLine("Author: " + Author);
    }
}

class PaperBook : Book
{
    public int Pages { get; set; }
    public new void Print()
    {
        base.Print();
        Console.WriteLine("Pages: " + Pages);
    }
}

class AudioBook : Book
{
    public double Duration { get; set; }
    public new void Print()
    {
        base.Print();
        Console.WriteLine("Duration: " + Duration + " minutes");
    }
}

class Library
{
    private List<IPrintable> books = new List<IPrintable>();
    public void AddBook(IPrintable book)
    {
        books.Add(book);
    }
    public void PrintBooks()
    {
        foreach (var book in books)
        {
            book.Print();
        }
    }
}

class LibrarySystem
{
    Library library;

    public void Run()
    {
        library = new Library();
        library.AddBook(new PaperBook { Title = "The Catcher in the Rye", Author = "J.D. Salinger", Pages = 214 });
        library.AddBook(new AudioBook { Title = "The Catcher in the Rye", Author = "J.D. Salinger", Duration = 5.5 });
        library.PrintBooks();
    }
}

実行結果

Title: The Catcher in the Rye
Author: J.D. Salinger
Pages: 214
Title: The Catcher in the Rye
Author: J.D. Salinger
Duration: 5.5 minutes

このコードでは、インターフェイスIPrintableを定義し、Bookクラス、PaperBookクラス、AudioBookクラスがそれを実装しています。 Libraryクラスは、IPrintable型のリストを保持し、追加や出力を行うメソッドを持っています。これにより、派生クラスにインターフェイスを実装することで多態性を実現することができます。

インターフェイスを使用することで、複数のクラスに共通の機能を実装することができ、多態性を実現することができます。また、インターフェイスは「契約」として扱うことができ、それを実装することでクラスが持つべき機能を明確にすることができます

抽象クラスを使う

new LibrarySystem().Run();

abstract class Book
{
    public string Title { get; set; }
    public string Author { get; set; }
    public abstract void Print();
}

class PaperBook : Book
{
    public int Pages { get; set; }
    public override void Print()
    {
        Console.WriteLine("Title: " + Title);
        Console.WriteLine("Author: " + Author);
        Console.WriteLine("Pages: " + Pages);
    }
}

class AudioBook : Book
{
    public double Duration { get; set; }
    public override void Print()
    {
        Console.WriteLine("Title: " + Title);
        Console.WriteLine("Author: " + Author);
        Console.WriteLine("Duration: " + Duration + " minutes");
    }
}

class Library
{
    private List<Book> books = new List<Book>();
    public void AddBook(Book book)
    {
        books.Add(book);
    }
    public void PrintBooks()
    {
        foreach (var book in books)
        {
            book.Print();
        }
    }
}

class LibrarySystem
{
    Library library;

    public void Run()
    {
        library = new Library();
        library.AddBook(new PaperBook { Title = "The Catcher in the Rye", Author = "J.D. Salinger", Pages = 214 });
        library.AddBook(new AudioBook { Title = "The Catcher in the Rye", Author = "J.D. Salinger", Duration = 5.5 });
        library.PrintBooks();
    }
}

実行結果

Title: The Catcher in the Rye
Author: J.D. Salinger
Pages: 214
Title: The Catcher in the Rye
Author: J.D. Salinger
Duration: 5.5 minutes

このコードは、抽象クラスを使用して、同じように図書館のシステムを表しています。 Bookクラスが抽象メソッドPrint()を定義し、PaperBookクラス、AudioBookクラスがそれを実装しています。 Libraryクラスは、Book型のリストを保持し、追加や出力を行うメソッドを持っています。この方法でも、多態性を実現することができます。

抽象クラスはインターフェイスと同じように多態性を実現するために使用できますが、抽象クラスには実装を持ったメソッドやプロパティがあります。インターフェイスは抽象メソッドやプロパティだけを持つため、実装の詳細を隠蔽し、抽象的なインターフェースを提供することができます。

抽象クラスインターフェイスを組み合わせて使う

new LibrarySystem().Run();

interface IPrintable
{
    void Print();
}

abstract class Book : IPrintable
{
    public string Title { get; set; }
    public string Author { get; set; }
    public abstract void Print();
}

class PaperBook : Book
{
    public int Pages { get; set; }
    public override void Print()
    {
        Console.WriteLine("Title: " + Title);
        Console.WriteLine("Author: " + Author);
        Console.WriteLine("Pages: " + Pages);
    }
}

class AudioBook : Book
{
    public double Duration { get; set; }
    public override void Print()
    {
        Console.WriteLine("Title: " + Title);
        Console.WriteLine("Author: " + Author);
        Console.WriteLine("Duration: " + Duration + " minutes");
    }
}

class Library
{
    private List<IPrintable> books = new List<IPrintable>();
    public void AddBook(IPrintable book)
    {
        books.Add(book);
    }
    public void PrintBooks()
    {
        foreach (var book in books)
        {
            book.Print();
        }
    }
}

class LibrarySystem
{
    Library library;

    public void Run()
    {
        library = new Library();
        library.AddBook(new PaperBook { Title = "The Catcher in the Rye", Author = "J.D. Salinger", Pages = 214 });
        library.AddBook(new AudioBook { Title = "The Catcher in the Rye", Author = "J.D. Salinger", Duration = 5.5 });
        library.PrintBooks();
    }
}

実行結果

Title: The Catcher in the Rye
Author: J.D. Salinger
Pages: 214
Title: The Catcher in the Rye
Author: J.D. Salinger
Duration: 5.5 minutes

このコードでは、Bookクラスを抽象クラスにし、IPrintableインターフェイスを実装しています。派生クラスのPaperBookクラス、AudioBookクラスもIPrintableインターフェイスを実装しています。 抽象クラスはインスタンスを生成することができないが、仮想メソッドを持っており、派生クラスにて実装しなければならないメソッドがある。 このように抽象クラスとインターフェイスを組み合わせることで、共通の機能を抽象クラスで定義し、派生クラスにて実装することで多態性を実現することができます。

抽象クラスはインターフェイスと同じように多態性を実現するために使用できますが、抽象クラスには実装を持ったメソッドやプロパティがあります。インターフェイスは抽象メソッドやプロパティだけを持つため、実装の詳細を隠蔽し、抽象的なインターフェースを提供することができます。

ファクトリーパターンで考える

new LibrarySystem().Run();

abstract class Book
{
    public string Title { get; set; }
    public string Author { get; set; }
    public abstract void Print();
}

class PaperBook : Book
{
    public int Pages { get; set; }
    public override void Print()
    {
        Console.WriteLine("Title: " + Title);
        Console.WriteLine("Author: " + Author);
        Console.WriteLine("Pages: " + Pages);
    }
}

class AudioBook : Book
{
    public double Duration { get; set; }
    public override void Print()
    {
        Console.WriteLine("Title: " + Title);
        Console.WriteLine("Author: " + Author);
        Console.WriteLine("Duration: " + Duration + " minutes");
    }
}

class BookFactory
{
    public static Book CreateBook(string type)
    {
        switch (type)
        {
            case "paper":
                return new PaperBook();
            case "audio":
                return new AudioBook();
            default:
                throw new ArgumentException("Invalid book type.");
        }
    }
}

class Library
{
    private List<Book> books = new List<Book>();
    public void AddBook(Book book)
    {
        books.Add(book);
    }
    public void PrintBooks()
    {
        foreach (var book in books)
        {
            book.Print();
        }
    }
}

class LibrarySystem
{
    Library library;

    public void Run()
    {
        library = new Library();
        Book paperBook = BookFactory.CreateBook("paper");
        paperBook.Title = "The Catcher in the Rye";
        paperBook.Author = "J.D. Salinger";
        ((PaperBook)paperBook).Pages = 214;
        library.AddBook(paperBook);

        Book audioBook = BookFactory.CreateBook("audio");
        audioBook.Title = "The Catcher in the Rye";
        audioBook.Author = "J.D. Salinger";
        ((AudioBook)audioBook).Duration = 5.5;
        library.AddBook(audioBook);

        library.PrintBooks();
    }
}

実行結果

Title: The Catcher in the Rye
Author: J.D. Salinger
Pages: 214
Title: The Catcher in the Rye
Author: J.D. Salinger
Duration: 5.5 minutes

LibrarySystemメソッドでは、BookFactoryクラスのCreateBookメソッドを使用して、PaperBookクラスとAudioBookクラスのインスタンスを生成しています。生成したインスタンスは、Bookクラスの変数に代入され、各プロパティに対して値を設定しています。最後に、LibraryクラスのAddBookメソッドを使用して、生成したインスタンスを追加し、PrintBooksメソッドを呼び出すことで書籍の一覧を出力します。

これにより、インスタンス生成の責務をBookFactoryクラスに移すことで、LibrarySystemメソッドからはインスタンス生成に関する詳細を除外することができ、コードの可読性を高めることができます。

オブザーバーパターンで考える

new LibrarySystem().Run();

interface IObserver
{
    void Update();
}

interface IPrintable
{
    void Print();
}

abstract class Book : IPrintable
{
    public string Title { get; set; }
    public string Author { get; set; }
    public abstract void Print();
}

class PaperBook : Book
{
    public int Pages { get; set; }
    public override void Print()
    {
        Console.WriteLine("Title: " + Title);
        Console.WriteLine("Author: " + Author);
        Console.WriteLine("Pages: " + Pages);
    }
}

class AudioBook : Book
{
    public double Duration { get; set; }
    public override void Print()
    {
        Console.WriteLine("Title: " + Title);
        Console.WriteLine("Author: " + Author);
        Console.WriteLine("Duration: " + Duration + " minutes");
    }
}

class Library
{
    private List<IObserver> observers = new List<IObserver>();
    private List<IPrintable> books = new List<IPrintable>();
    public void AddObserver(IObserver observer)
    {
        observers.Add(observer);
    }
    public void AddBook(IPrintable book)
    {
        books.Add(book);
        NotifyObservers();
    }
    public void NotifyObservers()
    {
        foreach (var observer in observers)
        {
            observer.Update();
        }
    }
    public void PrintBooks()
    {
        foreach (var book in books)
        {
            book.Print();
        }
    }
}

class PrintDisplay : IObserver
{
    public void Update()
    {
        Console.WriteLine("New book added to the library");
    }
}

class LibrarySystem
{
    Library library;

    public void Run()
    {
        library = new Library();
        PrintDisplay display = new PrintDisplay();
        library.AddObserver(display);
        library.AddBook(new PaperBook { Title = "The Catcher in the Rye", Author = "J.D. Salinger", Pages = 214 });
        library.AddBook(new AudioBook { Title = "The Catcher in the Rye", Author = "J.D. Salinger", Duration = 5.5 });
        library.PrintBooks();
    }
}

実行結果

New book added to the library
New book added to the library
Title: The Catcher in the Rye
Author: J.D. Salinger
Pages: 214
Title: The Catcher in the Rye
Author: J.D. Salinger
Duration: 5.5 minutes

このコードは、図書館の図書を表すクラスを使用しています。オブザーバーパターンに準拠しているため、図書館が図書を追加するたびに、登録されているオブザーバーに通知が送られます。

インターフェイス IObserver は、通知を受け取るオブジェクトが実装する必要があるメソッド Update() を定義しています。

インターフェイス IPrintable は、印刷可能なオブジェクトが実装する必要があるメソッド Print() を定義しています。

抽象クラス Book は、タイトルと著者を持つ印刷可能なオブジェクトを表し、 Print() メソッドを抽象メソッドとして定義しています。

クラス PaperBook は、抽象クラス Book を継承し、ページ数を追加し、 Print() メソッドをオーバーライドしています。

クラス AudioBook は、抽象クラス Book を継承し、再生時間を追加し、 Print() メソッドをオーバーライドしています。

クラス Libraryは、オブザーバーを登録するためのAddObserver()メソッドと、図書館に図書を追加するための AddBook() メソッドを持ち、オブザーバーに通知を送る NotifyObservers() メソッドを持っています。

クラス PrintDisplay は、 IObserver インターフェイスを実装し、 Update() メソッドをオーバーライドして、新しい本が追加されたことを表示するようにしています。

Main()メソッドでは、Library クラスのインスタンス、PrintDisplay クラスのインスタンスを作成し、それらを使用して、本を追加し、表示しています。

※上記のコードは新しい本が追加された際に一覧表示が出るだけなので、実際には不要な部分もあります。

DIパターンで考える

インターファイスを使って説明しています
上記インターフェイスパターンと同様なことがわかると思います

DIパターンに変更するためには、以下のようにします。

依存性の注入に使用するインターフェイスを定義します。

interface IBook 
{
    void Print();
}

Book, PaperBook, AudioBookクラスは、IBookインターフェイスを継承します。

class Book : IBook
 {
    public string Title { get; set; }
    public string Author { get; set; }
    public void Print() 
    {
        Console.WriteLine("Title: " + Title);
        Console.WriteLine("Author: " + Author);
    }
}

class PaperBook : IBook 
{
    public int Pages { get; set; }
    public void Print() 
    {
        Console.WriteLine("Title: " + Title);
        Console.WriteLine("Author: " + Author);
        Console.WriteLine("Pages: " + Pages);
    }
}

class AudioBook : IBook 
{
    public double Duration { get; set; }
    public void Print() 
    {
        Console.WriteLine("Title: " + Title);
        Console.WriteLine("Author: " + Author);
        Console.WriteLine("Duration: " + Duration + " minutes");
    }
}

Libraryクラスには、IBookインターフェイスを使用して、依存性を注入します。

class Library 
{
    private List<IBook> books = new List<IBook>();
    public void AddBook(IBook book) 
    {
        books.Add(book);
    }
    public void PrintBooks() 
    {
        foreach (var book in books)
        {
            book.Print();
        }
    }
}

LibrarySystemで、実際のBook, PaperBook, AudioBookオブジェクトを注入します

new LibrarySystem().Run();

class LibrarySystem
{
    Library library;

    public void Run()
    {
        library = new Library();
        library.AddBook(new PaperBook { Title = "The Catcher in the Rye", Author = "J.D. Salinger", Pages = 214 });
        library.AddBook(new AudioBook { Title = "The Catcher in the Rye", Author = "J.D. Salinger", Duration = 5.5 });
        library.PrintBooks();
    }
}

このように、DIパターンを使用することで、Libraryクラスが依存しているオブジェクトを実装に関係なく、外部から注入することができるようになります。

実行結果

Title: The Catcher in the Rye
Author: J.D. Salinger
Title: The Catcher in the Rye
Author: J.D. Salinger

C#,学習,設計

Posted by hidepon