「HeadFirstデザインパターン」と「Rubyによるデザインパターン」を読んで Decorator パターン

何番煎じか判りませんがお勉強メモを残します

「[HeadFirstデザインパターン](https://www.amazon.co.jp/dp/4873112494)」第3章
「[Rubyによるデザインパターン](https://www.amazon.co.jp/dp/4894712857)」第11章

# Decorator パターン

#### 「HeadFirstデザインパターン」でのJavaコードは（だいたい）こんな感じ

- コーヒー屋の料金計算
- 飲み物の種類は ハウスブレンド、ダークロースト、カフェイン抜き、エスプレッソ

```java
// 全ての飲み物の抽象クラス
public abstract class Beverage {
  String description = "";

  public String getDescription {
    return description;
  }

  public abstract double cost();
}
```

```java
public class Espresso extends Beverage {
  public Espresso() {
    description = "エスプレッソ";
  }

  public double cost() {
    return 1.99;
  }
}

public class HouseBlend extends Beverage {
  public HouseBlend() {
    description = "ハウスブレンド";
  }

  public double cost() {
    return 0.89;
  }
}
```

- トッピングもあります スチームミルク、豆乳、モカ、ホイップクリーム 等です
- あ、それらを組み合わせる事も出来なければならないんでした

```java
public class EspressoWithSteamedMilk extends Beverage {
  public Espresso() {
    description = "エスプレッソ スチームミルク";
  }

  public double cost() {
    return 1.99 + 0.1;
  }
}

public class EspressoWithSteamedMilkAndMocha extends Beverage {
  public Espresso() {
    description = "エスプレッソ スチームミルク モカ";
  }

  public double cost() {
    return 1.99 + 0.1 + 0.2;
  }
}

public class EspressoWithSteamedMilkAndSoy extends Beverage {
  public Espresso() {
    description = "エスプレッソ スチームミルク 豆乳";
  }

  public double cost() {
    return 1.99 + 0.1 + 0.15;
  }
}

public class EspressoWithSteamedMilkAndSoyAndWhip extends Beverage {
  public Espresso() {
    description = "エスプレッソ スチームミルク 豆乳 ホイップ";
  }

  public double cost() {
    return 1.99 + 0.1 + 0.15 + 0.1;
  }
}
```

### ちょっと待って！これどう考えてもヤバイだろ！

- 違う、そうじゃない
- Beberageクラスにトッピング情報を持たせればいいじゃない

```java
public abstract class Beverage {
  String description = "";
  Boolean milk  = false;
  Boolean soy   = false;
  Boolean mocha = false;
  Boolean whip  = false;

  public String getDescription {
    return description;
  }

  public setMilk() {
    milk = true;
  }

  public hasMilk() {
    milk;
  }

  public setSoy() {
    soy = true;
  }

  public hasSoy() {
    soy;
  }

  public double cost() {
    float condimentCost = 0.0;

    if (hasMilk()) {
      condimentCost += 0.1;
    }

    if (hasWhip()) {
      condimentCost += 0.1;
    }

    return condimentCost;
  }
}
```

```java
public class Espresso extends Beverage {
  public Espresso() {
    description = "エスプレッソ" + condimentDescriptions();
  }

  public double cost() {
    1.99 + super.cost();
  }
}
```

### これも微妙じゃね？

- 違う、そうじゃない
- ダブルモカとかどう表現するの？
- あとで飲み物やトッピングが増えたり値段が変わったらどうするの？各所それぞれ変更しないといけないの？

- 継承を使って飲み物とトッピングの価格を表すのはうまく機能しない
- クラス爆発・硬直した設計になる

- ではどうすればいいの
- Decoratorパターンでしょ

```java
// 飲み物抽象クラス
public abstract class Beverage {
  String description = "";

  public String getDescription() {
    return description;
  }

  public abstract double cost();
}

// トッピング抽象クラス
public abstract class CondimentDecorator extends Beverage {
  public abstract String getDescription();
}
```

```java
// 飲み物
public class Espresso extends Beverage {
  public Espresso() {
    description = "エスプレッソ";
  }

  public double cost() {
    return 1.99;
  }
}

public class HouseBlend extends Beverage {
  public HouseBlend() {
    description = "ハウスブレンド　";
  }

  public double cost() {
    return 0.89;
  }
}
```

```java
// トッピング
public class Mocha extends CondimentDecorator {
  Beverage beverage;

  public Mocha(Beverage beverage) {
    this.beverage = beverage;
  }

  public String getDescription() {
    return beverage.getDescription() + " モカ";
  }

  public double cost() {
    return beverage.cost() + 0.2;
  }
}

public class Whip extends CondimentDecorator {
  Beverage beverage;

  public Whip(Beverage beverage) {
    this.beverage = beverage;
  }

  public String getDescription() {
    return beverage.getDescription() + " ホイップ";
  }

  public double cost() {
    return beverage.cost() + 0.1;
  }
}
```

```java
Beverage beverage = new Espresso();
print(beverage.getDescription() + " $" + beverage.cost());

//=> エスプレッソ $1.99

Beverage beverage2 = new HouseBlend();
beverage2 = new Mocha(beverage2);
beverage2 = new Mocha(beverage2);
beverage2 = new Whip(beverage2);
print(beverage2.getDescription() + " $" + beverage2.cost());

//=> ハウスブレンド モカ モカ ホイップ $1.39
```

- Decoratorパターンはオブジェクトに付加的な責務を動的に(実行時に)付与、柔軟な機能拡張手段を提供、サブクラス化の代替手段となる
- CondimentDecorator(トッピング)クラスはBeverage(飲み物)クラスを継承している。継承は使うなって話じゃなかったの？ => ここでは型を一致させる為に継承を使っている。振る舞いは一切取得していない
- インタフェースを使えばよかったのかも(私見)
- 今回の例で言うと、トッピングが追加されたり値段が変更された際も、その部分の修正だけでよい

#### 「Rubyによるデザインパターン」でのRubyコードは（だいたい）こんな感じ

- オブジェクトの責務を変えたい、あるときは追加で必要になり、あるときは減らす、どうすればよいのか？
- Decoratorパターンを使うとレイヤ状に機能を積み重ねていく事ができ、それぞれの状況で必要なだけの機能を持つオブジェクトを作る事が出来る

ファイルにテキストを書き出す必要があるとします。そのシステムでは

- プレインテキストを出力したい
- 行番号を付与したい
- タイムスタンプを付与したい

時もある、そうじゃない時もある、それらを組み合わせたフォーマットで出力したいんだそうです

とりあえず、これらの要求に応える為に、適当なFile::IOを内部に持ったオブジェクトを作ってみます

```ruby
class EnhancedWriter
  def initialize(path=nil)
    path ||= 'delete_me.txt'
    @file = File.open(path, 'w')
    @line_number = 1
  end

  def write_line(line)
    @file.puts(line)
  end

  def timestamping_write_line(line)
    write_line("#{Time.now}: #{line}")
  end

  def numbering_write_line(line)
    write_line("#{@line_number}: #{line}")
    @line_number += 1
  end

  def close
    @file.close
  end
end
```

```ruby
e = EnhancedWriter.new
e.write_line('line_1')
e.write_line('line_2')
e.close

puts File.read('delete_me.txt')
# => line_1
# => line_2
```

```ruby
e = EnhancedWriter.new
e.timestamping_write_line('line_1')
e.timestamping_write_line('line_2')
e.close

puts File.read('delete_me.txt')
#=> 2020-01-23 17:01:08 +0900: line_1
#=> 2020-01-23 17:01:08 +0900: line_2
```

```ruby
e = EnhancedWriter.new
e.numbering_write_line('line_1')
e.numbering_write_line('line_2')
e.close

puts File.read('delete_me.txt')
#=> 1: line_1
#=> 2: line_2
```

- フォーマットを組み合わせるにはどうする、フォーマットが増えたらどうなる？その組み合わせ数は爆発するだろう
- クライアントは今から出力するテキストが行番号付きなのか、タイムスタンプ付きなのかを把握していなければならない、そのメソッドを間違いなく呼び出す必要がある
- 1つのクラスに全て突っ込んでいるこのクラスの問題は、全てを1つのクラスに詰め込んでいるまさにそのことにある
- 継承ベースのアプローチでは、あり得る全ての機能の組み合わせを設計時に考えなければならない

- より良い解決策は、必要な機能の組み合わせを動的に、実行時に組み立てるようにする事
- よろしくDecorator

```ruby
class SimpleWriter
  def initialize(path=nil)
    path ||= 'delete_me.txt'
    @file = File.open(path, 'w')
  end

  def write_line(line)
    @file.puts(line)
  end

  def close
    @file.close
  end
end

class NumberingWriter
  def initialize(real_writer)
    @real_writer = real_writer
    @line_number = 1
  end

  def write_line(line)
    @real_writer.write_line("#{@line_number}: #{line}")
    @line_number += 1
  end

  def close
    @real_writer.close
  end
end

class TimeStampWriter
  def initialize(real_writer)
    @real_writer = real_writer
  end

  def write_line(line)
    @real_writer.write_line("#{Time.now}: #{line}")
  end

  def close
    @real_writer.close
  end
end
```

```ruby
w = SimpleWriter.new
w.write_line('line_1')
w.write_line('line_2')
w.close

puts File.read('delete_me.txt')
#=> line_1
#=> line_2
```

```ruby
w = SimpleWriter.new
w = NumberingWriter.new(w)
w.write_line('line_1')
w.write_line('line_2')
w.close

puts File.read('delete_me.txt')
#=> 1: line_1
#=> 2: line_2
```

```ruby
w = SimpleWriter.new
w = TimeStampWriter.new(w)
w.write_line('line_1')
w.write_line('line_2')
w.close

puts File.read('delete_me.txt')
#=> 2020-01-23 17:01:08 +0900: line_1
#=> 2020-01-23 17:01:08 +0900: line_2
```

```ruby
w = SimpleWriter.new
w = NumberingWriter.new(w)
w = TimeStampWriter.new(w)
w.write_line('line_1')
w.write_line('line_2')
w.close

puts File.read('delete_me.txt')
#=> 1: 2020-01-23 17:01:08 +0900: line_1
#=> 2: 2020-01-23 17:01:08 +0900: line_2
```

- 全てのデコレータオブジェクトにオリジナルと同じ基本的なインタフェースを持たせたので、デコレータに提供する"real_writer"が実際にSimpleWriterのインスタンスである必要はない
- つまり、好きな長さ、順番のデコレータチェーンを作れると言うこと

ちなみにRubyなら module と extend を使った方法もありえる

```ruby
class SimpleWriter
  # 変更なし
  def initialize(path=nil)
    path ||= 'delete_me.txt'
    @file = File.open(path, 'w')
  end

  def write_line(line)
    @file.puts(line)
  end

  def close
    @file.close
  end
end

module TimeStampWriter
  def write_line(line)
    super("#{Time.now}: #{line}")
  end
end

module NumberingWriter
  attr_reader :line_number

  def write_line(line)
    @line_number ||= 1
    super("#{@line_number}: #{line}")
    @line_number += 1
  end
end
```

```ruby
w = SimpleWriter.new
w.extend(NumberingWriter)
w.write_line('line_1')
w.write_line('line_2')
w.close

puts File.read('delete_me.txt')
#=> 1: line_1
#=> 2: line_2
```

```ruby
w = SimpleWriter.new
w.extend(TimeStampWriter)
w.write_line('line_1')
w.write_line('line_2')
w.close

puts File.read('delete_me.txt')
#=> 2020-01-23 17:01:08 +0900: line_1
#=> 2020-01-23 17:01:08 +0900: line_2
```

```ruby
w = SimpleWriter.new
w.extend(TimeStampWriter)
w.extend(NumberingWriter)
w.write_line('line_1')
w.write_line('line_2')
w.close

puts File.read('delete_me.txt')
#=> 2020-01-23 17:01:08 +0900: 1: line_1
#=> 2020-01-23 17:01:08 +0900: 2: line_2
```

- ああ、これはすごくいい感じがしますね(私見)
- このような動的なテクニックには1つ欠点があるかも知れません Rubyでは追加したモジュールを取り除く事ができない為です