Skip to content

Commit 15479d6

Browse files
committed
feat(behavioral): add visitor pattern
1 parent e27ae2b commit 15479d6

File tree

1 file changed

+156
-0
lines changed

1 file changed

+156
-0
lines changed

behavioral/visitor.ts

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
namespace Visitor {
2+
type Coords = { x: number; y: number };
3+
4+
interface Visitor<T = void> {
5+
forRect(rect: Rect): T;
6+
forCircle(circle: Circle): T;
7+
forTriangle(triangle: Triangle): T;
8+
}
9+
10+
interface VisitingElement {
11+
accept<T>(visitor: Visitor<T>): T;
12+
}
13+
14+
class Rect implements VisitingElement {
15+
constructor(
16+
public coords: Coords,
17+
public height: number,
18+
public width: number
19+
) {}
20+
21+
accept<T>(visitor: Visitor<T>) {
22+
return visitor.forRect(this);
23+
}
24+
}
25+
26+
class Circle implements VisitingElement {
27+
constructor(public coords: Coords, public radius: number) {}
28+
29+
accept<T>(visitor: Visitor<T>) {
30+
return visitor.forCircle(this);
31+
}
32+
}
33+
34+
class Triangle implements VisitingElement {
35+
constructor(
36+
public vertex1: Coords,
37+
public vertex2: Coords,
38+
public vertex3: Coords
39+
) {}
40+
41+
accept<T>(visitor: Visitor<T>) {
42+
return visitor.forTriangle(this) as T;
43+
}
44+
}
45+
46+
class AreaVisitor implements Visitor {
47+
forRect(rect: Rect) {
48+
return rect.width * rect.height;
49+
}
50+
51+
forCircle(circle: Circle) {
52+
return Math.PI * Math.pow(circle.radius, 2);
53+
}
54+
55+
forTriangle(triangle: Triangle) {
56+
const side1 = this.euclidDistance(triangle.vertex1, triangle.vertex2);
57+
const side2 = this.euclidDistance(triangle.vertex2, triangle.vertex3);
58+
const side3 = this.euclidDistance(triangle.vertex3, triangle.vertex1);
59+
60+
// heron's formula
61+
const perimeter = (side1 + side2 + side3) / 2;
62+
63+
const area = Math.sqrt(
64+
perimeter *
65+
(perimeter - side1) *
66+
(perimeter - side2) *
67+
(perimeter - side3)
68+
);
69+
70+
return area;
71+
}
72+
73+
private euclidDistance(startVertex: Coords, endVertex: Coords) {
74+
const distance = Math.sqrt(
75+
Math.pow(endVertex.x - startVertex.x, 2) +
76+
Math.pow(endVertex.y - startVertex.y, 2)
77+
);
78+
79+
return distance;
80+
}
81+
}
82+
83+
class DrawerVisitor implements Visitor {
84+
context: CanvasRenderingContext2D;
85+
86+
constructor() {
87+
const canvas = document.createElement("canvas");
88+
this.context = canvas.getContext("2d")!;
89+
}
90+
91+
forRect(rect: Rect) {
92+
this.context.fillStyle = "#000";
93+
this.context.rect(rect.coords.x, rect.coords.y, rect.width, rect.height);
94+
this.context.fill();
95+
}
96+
97+
forCircle(circle: Circle) {
98+
this.context.fillStyle = "#000";
99+
this.context.arc(
100+
circle.coords.x,
101+
circle.coords.y,
102+
circle.radius,
103+
0,
104+
Math.PI * 2
105+
);
106+
this.context.fill();
107+
}
108+
109+
forTriangle(triangle: Triangle) {
110+
this.context.strokeStyle = "red";
111+
112+
this.context.beginPath();
113+
this.context.moveTo(triangle.vertex1.x, triangle.vertex1.y);
114+
this.context.lineTo(triangle.vertex2.x, triangle.vertex2.y);
115+
this.context.lineTo(triangle.vertex3.x, triangle.vertex3.y);
116+
this.context.lineTo(triangle.vertex1.x, triangle.vertex1.y);
117+
this.context.closePath();
118+
this.context.stroke();
119+
}
120+
}
121+
122+
function getTotalArea(figures: VisitingElement[]) {
123+
const areaVisitor = new AreaVisitor();
124+
125+
const totalArea = figures.reduce((sum, figure) => {
126+
return sum + figure.accept(areaVisitor);
127+
}, 0);
128+
129+
return totalArea;
130+
}
131+
132+
function draw(figures: VisitingElement[]) {
133+
const drawer = new DrawerVisitor();
134+
135+
figures.forEach((figure) => figure.accept(drawer));
136+
}
137+
}
138+
139+
/**
140+
* Visitor design pattern
141+
*
142+
* The actual algorithm is outside from the class, inside visitor,
143+
* which implements the logic for different classes independently
144+
*
145+
* Visitor pattern pros
146+
* * Provides uniform way of working with class from outside.
147+
* * Open/Close principle, creating a new visitor will not force us to change class code.
148+
* * Single responsibility principle, class should not care about what the visitor does,
149+
* so it did not keep the logic inside.
150+
* * Handy for collecting information about the visitor specific method calls.
151+
*
152+
* Visitor pattern cons
153+
* * Each time new class is created in application, we need to add visitor implementation for it.
154+
* * Visitor lacks accessing class private methods and properties.
155+
*
156+
*/

0 commit comments

Comments
 (0)