題のごとし。
ググって理解したところによれば、factory method って「具象クラスごとに、同種手続き(たいてい抽象クラスで定義されてるメソッド)時に呼び出されるクラス可変的なコンストラクタをメソッド化したもの(で、具象クラス実装時に中身が書かれるもの)」っぽい。
で、「同種手続き」というのを抽象クラスのレベルで書いちゃうというのがどうやら template method パティーンらしい。ググった感じだと、factory method パティーンは template method パティーンに更に組み込んで使うっぽい。
んだけど、「実践Python3」の場合、この「同種手続き」というのに共通性がほとんどなくて、「えぇ…」ってなっちゃった。
たぶん、Board の上に駒を置いていく操作、populate_board
が同種手続き(factory method パティーンでいうanOperation
?)に相当しそうなんだけど、そもそも anOperation
は具象クラスでオーバーライドしない(したら意味ないし)から、「???」って感じだ。
でも、create_piece
は「ファクトリー関数」らしくて、これのおかげで、populate_board
は「「ファクトリーメソッド」と呼ぶことができる」らしい。そうすると、同書においては、__init__
が anOperation に相当して、populate_board
がファクトリーメソッドということになるのだろうか…。
Factory Method パターン - Wikipedia 見てたら、
factoryMethodは、[...] パラメータを取り、それによって生成するクラスを変えることもある。
ともあるので、それならむしろ create_piece
が factoryMethod では……?
そう考えると、AbstractBoard
はこうしたくなる(ちなみに個人的な趣味で ABC にしておいた)
from abc import ABCMeta, abstractmethod class AbstractBoard(metaclass=ABCMeta): def __init__(self, rows, columns): self.rows = rows self.columns = columns self.board = [[None for _ in range(columns)] for _ in range(rows)] self.populate_board() def populate_board(self): for row in range(self.rows): for column in range(self.columns): self.board[row][column] = self.create_piece(row, column) @abstractmethod def create_piece(row, column): pass def __str__(self): ...
populate_board
は各マスに対して、サブクラスで実装されたcreate_piece(row, column)
を呼び出して相応しいインスタンスをもらえる。populate_board
は anOperation
に相当するはずで、個別の具象ボードにおいて共通のプロセス。
ほんで、ChessBoard
具象クラスでは
class ChessBoard(AbstractBoard): def __init__(self): super().__init__(8, 8) def create_piece(self, row, column): piece_dict = self._init_piece_map() name_dict = {PAWN: "ChessPawn", ROOK: "ChessRook", KNIGHT: "ChessKnight", BISHOP: "ChessBishop", KING: "ChessKing", QUEEN: "ChessQueen"} if (row, column) in piece_dict: kind, color = piece_dict[(row, column)] name = name_dict[kind] return globals()[color + name]() else: return None def _init_piece_map(self): colors = (((0, 1), BLACK), ((6, 7), WHITE)) kinds = (((0, 7), ROOK), ((1, 6), KNIGHT), ((2, 5), BISHOP), ((3,), QUEEN), ((4,), KING), ((0, 1, 2, 3, 4, 5, 6, 7), PAWN)) piece_dict = {(row, column): (kind, color) for row, color in colors for column, kind in kinds} return piece_dict
ヘルパーメソッドとして _init_piece_map
を用意した。_init_piece_map
で初期状態における各コマの座標の辞書を返してもらって、今の位置がその辞書にあれば相応のインスタンスを渡してあげるという形。
んん…でもこれならもういっそこうした方が…
from abc import ABCMeta, abstractmethod class AbstractBoard(metaclass=ABCMeta): def __init__(self, rows, columns): self.rows = rows self.columns = columns self.board = [[None for _ in range(columns)] for _ in range(rows)] self.populate_board() def populate_board(self): for row in range(self.rows): for column in range(self.columns): self.board[row][column] = self.create_piece(row, column) def create_piece(self, row, column): piece_dict = self.initial_piece_map() name_dict = self.get_name_dict() if (row, column) in piece_dict: kind, color = piece_dict[(row, column)] name = name_dict[kind] return globals()[color + name]() else: return None @abstractmethod @staticmethod def initial_piece_map(): pass @abstractmethod @staticmethod def get_name_dict(): pass def __str__(self): ... class ChessBoard(AbstractBoard): def __init__(self): super().__init__(8, 8) @staticmethod def get_name_dict(): name_dict = {PAWN: "ChessPawn", ROOK: "ChessRook", KNIGHT: "ChessKnight", BISHOP: "ChessBishop", KING: "ChessKing", QUEEN: "ChessQueen"} return name_dict @staticmethod def initial_piece_map(): colors = (((0, 1), BLACK), ((6, 7), WHITE)) kinds = (((0, 7), ROOK), ((1, 6), KNIGHT), ((2, 5), BISHOP), ((3,), QUEEN), ((4,), KING), ((0, 1, 2, 3, 4, 5, 6, 7), PAWN)) piece_dict = {(row, column): (kind, color) for row, color in colors for column, kind in kinds} return piece_dict
今回のテーマだと本質的に差異が生じるところって「どこにどんな駒を置くのか」なので、それを具象クラスで決めるという風にしたほうが簡潔そう…。特に、駒の位置情報を司るinitial_piece_map
が分離するおかげで、外部ファイルからの読み込みに変更するのも簡単。
ただ、これが factory method パティーンなのかと言われると……。そもそもこのテーマ設定がファクトリーメソッドパティーンに向いてないのかしら。
しかし考えてみれば、population の仕方を抽象メソッドで固定してしまうというのはハードコーディングな気もする。population の仕方は同書のやり方もあるし、おかゆのやり方もあるわけで(たとえばおかゆのやり方は、列真ん中付近の無駄なところまで走査してるので、パフォーマンスが悪い)。そうなると、やはり @abstractmethod
にするのは populate_board
にしておくべきなのだろうか…。
いや、でも、仮にそうだったとしても、populate_board
が factory method とは思えない。こいつは別に anOperation (この状況でいえば__init__
) に対してクラス差異的にオブジェクトを生成してるわけではなくて、個別クラス特有的にインスタンスの内部状態を変更してるだけ。単なる素朴なオーバーライドにしか見えない。
もやもやする
追記
2016-11-29-15:20
えーっと。factory パターンというのがあるのを知った。理解によれば、「factoryクラスに指示(引数)を与えて、それに見合ったオブジェクトを持ってきてくれるというクールなやつをつくる」パティーンのことらしい。言い方としては「指示に見合ったクラスのインスタンスを作り出す」といったほうがいいか(関数でよくない…?シンプルなファクトリなら関数でもできる(create_piece
がまさにそう)けど、ファクトリの内部状態によって指示への従い方を変えていく…みたいなことをするなら確かにクラスである必要がありそう(でもファクトリってそんな複雑なことする(していい)のかな))。
個人的には router みも感じる。
これを踏まえると、なるほど、create_piece
は確かに「ファクトリー関数」ではあっても factory method ではない気がしてきた。
じゃあ、問題はどれがanOperation
で、factory method
なのか。
サルでもわかる 逆引きデザインパターン 第2章 逆引きカタログ ロジック編 Factory/Factory Method(ファクトリ/ファクトリメソッド) をみる。
それでは、ファクトリパターンとファクトリメソッドパターンの違いは何でしょうか?
ファクトリパターンは、オブジェクトの生成処理だけでなく、どのオブジェクトを生成するかの判断もオブジェクトェクトの使用者から隠してくれるパターンです。 ファクトリパターンでは、生成するオブジェクトの種類の変更をファクトリの処理の中で動的に行います。
しかし、ファクトリパターンでは、生成するオブジェクトの種類が増えたり、生成処理手順が複雑化した場合、ファクトリ内の処理が冗長で複雑になってしまいます。 そこで登場するのがファクトリメソッドパターンです。
せやな。続き:
ファクトリメソッドパターンでは、生成するオブジェクトごとにファクトリを用意し、ファクトリに対して共通のスーパークラスを設けることで、オブジェクトの生成処理を柔軟に行います。 スーパークラスでは、オブジェクト生成に共通な処理の実装と、オブジェクトのnewを行うメソッドやオブジェクト固有の生成手順を抽象メソッドとして定義します。 サブクラスでは、抽象メソッドの実装(オブジェクトのnew)を行うだけです。 オブジェクトの生成はサブクラスで行い、サブクラスでは生成処理の差分のみを実装すればよいので、生成処理を簡略化できます(注4)。
ファクトリメソッドパターンは、1つのファクトリは1つのオブジェクトの生成のみを行うため、生成するオブジェクトの種類の変更を行う場合、ファクトリクラスを切り替える必要があります。 ファクトリメソッドパターンでは多くの場合、オブジェクトの使用者はファクトリのスーパークラスを使用します。 そしてファクトリ指定は、オブジェクト使用者の生成時にコンストラクタで渡したり、ファクトリ設定用メソッドを設けるなどの手段が必要になります。
やっぱりとにかくファクトリメソッドパターンというのは
- 共通処理は抽象クラスで
- 差分処理(たいていコンストラクタの指定)は具象クラスで
だと思うんだよな。で、ファクトリである以上、多分、クライアントに渡されるのはプロダクト(を加工したもの)であるべきで、さらにそのプロダクトというのはファクトリーメソッドによって可変的に持ってきたもの。
と考えたときに、やっぱり「実践Python3」のゲームボードの例は気持ちが悪い…。
なんか、もしかして「ファクトリ関数を中に含んだメソッド」をファクトリメソッドと呼んでるんじゃないかコイツ?という感がしてきた。
あ~ もやもやする
追記
16:50
これを Factory method パターンと理解しようとするのを諦めた。
で、こういうの見つけた
あまりにも自由度が高くなってきたらクラスを引数で渡してやれっていう…えぇ…いやそりゃそうするけど……