Help us understand the problem. What is going on with this article?

C#で学ぶデザインパターン入門 ⑫Decorator Pattern

More than 3 years have passed since last update.

概要

@hyuki 先生著の『Javaで学ぶデザインパターン入門』(2004年、SB Creative)の1章ずつをベースに、サンプルコードをC#で置き換えながら勉強していく記事です。

※著者の @hyuki 先生には適切に書籍への参照を入れれば問題ない旨ご確認いただいています。

本題

Decoratorパターン
第12回はDecoratorパターンです。Decoratorパターンは「外枠(飾り、Decoration)を再帰的に追加することで核となる部分の機能を追加していく」デザインパターンです。

サンプルコード

早速具体的な事例を見てみましょう。『Javaで学ぶデザインパターン入門』(2004年、SB Creative)に掲載されているコードをC#で(大体)書き換えます。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace DecoratorPattern
{
    class Program
    {
        static void Main(string[] args)
        {
            Display b1 = new StringDisplay("Hello, world.");
            Display b2 = new SideBorder(b1, '#');
            Display b3 = new FullBorder(b2);
            b1.Show();
       // => Hello, world.
            b2.Show();
            // => #Hello, world.#
            b3.Show();
            // => 
            // +---------------+
            // |#Hello, world.#|
            // +---------------+
            Display b4 =
                new SideBorder(
                    new FullBorder(
                        new SideBorder(
                            new FullBorder(
                                new StringDisplay("こんにちは。")
                                ),
                                '*')
                            ),
                            '/'
                        );
            b4.Show();
            // =>
            // /+----------------+/
            // /|*+------------+*|/
            // /|*|こんにちは。|*|/
            // /|*+------------+*|/
            // /+----------------+/

            // 実行が一瞬で終わって確認できないので、キーの入力を待ちます
            Console.ReadLine();
        }
    }

    // Component
    // ・機能を追加するときの核
    public abstract class Display
    {
        public abstract int Columns { get; }
        public abstract int Rows { get; }
        public abstract string GetRowText(int row);
        public void Show()
        {
            for(int i = 0; i < Rows; i++)
            {
                Console.WriteLine(GetRowText(i));
            }
        }
    }

    // ConcreteComponent
    // ・Componentを実装
    public class StringDisplay : Display
    {
        private string str;
        public StringDisplay(string str)
        {
            this.str = str;
        }
        public override int Columns
        {
            get
            {
                Encoding sjisEnc = Encoding.GetEncoding("shift_jis");
                return sjisEnc.GetByteCount(str);
            }
        }

        public override int Rows
        {
            get { return 1; }
        }

        public override string GetRowText(int row)
        {
            if (row == 0)
            {
                return str;
            }
            else
            {
                return null;
            }
        }
    }

    // Decorator
    // ・Componentと同じインターフェース(API)を持つ
    // ・飾る対象となるComponentを持つ
    public abstract class Border : Display
    {
        protected Display display;
        protected Border(Display display)
        {
            this.display = display;
        }
    }

    // ConcreteDecorator
    // ・Decoratorを実装
    public class SideBorder : Border
    {
        private char borderChar;
        public SideBorder(Display display, char ch) : base(display)
        {
            this.borderChar = ch;

        }
        public override int Columns
        {
            get
            {
                return 1 + display.Columns + 1;
            }
        }

        public override int Rows
        {
            get
            {
                return display.Rows;
            }
        }

        public override string GetRowText(int row)
        {
            return borderChar + display.GetRowText(row) + borderChar;
        }
    }

    // ConcreteDecorator
    // ・Decoratorを実装
    public class FullBorder : Border
    {
        public FullBorder(Display display) : base(display) { }
        public override int Columns
        {
            get
            {
                return 1 + display.Columns + 1;
            }
        }

        public override int Rows
        {
            get
            {
                return 1 + display.Rows + 1;
            }
        }

        public override string GetRowText(int row)
        {
            if (row == 0)
            {
                return "+" + MakeLine('-', display.Columns) + "+";
            }
            else if (row == display.Rows + 1)
            {
                return "+" + MakeLine('-', display.Columns) + "+";
            }
            else
            {
                return "|" + display.GetRowText(row - 1) + "|";
            }
        }

        private string MakeLine(char ch, int count)
        {
            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < count; i++)
            {
                sb.Append(ch);
            }
            return sb.ToString();
        }
    }
}

効能

  • 包まれるものを変更することなく機能を追加することができる
  • 委譲によりクラス間がゆるく結合しているため、フレームワークのソースを変更することなくオブジェクト間の関係を変えた新しいオブジェクトを作ることができる
  • ConcreteDecoratorを増やせば多様な機能を追加できる

使用上の注意

  • よく似ている小さなクラスができてしまう

関連しているパターン

感想、疑問、メモ

  • 飾り枠を使って中身を隠してもインターフェース(サンプルではColumns、Rows、GetRowText())は隠されていない。この状態を「インターフェース(API)が透過的である」という。
  • javaの標準クラスだとjava.ioというパッケージにDecoratorパターンが採用されている。
  • 機能拡張 = 継承、の前に検討するとよいかもしれません。

C#で学ぶデザインパターン入門

①Iterator
②Adapter
③Template Method
④Factory Method
⑤Singleton
⑥Prototype
⑦Builder
⑧AbstractFactory
⑨Bridge
⑩Strategy Pattern
⑪Composite Pattern
⑫Decorator Pattern

toshi0607
最近はGoとServerlessが好きです!
https://toshi0607.com/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした