はじめに
変更しやすいコードが書けないのにソフトウェア開発とか舐めているのですか
天使様、ごめんなさい〜、nikkieです1。
「かわいい」と技術書が夢の合体を果たした、ちょうぜつ本(『ちょうぜつソフトウェア設計入門』2)!🤗
昨年から読書会を共同主催しており、現在は第8章「デザインパターン」を読み進めています。
直近読んだ範囲から、Visitorを取り上げます。来訪者編です!
目次
前回のちょうぜつ本!
前から順に読み進めて第8章に突入し、読書会では範囲を細かくしてじっくりと読み進めています。
- 読書ログ | #ちょうぜつ本 第8章 Template Methodパターン 〜穴埋め問題にする〜 - nikkie-ftnextの日記
- 読書&写経ログ | #ちょうぜつ本 第8章 Bridgeパターン 〜has-a関係でとらえて、複数系統の組合せを表現できる!〜 - nikkie-ftnextの日記
- 読書ログ | #ちょうぜつ本 (第8章)でも #fukabori (48)でも、ただしSingleton、テメーはダメだ - nikkie-ftnextの日記
- 読書ログ | #ちょうぜつ本 第8章 Facadeパターン 〜表玄関だけを通って!〜 - nikkie-ftnextの日記
- 読書&写経ログ | #ちょうぜつ本 第8章 Mediatorパターン 〜ColleagueたちにMediator自身を渡して、必ずMediatorを介在させる!〜 - nikkie-ftnextの日記
- 読書ログ | #ちょうぜつ本 第8章 Proxyパターン 〜Python文法のデコレータは1つの例〜 - nikkie-ftnextの日記
- 読書ログ | #ちょうぜつ本 第8章 Decoratorパターン 〜新たなメソッドを追加するラッパー。委譲による実装がうまい!〜 - nikkie-ftnextの日記
- 読書ログ | #ちょうぜつ本 第8章 Adapterパターン 〜身の回りにあるアダプタと同じ。2つのインターフェースが合わないときに変換する!〜 - nikkie-ftnextの日記
Compositeパターンは、自己再帰的なデータ構造。
複数と単数の同一視は、頭いいですね。
Visitorパターン
Compositeパターンのデータの各要素を操作する場合に、visitor(訪問者)を渡して訪問者に要素の操作をしてもらいます。
- データ構造の各要素は、訪問者を受け入れるようにしておくだけ
- 自身を訪問者に渡して、訪問者が要素を操作する
- 操作を拡張可能にできる(要素を操作する訪問者を拡張して渡せばよい)
操作対象が操作の詳細を決められないとき、Visitor を受け入れられるようにだけしておけば、操作の詳細を除いて先に安定させてしまえる、というのがこのパターンです。
公開時期的にもマッチしたサンタクロースのたとえです(煙突はvisitor(サンタクロース)をaccept)。
今回はピンときたVisitorパターン
私は木構造のデータに関心があり(Sphinx拡張3や抽象構文4)、Visitorパターン自体は名前を知っていました。
Visitorという概念をつかもうと、手を動かすのと並行して結城先生本を紐解きもしましたが、いまいちピンときていませんでした。
ちょうぜつ本のコードは具体のVisitorには全く言及していないがゆえに、私には分かりやすかったです(登場人物が多くて私が混乱していたふしがあります)
データ構造
Node
accept()
メソッドでvisitorを受け入れる- 自身をvisitorにどう渡すかだけ実装
Branch
Node
を継承=Branch
はNode
であるaccept()
メソッドでvisitorを受け入れる- 自身と子要素をvisitorにどう渡すかだけ実装
class Node: def accept(self, visitor) -> None: visitor(self) class Branch(Node): def __init__(self, *args: Node) -> None: self.children = list(args) def accept(self, visitor) -> None: super().accept(visitor) for child in self.children: child.accept(visitor)
visitorの実装は読者に委ねられていたので、要素をprint()
するだけとしました。
class ExampleVisitor: def __call__(self, acceptable: VisitorAcceptable) -> None: print(acceptable)
データ構造を用意します。
classDiagram root *-- node root *-- branch branch *-- left branch *-- right
root.accept(ExampleVisitor())
とrootにvisitorを渡して呼び出すだけで、visitorが各要素を処理します!
<__main__.Branch object at 0x103143210> <__main__.Node object at 0x1031431d0> <__main__.Branch object at 0x103143150> <__main__.Node object at 0x1031430d0> <__main__.Node object at 0x103143110>
結城先生本では、要素がvisitorを受け入れて自身をvisitorに渡し、visitorが各要素を処理するという部分が当時の私には理解しきれませんでした(まだるっこしいという誤解をしました)。
ちょうぜつ本の例はvisitorの実装に言及していないので、「こういうデータ構造にvisitorを渡すのか」と気づけて、私にはブレイクスルーとなりました。
Sphinx(やdocutils)とつながりました!
(ここのトピック、詳しくは別記事としたいと思います)
docutils(バージョン 0.20.1)のソースコード(docutils/nodes.py)を覗くと、Visitorパターンがありました。
class Node: def walk(self, visitor): # 省略 # 自身をvisitorにどう渡すかを実装している def walkabout(self, visitor): # 省略 class NodeVisitor: """ "Visitor" pattern [GoF95]_ abstract superclass implementation for document tree traversals. 省略 """ def dispatch_visit(self, node): # 省略 # Nodeの操作を実装している def dispatch_departure(self, node): # 省略
また、SphinxではdocutilsのNodeVisitor
を継承したクラスがトランスレイター5です!
https://github.com/sphinx-doc/sphinx/blob/v7.2.6/sphinx/builders/__init__.py#L62
from docutils import nodes class Builder: default_translator_class: type[nodes.NodeVisitor]
つまり、Sphinx拡張を書くとき、私は知らず知らず(さらに理解至らず至らず)のうちにVisitorパターンを使っていたのです!
Sphinxはユーザが要素を追加できるんですよね。
ユーザが追加する要素への操作は、docutilsやSphinx側のソースコードにあらかじめ定義しておけません。
要素を追加したユーザがvisitor(トランスレイター)も合わせて追加することで成り立っているという理解です。
つまり、要素を追加できるというSphinxの拡張性は、Visitorパターンに支えられていると思われます。
また、ユーザが要素を拡張して作ったデータ構造について、要素の取り出し方(イテレータ)を事前に定義するのも難しそうです。
どの要素にもvisitorを渡すことで操作するというのは、とてもかしこいですね!
終わりに
ちょうぜつ本 8章よりVisitorパターンでした。
データ構造の各要素は訪問者を受け入れるようにしておき、訪問者が要素を操作します。
これにより、操作の詳細は訪問者で定義すればよく、拡張も可能になります(Sphinxの例)
来訪者編は、いいぞ!