Update 2023.11.16 2017.05.05

Python デザインパターン サンプルコード Strategy
Mark Summerfield『実践 Python 3』デザインパターンのサンプルコード
Python3(3.11)で動くソースコード(.pyファイル .ipynbファイル)あります
「anaconda3」on .py「PyCharm」.ipynb「Jupyter Notebook」

著作権の問題があるので,本に書いてないことだけを解説します。つまり,視点を変えて解説します。

◆◆Strategy パターンの使われる場面◆◆

Strategy戦略のアルゴリズムが複数ありかつ複雑な場合,Strategy戦略を独立させ交換可能にするのがStrategy パターンである。Strategy戦略をクライアントから隠蔽することもできる。

原著にならって,【tabulator3.py】【tabulator4.py】を解説します。

(2023-11-16)Python3.11で動作確認済み

【重要な注意】本プログラムの出力はhtmlコードであり,htmlファイルではありません。そのhtmlコードをそのままこの解説記事のソースコードに貼り付けていますので,このWebページ全体から見れば二重構造(ネスト)になっているのでbugるかもしれません。少なくとも HTML5 チェッカーからは警告を受けます。

私のブラウザGoogle Chromeでは問題なく出力が表示されています。


◆◆Strategy パターンとは◆◆

GoFによれば,Strategy パターンの目的は, 「アルゴリズムの集合を定義し,各アルゴリズムをカプセル化して,それらを交換可能にする。Strategy パターンを利用することで,アルゴリズムを,それを利用するクライアントからは独立に変更することができるようになる。」

GoFによれば,Strategy パターンの別名は,Policy パターンである。

GoFによれば,Strategy パターンは次のような場合に利用する。
・関連する多くのクラスが振る舞いのみ異なっている場合。Strategy パターンは,多くの振る舞いの中の1つでクラスを構成する方法を提供する。
・複数の異なるアルゴリズムを必要とする場合。たとえば,空間と時間のトレードオフを反映する複数のアルゴリズムを定義する場合が考えられる。このとき,複数のアルゴリズムをクラス階層[HO87]として実装していく際に,Strategy パターンを利用することができる。
・アルゴリズムが,クライアントが知るべきではないデータを利用している場合。Strategy パターンを利用することにより,複雑でアルゴリズムに特有なデータ構造を公開するのを避けることができる。
・クラスが多くの振る舞いを定義しており,これらがオペレーション内で複数の条件文として現れている場合。このとき,多くの条件文を利用する代わりに,条件分岐後の処理を Strategy クラスに移し換える。

GoFによれば,Strategy パターンに関連するパターンは次のようなものである。
Flyweight パターン: ConcreteStrategy オブジェクトは,しばしば有効な flyweight になる。

◆◆Strategy パターンのサンプルのクラス構成◆◆

ソースコードは巻末にあり,ソースファイルはこのWebの左上隅にありダウンロードできます。
    def text_tabulator    def text_tabulator

             ↑                  ↑
                  Layout
               (tabulate)

サンプルは,プレーンテキストまたはHTMLコードにより表を作る Strategy を簡単に切り替えるものです。

def text_tabulator は,プレーンテキストにより表をつくります。
def text_tabulator は,HTMLコードにより表をつくります。
class Layout は,2つの Strategy の切り替えを簡単にします。

◆◆Strategy パターンの実装◆◆

tabulator3.py と tabulator4.py は,class Layout 以外は同じものです。

Strategy に相当する def text_tabulator と def text_tabulator が class Layout により,簡単に切り替えることができるのが,Strategy パターンです。

このサンプルでは,Strategy がグローバル関数になっていますが,これらがクラスであるとその呼び方は千差万別となるかもしれません。

同じサイトに別著書の Strategy パターンの記事がありますので参考にしてください。
https://yamakatsusan.web.fc2.com/hp_software/pythonpattern10.html
『Strategy パターン 結城 浩「Java言語で学ぶデザインパターン入門」をPython化』

Strategy に相当する def text_tabulator と def text_tabulator を概略説明すると,19行目にある10の人名を,プレーンテキストまたは HTML コードにより,表をつくる関数です。表の段数を2段から5段まで指定できます。

これらのグローバル関数の出力を次に示します。HTMLコードを実際にブラウザで見たときの表もいっしょに示します。

これらのグローバル関数の中身は,Strategy パターンの本質と何の関係もありませんので,詳細は省略させていただきます。

【標準出力】

<table border="1">
<tr><td>Nikolai Andrianov</td><td>Matt Biondi</td><td>Bjorn Dahlie</td><td>Birgit Fischer</td><td>Sawao Kato</td></tr>
<tr><td>Larisa Latynina</td><td>Carl Lewis</td><td>Michael Phelps</td><td>Mark Spitz</td><td>Jenny Thompson</td></tr>
</table>

<table border="1">
<tr><td>Nikolai Andrianov</td><td>Matt Biondi</td><td>Bjorn Dahlie</td><td>Birgit Fischer</td></tr>
<tr><td>Sawao Kato</td><td>Larisa Latynina</td><td>Carl Lewis</td><td>Michael Phelps</td></tr>
<tr><td>Mark Spitz</td><td>Jenny Thompson</td></tr>
</table>

