generated at
Visitorパターン
from GoF



「データ構造」と「操作」を分けて定義するパターン

通常のアプローチで書いたもの
ts
// 形状の基本クラス abstract class Shape { abstract area(): number; abstract perimeter(): number; } class Circle extends Shape { constructor(public radius: number) { super(); } area(): number { return Math.PI * this.radius * this.radius; } perimeter(): number { return 2 * Math.PI * this.radius; } } class Square extends Shape { constructor(public side: number) { super(); } area(): number { return this.side * this.side; } perimeter(): number { return 4 * this.side; } } const circle = new Circle(5); console.log(circle.area()); // 出力: 78.53981633974483 console.log(circle.perimeter()); // 出力: 31.41592653589793
Circle Scuare という具体的なclassを作り、それぞれに共通する操作 area permieter を実装している


Visitorパターンで書き直したもの
ts
// 形状のインターフェース interface Shape { accept(visitor: ShapeVisitor): void; } class Circle implements Shape { constructor(public radius: number) {} accept(visitor: ShapeVisitor): void { visitor.visitCircle(this); } } class Square implements Shape { constructor(public side: number) {} accept(visitor: ShapeVisitor): void { visitor.visitSquare(this); } } // ビジターのインターフェース interface ShapeVisitor { visitCircle(circle: Circle): void; visitSquare(square: Square): void; } class AreaCalculator implements ShapeVisitor { visitCircle(circle: Circle): void { console.log(Math.PI * circle.radius * circle.radius); } visitSquare(square: Square): void { console.log(square.side * square.side); } } class PerimeterCalculator implements ShapeVisitor { visitCircle(circle: Circle): void { console.log(2 * Math.PI * circle.radius); } visitSquare(square: Square): void { console.log(4 * square.side); } } const circle = new Circle(5); const areaCalculator = new AreaCalculator(); circle.accept(areaCalculator); // 出力: 78.53981633974483 const perimeterCalculator = new PerimeterCalculator(); circle.accept(perimeterCalculator); // 出力: 31.41592653589793
Circle Square は構造だけを定義
AreaCalulator PerimeterCalculator という操作だけをまとめたclassを別に作る



↑classで書くとパッとしないが、要はHaskellでいう以下のようなことをclassで模倣してるだけ
hs
data Shape = Circle Float | Square Float area :: Shape -> Float area (Circle r) = pi * r * r area (Square s) = s * s perimeter :: Shape -> Float perimeter (Circle r) = 2 * pi * r perimeter (Square s) = 4 * s
classって本当に冗長すぎるんだよなmrsekut


メリットとしては、
新しい操作を追加する際に、元のclassを修正しないで良い
上記の例だとCircleに手を加えるのではなく、新しく Hoge のような操作classを定義して拡張する
データ構造と操作の分離
OOP目線でもメリットなのだろうかmrsekut
そうなら最初からclass使わなくて良くない?という気がせんでもないが


多重ディスパッチをサポートしてるJuliaが中間ぐらいの感じ
OOP感残しつつもVisitorパターンの表現がわかりやすい
julia
abstract type Shape end struct Circle <: Shape radius::Float64 end struct Square <: Shape side::Float64 end # 面積を計算する関数 function calculate_area(s::Circle) println(π * s.radius * s.radius) end function calculate_area(s::Square) println(s.side * s.side) end # 周囲の長さを計算する関数 function calculate_perimeter(s::Circle) println(2 * π * s.radius) end function calculate_perimeter(s::Square) println(4 * s.side) end # 使用例 circle = Circle(5.0) calculate_area(circle) # 出力: 78.53981633974483 calculate_perimeter(circle) # 出力: 31.41592653589793
要はoverloadをしたいだけ