Bridgeパターン

概要

クラスを複数の方向に拡張させる」パターンです。

例題

①:配列のソートをクイックソートまたはバブルソートで行います(方向①)

②:後に、①の機能に、ソートの時間を計測する機能を足します(方向②)

クイックソート・バブルソートについてはqiitaの記事よりお借りしたコードを用います。

# バブルソート
def bsort(a):
    for i in range(len(a)):
        for j in range(len(a)-1, i, -1):
            if a[j] < a[j-1]:
                a[j], a[j-1] = a[j-1], a[j]

    return a


# クイックソート
def qsort(a):
    if len(a) in (0, 1):
        return a

    p = a[-1]
    left = [x for x in a[:-1] if x <= p]
    right = [x for x in a[:-1] if x > p]

    return qsort(left) + [p] + qsort(right)


# 簡易テスト
if __name__ == "__main__":
    print(bsort([2, 7, 3, 4, 9, 1]))
    print(qsort([2, 7, 3, 4, 9, 1]))

サンプルコード

import time


def main():
    l = [2, 7, 3, 4, 9, 1]
    sorter_quick = Sorter(QuickSorter())
    print(sorter_quick.sort(l))

    sorter_bubbl = Sorter(BubbleSorter())
    print(sorter_bubbl.sort(l))

    sorter_quick_timer = TimeSorter(QuickSorter())
    print(sorter_quick_timer.timesort(l))

    sorter_bubbl_timer = TimeSorter(BubbleSorter())
    print(sorter_bubbl_timer.timesort(l))


###############
# バブルソート
def bsort(a):
    for i in range(len(a)):
        for j in range(len(a)-1, i, -1):
            if a[j] < a[j-1]:
                a[j], a[j-1] = a[j-1], a[j]

    return a


# クイックソート
def qsort(a):
    if len(a) in (0, 1):
        return a

    p = a[-1]
    left = [x for x in a[:-1] if x <= p]
    right = [x for x in a[:-1] if x > p]

    return qsort(left) + [p] + qsort(right)

###############


# ①の機能の実装
class Sorter:
    def __init__(self, sorter):
        self.sorter = sorter

    def sort(self, a):
        return self.sorter.sort(a)


class SortImple:
    def sort(a):
        raise NotImplementedError


class QuickSorter(SortImple):
    def __init__(self):
        pass

    def sort(self, a):
        return qsort(a)


class BubbleSorter(SortImple):
    def __init__(self):
        pass

    def sort(self, a):
        return bsort(a)


# ②の機能拡張
class TimeSorter(Sorter):
    def timesort(self, a):
        start = time.time()
        a_sorted = self.sorter.sort(a)
        print(time.time() - start)
        return a_sorted


if __name__ == "__main__":
    main()

# 出力

[1, 2, 3, 4, 7, 9] [1, 2, 3, 4, 7, 9] 2.2172927856445312e-05 [1, 2, 3, 4, 7, 9] 7.867813110351562e-06 [1, 2, 3, 4, 7, 9]

特徴

# アンチパターンの実装

まずは素直にSorterに対して継承することで実装します。

クラス図は以下のようになります。

Bridgeパターンのサンプルコード(アンチパターン)におけるクラス図



import time


# main, qsort, bsortは同じなので省略
# ①の機能の実装
class Sorter:
    def __init__(self):
        pass

    def sort(self, a):
        raise NotImplementedError()


class QuickSorter(Sorter):
    def __init__(self):
        pass

    def sort(self, a):
        return qsort(a)


class BubbleSorter(Sorter):
    def __init__(self):
        pass

    def sort(self, a):
        return bsort(a)


# ②の機能拡張
# QuickSorterはSorterを継承しているため、TimerSorterでsortするには再度継承が必要
# (クラス名も変更の必要がある)
class TimerSorter(Sorter):
    def __init__(self):
        pass

    def timesort(self, a):
        start = time.time()
        a_sorted = self.sort(a)
        print(time.time() - start)
        return a_sorted


class TimeQuickSorter(TimerSorter):
    def __init__(self):
        pass

    def sort(self, a):
        return qsort(a)


class TimeBubbleSorter(TimerSorter):
    def __init__(self):
        pass

    def sort(self, a):
        return bsort(a)


if __name__ == "__main__":
    main()

サンプルコードのようにすることで、QuickSorter(), BubbleSorter()のコードの重複が解消されます。

一番大事なところはSorterクラスの以下の部分です。

class Sorter:
    def __init__(self, sorter):
        self.sorter = sorter

    def sort(self, a):
        return self.sorter.sort(a)

アンチパターンでは、Sorterクラスのsort()メソッドは実装されておらず、子クラスに実装が任されていました。

これを上記のように委譲(オブジェクトのメソッドに振る舞いを委譲すること)することで、柔軟な振る舞いをできるようにしています。

Pythonらしく書くためのコツ

サンプルコードにて「関数は第一級オブジェクトである」を使用しています。

クラス図

# 一般的なクラス図

一般的なクラス図(Bridgeパターン)

# サンプルコードにおけるクラス図

サンプルコードにおけるクラス図(Bridgeパターン)

# サンプルコード(アンチパターン)におけるクラス図

サンプルコード(アンチパターン)におけるクラス図(Bridgeパターン)

参考URL

Pythonによるデザインパターン-bridge

Bridge パターン - Wikipedia