<table border="1">
<tr><td>Nikolai Andrianov</td><td>Matt Biondi</td><td>Bjorn Dahlie</td></tr>
<tr><td>Birgit Fischer</td><td>Sawao Kato</td><td>Larisa Latynina</td></tr>
<tr><td>Carl Lewis</td><td>Michael Phelps</td><td>Mark Spitz</td></tr>
<tr><td>Jenny Thompson</td></tr>
</table>

<table border="1">
<tr><td>Nikolai Andrianov</td><td>Matt Biondi</td></tr>
<tr><td>Bjorn Dahlie</td><td>Birgit Fischer</td></tr>
<tr><td>Sawao Kato</td><td>Larisa Latynina</td></tr>
<tr><td>Carl Lewis</td><td>Michael Phelps</td></tr>
<tr><td>Mark Spitz</td><td>Jenny Thompson</td></tr>
</table>

+-------------------+-------------------+-------------------+-------------------+-------------------+
| Nikolai Andrianov | Matt Biondi       | Bjorn Dahlie      | Birgit Fischer    | Sawao Kato        |
| Larisa Latynina   | Carl Lewis        | Michael Phelps    | Mark Spitz        | Jenny Thompson    |
+-------------------+-------------------+-------------------+-------------------+-------------------+

+-------------------+-------------------+-------------------+-------------------+
| Nikolai Andrianov | Matt Biondi       | Bjorn Dahlie      | Birgit Fischer    |
| Sawao Kato        | Larisa Latynina   | Carl Lewis        | Michael Phelps    |
| Mark Spitz        | Jenny Thompson    |                   |                   |
+-------------------+-------------------+-------------------+-------------------+

+-------------------+-------------------+-------------------+
| Nikolai Andrianov | Matt Biondi       | Bjorn Dahlie      |
| Birgit Fischer    | Sawao Kato        | Larisa Latynina   |
| Carl Lewis        | Michael Phelps    | Mark Spitz        |
| Jenny Thompson    |                   |                   |
+-------------------+-------------------+-------------------+

+-------------------+-------------------+
| Nikolai Andrianov | Matt Biondi       |
| Bjorn Dahlie      | Birgit Fischer    |
| Sawao Kato        | Larisa Latynina   |
| Carl Lewis        | Michael Phelps    |
| Mark Spitz        | Jenny Thompson    |
+-------------------+-------------------+

【上のHTMLコードをブラウザで見る】
Nikolai AndrianovMatt BiondiBjorn DahlieBirgit FischerSawao Kato
Larisa LatyninaCarl LewisMichael PhelpsMark SpitzJenny Thompson
Nikolai AndrianovMatt BiondiBjorn DahlieBirgit Fischer
Sawao KatoLarisa LatyninaCarl LewisMichael Phelps
Mark SpitzJenny Thompson
Nikolai AndrianovMatt BiondiBjorn Dahlie
Birgit FischerSawao KatoLarisa Latynina
Carl LewisMichael PhelpsMark Spitz
Jenny Thompson
Nikolai AndrianovMatt Biondi
Bjorn DahlieBirgit Fischer
Sawao KatoLarisa Latynina
Carl LewisMichael Phelps
Mark SpitzJenny Thompson

【注意】上のソースは,HTML で書かれているわけであり,このWebページ全体から見れば二重構造(ネスト)になっているのでbugるかもしれません。少なくとも HTML5 チェッカーからは警告を受けます。

Strategy に相当する def text_tabulator と def text_tabulator の呼び方は,tabulator3.py と tabulator4.py のメインルーチンは同じなのですが,class Layout が違っています。

よく見ると,tabulator3.py では,インスタンスメソッドを呼んでいて,tabulator4.py では,インスタンス変数を呼んでいます。どちらも,グローバル関数を戻り値にしていますので,グローバル関数を切り替えることができるのです。

【tabulator3.py】

def main():
    htmlLayout = Layout(html_tabulator)
    for rows in range(2, 6):
        print(htmlLayout.tabulate(rows, WINNERS))
    textLayout = Layout(text_tabulator)
    for rows in range(2, 6):
        print(textLayout.tabulate(rows, WINNERS))


class Layout:

    def __init__(self, tabulator):
        self.tabulator = tabulator


    def tabulate(self, rows, items):
        return self.tabulator(rows, items)

【tabulator4.py】

def main():
    htmlLayout = Layout(html_tabulator)
    for rows in range(2, 6):
        print(htmlLayout.tabulate(rows, WINNERS))
    textLayout = Layout(text_tabulator)
    for rows in range(2, 6):
        print(textLayout.tabulate(rows, WINNERS))


class Layout:

    def __init__(self, tabulator):
        self.tabulate = tabulator

Strategy パターンは,これだけのことですが,ポイントは,同じ呼び方ができるということです。

◆◆ソースコード◆◆

このWebの左上隅からダウンロードできます。

