Factory (継承)と Strategy (移譲)の混合例


## 概要

次の 2つを混合させた「実装例」. 実践に近い気がする.[^1]

・基底クラスを継承をした構造 (Factory Method)
・"関数ポインタ" のみを継承した構造 (Strategy Pattern)

引用元は下記書籍である.

ただし、内容について細かく記すと書籍の無断転載になるので大まかに記している.
また、コードは Ruby から Python に書き換えている.

|引用元情報|一言|
|:--|:--|
|[書籍 -- Rubyによるデザインパターン (Russ Olsen 著)](https://www.amazon.co.jp/Ruby%E3%81%AB%E3%82%88%E3%82%8B%E3%83%87%E3%82%B6%E3%82%A4%E3%83%B3%E3%83%91%E3%82%BF%E3%83%BC%E3%83%B3-Russ-Olsen/dp/4894712857)|[原書(英文)](https://www.amazon.com/gp/product/0321490452/ref=as_li_ss_tl?ie=UTF8&tag=eloqruby-20&linkCode=as2&camp=217145&creative=399349&creativeASIN=0321490452)|
|[GitHub -- 著者 Russ Olsen 氏](https://github.com/russolsen/design_patterns_in_ruby_code/tree/master/01)||



## コード例

### ファイル構成

```bash
.
|-- ex3_vehicle.py
|-- ex5_engine.py
|-- ex7_diesel.py
|-- ex7_gasoline.py
|-- ex8_delegate.py
`-- ex8_delegate_test.py
```

## クラス図

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1246056/099c3fb0-8434-5590-b72c-8496aa998b4c.png)



## 実装方針

### ./ex3_vehicle.py

基底クラス

```python
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
import sys; sys.dont_write_bytecode = True

class Vehicle(object):
  def __init__(self, weight):
    self.weight = weight
    print(f'This car weighs {self.weight}kg')

```

### ./ex8_delegate.py

初期状態では ガソリンエンジンを使用し、`switch_to_diesel` で ディーゼルエンジンに切り替える.

```python
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
import sys; sys.dont_write_bytecode = True
from ex3_vehicle import *
from ex7_gasoline import *
from ex7_diesel import *

class Car(Vehicle):
  def __init__(self, weight):
    super().__init__(weight)
    self.engine = GasolineEngine()

  def start_engine(self):
    self.engine.start()

  def stop_engine(self):
    self.engine.stop()

  def sunday_drive(self):
    self.engine.start()
    print('cruise out into the country and return')
    self.engine.stop()

  def switch_to_diesel(self):
    self.engine = DieselEngine()

```


### ./ex5_engine.py

エンジンの基底クラス.

```python
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
import sys; sys.dont_write_bytecode = True

class Engine:
  def start(self):
    print('start the engine')

  def stop(self):
    print('stop the engine')

```


### ./ex7_diesel.py

具象クラス「ディーゼルエンジン」.

```python
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
import sys; sys.dont_write_bytecode = True

from ex5_engine import *

class DieselEngine(Engine):
  def start(self):
    print('start a diesel engine')

  def stop(self):
    print('stop a diesel engine')

```


### ./ex7_gasoline.py

具象クラス「ガソリンエンジン」.

```python
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
import sys; sys.dont_write_bytecode = True

from ex5_engine import *

class GasolineEngine(Engine):
  def start(self):
    print('start a gasoline engine')

  def stop(self):
    print('stop a gasoline engine')

```


### ./ex8_delegate_test.py

テストドライバ.
unittest の使い方をメモしているので長くなっている.

```python
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
import sys; sys.dont_write_bytecode = True
import re
import unittest
from io import StringIO
from ex8_delegate import *

class DelegateToEngineTest1(unittest.TestCase):

  def test_delegation(self):
    # 初期状態のエンジンはガソリンである
    c = Car(weight=650)
    # ガソリンエンジンであるかチェックする
    self.assertIsInstance(c.engine, GasolineEngine)
    # 出掛ける & 帰宅する
    c.sunday_drive()

    # ディーゼルエンジンに切り替える
    c.switch_to_diesel()
    # ディーゼルエンジンであるかチェックする
    self.assertIsInstance(c.engine, DieselEngine)
    # 出掛ける & 帰宅する
    c.sunday_drive()


class DelegateToEngineTest2(unittest.TestCase):
 
  def setUp(self): #! note_0001
    u""" 標準出力ストリームをキャプチャする """
    self.capture = StringIO()
    sys.stdout = self.capture
 
  def tearDown(self): #! note_0002
    u""" 標準出力ストリームを元に戻す """
    sys.stdout = sys.__stdout__

  def test_delegation(self):
    # 初期状態のエンジンはガソリンである
    c = Car(weight=650)
    c.start_engine()
    # 標準出力と完全一致させる
    self.assertRegex(self.capture.getvalue(), r'This car weighs 650kg\nstart a gasoline engine\n') #! note_0003
    c.start_engine()
    # 正規表現で一致させる
    self.assertRegex(self.capture.getvalue(), '^This car weighs 650kg') #! note_0003
    # 正規表現で一致させる
    self.assertRegex(self.capture.getvalue(), '.* gasoline engine\n$') #! note_0003

# note_0001:
#   https://zashikiro.hateblo.jp/entry/2014/01/28/210252
#   https://docs.python.org/ja/3/library/unittest.html#unittest.TestCase.setUp
#   https://stackoverflow.com/questions/52440965/attributeerror-type-object-io-stringio-has-no-attribute-stringio
# note_0002:
#   https://zashikiro.hateblo.jp/entry/2014/01/28/210252
#   https://docs.python.org/ja/3/library/unittest.html#unittest.TestCase.tearDown
#   https://stackoverflow.com/questions/52440965/attributeerror-type-object-io-stringio-has-no-attribute-stringio
# note_0003:
#   https://docs.python.org/ja/3/library/unittest.html#unittest.TestCase.assertRegex
```


## 実行

### 単体テスト環境

```bash
$ python -m unittest ex8_delegate_test.py
This car weighs 650kg
start a gasoline engine
cruise out into the country and return
stop a gasoline engine
start a diesel engine
cruise out into the country and return
stop a diesel engine
..
----------------------------------------------------------------------
Ran 2 tests in 0.001s

OK
```

以上.



[^1]: 実践では、デザインパターンの本のような典型的な構造はしておらず、GoF パターンに無理に当てはめることはしなかった.