0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

デザインパターン Decoratorパターン

Last updated at Posted at 2023-10-09

Decoratorパターン

Decoratorパターンは、継承の代わりにコンポジションを使用するデザインパターン。

継承の代替手段として知られ、継承を利用するよりも柔軟に機能追加を行うことができる。継承を避けたい場合に特に有用

継承がコンパイル時に機能を追加しているのに対して、Decoratorパターンはプログラムの実行時に機能を追加しているため、クラスの変更が最小限に抑えられ、柔軟性が向上する。

具体的には、オブジェクトをラップし、親クラスへの参照を保持したクラスを利用することで、ラップするごとに独自の機能を追加していくことができる。

ランタイム時に機能を追加することができ、これによって機能を拡張させることができる。

コードが複雑になるという欠点を持つ。

java.ioパッケージのInputStreamに関連するものや、UIフレームワークで利用されている。

Decoratorパターンが利用できるシチュエーション

バーガーショップの注文システムを作成するとする。

バーガーは「ハンバーガー」「チーズバーガー」の2種類があり、その他オプションとして「ポテト」「サラダ」があるとして、オプションを追加したときの値段を求めるシステムとする。

必要な抽象クラス

場合によっては抽象クラスではなく、インターフェースを用いる場合もある。
decorator.png

ここで重要なのはToppingsDecoratorは、BurgerComponentの機能を利用するために継承しているのではなく、同等に扱えるようにするために継承を行っているという点。

ToppingsDecoratorBurgerComponentには「is - a」ではなく「has - a」の関係がある。

BurgerComponent.java
public abstract class BurgerComponent {
    // クライアントはcost()だけ知っていれば良い
    public abstract int cost();
}
ToppingsDecorator.java
// Decoratorパターンでは、「同じ型として扱うための継承」を行う
// Decorator is Component. ではないが、Decorator has Component. の関係になっている
public abstract class ToppingsDecorator extends BurgerComponent {

    // Componentへの参照を保持する
    BurgerComponent burger;

    // Component を Decorator がラップする
    public ToppingsDecorator(BurgerComponent burger) {
        this.burger = burger;
    }

    public abstract int cost();
}

抽象クラスの継承

Componentを継承したクラス

Hamburger.java
// ハンバーガーは Decorator ではなく、 Component
public class Hamburger extends BurgerComponent {

    int price;

    public Hamburger(){
        price = 120;
    }

    @Override
    public int cost() {
        return price;
    }
}
CheeseBurger.java
// チーズバーガーも Decorator ではなく、 Component
public class CheeseBurger extends BurgerComponent {

    int price;

    public CheeseBurger() {
        price = 120;
    }

    @Override
    public int cost() {
        return price;
    }
}

Decoratorを継承したクラス

Potato.java
// ポテトは Decorator
public class Potato extends ToppingsDecorator {

    int price;

    public Potato(BurgerComponent burger){
       // Component を Decorator でラップする
       super(burger);
       price = 50;
    }

    @Override
    public int cost() {
        // Decoratorが保持するComponentへの参照を利用して、機能を追加できる
        return burger.cost() + price;
    }
}
Salad.java
// サラダも Decorator
public class Salad extends ToppingsDecorator {
   
    int price;

    public Salad(BurgerComponent burger) {
       // Component を Decorator でラップする
        super(burger);
        price = 70;
    }

    @Override
    public int cost() {
        // Decoratorが保持するComponentへの参照を利用して、機能を追加できる
        return burger.cost() + price;
    }
}

Decoratorパターンの利用

クライアントは、Componentcost()を使えることだけを知っている。

Main.java
public class Main {
    public static void main(String[] args) {
        // オプションをつけない注文
        BurgerComponent burger1 = new Hamburger();
        System.out.println("It's " + burger1.cost() + "yen");
        // >> It's 120yen

        // Component を Decorator でラップする
        BurgerComponent burger2 = new Salad(new Hamburger());
        System.out.println("It's " + burger2.cost() + "yen");
        // >> It's 190yen

        // 好きなだけラップできる
        BurgerComponent burger3 = new Potato(
                                    new Potato(
                                        new Salad(
                                            new Hamburger())));
        System.out.println("It's " + burger3.cost() + "yen");
        // >> It's 290yen
    }
}

ポイント

  • Decoratorパターンでの機能の追加は、オブジェクトが保持している(has a)親クラスの処理の前後に、追加したい処理を加えることで行う。
  • Decoratorによるラップは、いくらでも(何層でも)行うことができる。
  • クライアントから見た時、Componentの実装を気にする必要がない(cost()というメソッドを持つことさえ知っていれば良い = 透過的
    )。
  • Factoryパターンや、Builderパターンと併用されることが多い。
  • HamburgerCheeseBurgerなどのクラス(Componentを継承したクラス)や、SaladPotatoなどの小さなクラス(Decoratorを継承したクラス)が大量に作成される可能性があり、コードが複雑になることがある。

参考

Head Firstデザインパターン ―頭とからだで覚えるデザインパターンの基本

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?