ソースファイルは4つです。1つのサンプル出力を添付します。
・tabulator1.py;Strategy パターンのPythonサンプル(その1)
・tabulator2.py;Strategy パターンのPythonサンプル(その2)
・tabulator3.py;Strategy パターンのPythonサンプル(その3)
・tabulator4.py;Strategy パターンのPythonサンプル(その4)

【tabulator3.py】
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
#!/usr/bin/env python3
# Copyright c 2012-13 Qtrac Ltd. All rights reserved.
# This program or module is free software: you can redistribute it
# and/or modify it under the terms of the GNU General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version. It is provided for
# educational purposes and is distributed in the hope that it will be
# useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# General Public License for more details.

import sys
if sys.version_info[:2] < (3, 2):
    from xml.sax.saxutils import escape
else:
    from html import escape


WINNERS = ("Nikolai Andrianov", "Matt Biondi", "Bjorn Dahlie",
        "Birgit Fischer", "Sawao Kato", "Larisa Latynina", "Carl Lewis",
        "Michael Phelps", "Mark Spitz", "Jenny Thompson")


def main():
    htmlLayout = Layout(html_tabulator)
    for rows in range(2, 6):
        print(htmlLayout.tabulate(rows, WINNERS))
    textLayout = Layout(text_tabulator)
    for rows in range(2, 6):
        print(textLayout.tabulate(rows, WINNERS))


class Layout:

    def __init__(self, tabulator):
        self.tabulator = tabulator


    def tabulate(self, rows, items):
        return self.tabulator(rows, items)


def html_tabulator(rows, items):
    columns, remainder = divmod(len(items), rows)
    if remainder:
        columns += 1
    column = 0
    table = ['<table border="1">\n']
    for item in items:
        if column == 0:
            table.append("<tr>")
        table.append("<td>{}</td>".format(escape(str(item))))
        column += 1
        if column == columns:
            table.append("</tr>\n")
        column %= columns
    if table[-1][-1] != "\n":
        table.append("</tr>\n")
    table.append("</table>\n")
    return "".join(table)


def text_tabulator(rows, items):
    columns, remainder = divmod(len(items), rows)
    if remainder:
        columns += 1
        remainder = (rows * columns) - len(items)
        if remainder == columns:
            remainder = 0
    column = columnWidth = 0
    for item in items:
        columnWidth = max(columnWidth, len(item))
    columnDivider = ("-" * (columnWidth + 2)) + "+"
    divider = "+" + (columnDivider * columns) + "\n"
    table = [divider]
    for item in items + (("",) * remainder):
        if column == 0:
            table.append("|")
        table.append(" {:<{}} |".format(item, columnWidth))
        column += 1
        if column == columns:
            table.append("\n")
        column %= columns
    table.append(divider)
    return "".join(table)


if __name__ == "__main__":
    main()

【tabulator4.py】
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
#!/usr/bin/env python3
# Copyright c 2012-13 Qtrac Ltd. All rights reserved.
# This program or module is free software: you can redistribute it
# and/or modify it under the terms of the GNU General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version. It is provided for
# educational purposes and is distributed in the hope that it will be
# useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# General Public License for more details.

import sys
if sys.version_info[:2] < (3, 2):
    from xml.sax.saxutils import escape
else:
    from html import escape


WINNERS = ("Nikolai Andrianov", "Matt Biondi", "Bjorn Dahlie",
        "Birgit Fischer", "Sawao Kato", "Larisa Latynina", "Carl Lewis",
        "Michael Phelps", "Mark Spitz", "Jenny Thompson")


def main():
    htmlLayout = Layout(html_tabulator)
    for rows in range(2, 6):
        print(htmlLayout.tabulate(rows, WINNERS))
    textLayout = Layout(text_tabulator)
    for rows in range(2, 6):
        print(textLayout.tabulate(rows, WINNERS))


class Layout:

    def __init__(self, tabulator):
        self.tabulate = tabulator


def html_tabulator(rows, items):
    columns, remainder = divmod(len(items), rows)
    if remainder:
        columns += 1
    column = 0
    table = ['<table border="1">\n']
    for item in items:
        if column == 0:
            table.append("<tr>")
        table.append("<td>{}</td>".format(escape(str(item))))
        column += 1
        if column == columns:
            table.append("</tr>\n")
        column %= columns
    if table[-1][-1] != "\n":
        table.append("</tr>\n")
    table.append("</table>\n")
    return "".join(table)


def text_tabulator(rows, items):
    columns, remainder = divmod(len(items), rows)
    if remainder:
        columns += 1
        remainder = (rows * columns) - len(items)
        if remainder == columns:
            remainder = 0
    column = columnWidth = 0
    for item in items:
        columnWidth = max(columnWidth, len(item))
    columnDivider = ("-" * (columnWidth + 2)) + "+"
    divider = "+" + (columnDivider * columns) + "\n"
    table = [divider]
    for item in items + (("",) * remainder):
        if column == 0:
            table.append("|")
        table.append(" {:<{}} |".format(item, columnWidth))
        column += 1
        if column == columns:
            table.append("\n")
        column %= columns
    table.append(divider)
    return "".join(table)


if __name__ == "__main__":
    main()

以上

トップページに戻る
inserted by FC2 system