Visitorパターン
「データ構造」と「操作」を分けて定義するパターン
通常のアプローチで書いたもの
code: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パターンで書き直したもの
code: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で模倣してるだけ
code: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.icon
メリットとしては、
新しい操作を追加する際に、元のclassを修正しないで良い
上記の例だとCircleに手を加えるのではなく、新しくHogeのような操作classを定義して拡張する
データ構造と操作の分離
OOP目線でもメリットなのだろうかmrsekut.icon
そうなら最初からclass使わなくて良くない?という気がせんでもないが
OOP感残しつつもVisitorパターンの表現がわかりやすい
code: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をしたいだけ