Visitor classes are commonly used to perform operations on a set of objects without modifying the structure of their underlying classes.
Visitor Design Pattern allows you to add new operations(behaviours) via Visitor classes to existing classes without modifying their structure. Instead of adding the operation within the existing class, you create a visitor class named as operation you want to have in existing class, so that the operation is externalised from a set of objects without modifying the structure of their underlying classes.
Visitor Design Pattern is more about adding new operations to existing classes via Visitor classes without modifying their structure, rather than adding new fields or methods directly to the classes themselves.
Consider a system where you have a hierarchy of classes representing different shapes, like
Circle
,Rectangle
, andTriangle
. Each of these shapes implementsVisitable
interface(or its equivalent) to allow visitor classes to operate on them.
In the context of Visitor Design Pattern, the name of Visitor class reflects the operation or behaviour it implements for the objects it visits. The operation is externalised from the objects themselves and encapsulated within the visitor class.
For instance:
– PrintDetailsVisitor might be a visitor that prints details about each object it visits.
– CalculatePriceVisitor could be a visitor that calculates the price or cost associated with each object it visits.
– ApplyDiscountVisitor might be a visitor that applies a discount to each object it visits.
The core idea is to separate the operation from the class structure, allowing you to add new operations without modifying the classes of the objects you're operating upon. Each new operation would typically be represented by a new visitor class.
The typical scenario where the Visitor Design Pattern shines is when you have a stable class hierarchy (like different types of products or shapes) and you want to perform various operations on those classes (like calculating taxes, discounts, or drawing the shapes) without changing the code within the classes themselves.
Here's a breakdown:
– Adding New Operations: If you want to add new operations or behaviours that should be applied to a group of classes, you can use the Visitor Design Pattern. You define a visitor interface with a visit method for each class in the original hierarchy and then implement specific visitors for different operations.
– Not Modifying Existing Classes: Visitor Design Pattern allows you to add new operations via Visitor classes without modifying existing classes.
– Not Ideal for Adding New Classes: If you frequently add new classes within the hierarchy, Visitor Design Pattern becomes less suitable, as you'll have to modify all existing visitor classes to handle the new class. Assume that following code is our initial design, there are two classes namedBook
andFruit
and one visitor interface namedProductVisitor
which contains two visit methods with Book and Fruit parameters. I created two visitor classes namedPrintDetailsVisitor
andCalculatePriceVisitor
which implementsProductVisitor
interface. But, by adding new classes in class hierarchy(in this case,Book
andFruit
), we have to add a new visit method inProductVisitor
interface.I will just add one class in hierarch and i had to modify
ProductVisitor
interface by adding newvisit(electronic: Electronic): void;
as a result, two visitor classes which implement this interface have to be modified. More visitor classes mean more modification with every new class in hierarch.
Use case 1
// =========================================================
// DOMAIN LAYER
// The heart of the system, where the main business logic resides.
// Entities, value objects, and domain events are typically found here.
// =========================================================
// =============== INTERFACE DEFINITIONS ===================
// Abstract contracts that shape the interactions within the domain.
interface Product {
accept(visitor: ProductVisitor): void;
}
interface ProductVisitor {
visit(book: Book): void;
visit(fruit: Fruit): void;
}
// ================== PRODUCT AGGREGATES ====================
// Aggregates ensure that all operations on the contained entities and
// value objects maintain consistency and invariants.
class Book implements Product {
title: string;
author: string;
isbn: string;
constructor(
title: string,
author: string,
isbn: string,
) {
this.title = title;
this.author = author;
this.isbn = isbn;
}
accept(visitor: ProductVisitor): void {
visitor.visit(this);
}
}
class Fruit implements Product {
name: string;
expiryDate: Date;
constructor(
name: string,
expiryDate: Date,
) {
this.name = name;
this.expiryDate = expiryDate;
}
accept(visitor: ProductVisitor): void {
visitor.visit(this);
}
}
// ================== DOMAIN SERVICES ========================
// Domain services hold specific operations, which don't naturally fit
// within an entity or value object.
class PrintDetailsVisitor implements ProductVisitor {
visit(book: Book): void;
visit(fruit: Fruit): void;
visit(product: Book | Fruit): void {
if (product instanceof Book) {
this.printBookDetails(product);
} else if (product instanceof Fruit) {
this.printFruitDetails(product);
}
}
private printBookDetails(book: Book){
// Print something
}
private printFruitDetails(fruit: Fruit){
// Print something
}
}
class CalculatePriceVisitor implements ProductVisitor {
visit(book: Book): void;
visit(fruit: Fruit): void;
visit(product: Book | Fruit): void {
if (product instanceof Book) {
this.calculateBookPrice(product);
} else if (product instanceof Fruit) {
this.calculateFruitPrice(product);
}
}
private calculateBookPrice(book: Book){
// calculate book price
}
private calculateFruitPrice(fruit: Fruit){
// calculate fruit price
}
}
Use case 1: Cost of adding new class in hierarch
By adding
Electronic
class in product class hierarchy will affectProductVisitor
,PrintDetailsVisitor
, andCalculatePriceVisitor
so if there are 10 visitor classes, thenProductVisitor
interface and 10 visitor classes need to be changed.
// =========================================================
// DOMAIN LAYER
// The heart of the system, where the main business logic resides.
// Entities, value objects, and domain events are typically found here.
// =========================================================
// =============== INTERFACE DEFINITIONS ===================
// Abstract contracts that shape the interactions within the domain.
interface Product {
accept(visitor: ProductVisitor): void;
}
interface ProductVisitor {
visit(book: Book): void;
visit(fruit: Fruit): void;
visit(electronic: Electronic): void; // NEW
}
// ================== PRODUCT AGGREGATES ====================
// Aggregates ensure that all operations on the contained entities and
// value objects maintain consistency and invariants.
// ... (Other product components like Book, Fruit, etc.)
// NEW: Added a new class to the product hierarchy for electronics
class Electronic implements Product {
name: string;
price: number;
model: string;
constructor(name: string, price: number, model: string) {
this.name = name;
this.price = price;
this.model = model;
}
accept(visitor: ProductVisitor): void {
visitor.visit(this);
}
}
// ================== DOMAIN SERVICES ========================
// Domain services hold specific operations, which don't naturally fit
// within an entity or value object.
// ... (Other domain services like PrintDetailsVisitor, CalculatePriceVisitor, etc.)
class PrintDetailsVisitor implements ProductVisitor {
visit(book: Book): void;
visit(fruit: Fruit): void;
visit(electronic: Electronic): void // NEW
visit(product: Book | Fruit | Electronic): void { // NEW
if (product instanceof Book) {
this.printBookDetails(product);
} else if (product instanceof Fruit) {
this.printFruitDetails(product);
} else if (product instanceof Electronic) { // NEW
this.printElectronicDetails(product);
}
}
private printBookDetails(book: Book){
// Print something
}
private printFruitDetails(fruit: Fruit){
// Print something
}
// NEW
private printElectronicDetails(electronic: Electronic){
// Print something
}
}
class CalculatePriceVisitor implements ProductVisitor {
visit(book: Book): void;
visit(fruit: Fruit): void;
visit(electronic: Electronic): void // NEW
visit(product: Book | Fruit | Electronic): void { // NEW
if (product instanceof Book) {
this.calculateBookPrice(product);
} else if (product instanceof Fruit) {
this.calculateFruitPrice(product);
} else if (product instanceof Electronic) { // NEW
this.calculateElectronicPrice(product);
}
}
private calculateBookPrice(book: Book){
// calculate book price
}
private calculateFruitPrice(fruit: Fruit){
// calculate fruit price
}
// NEW
private calculateElectronicPrice(electronic: Electronic){
// calculate electronic price
}
}
Use case 2
Interfaces
interface VisitableShape {
accept<T>(visitor: ShapeVisitor<T>): T;
}
interface ShapeVisitor<T> {
visit(circle: Circle): T;
visit(rectangle: Rectangle): T;
visit(square: Square): T;
}
A set of Objects
Notice that
Shape
classes don't have any method(behaviour), instead, it hasaccept
method to send its reference by callingvisitor.visit(this)
. So, visitor class name determine operation need to be done on shape.
class Circle implements VisitableShape {
constructor(public radius: number) {}
accept<T>(visitor: ShapeVisitor<T>): T {
return visitor.visit(this);
}
}
class Rectangle implements VisitableShape {
constructor(public width: number, public height: number) {}
accept<T>(visitor: ShapeVisitor<T>): T {
return visitor.visit(this);
}
}
class Square extends Rectangle {
constructor(public sideLength: number) {
super(sideLength, sideLength);
}
}
Visitors
There are 8 visitor classes as below, in terms of Visitor Design Pattern, instead of adding behaviours in
Shape
classes, each behaviour(method) is externalised by one Visitor class. For example, instead of addingcalculateArea
method inShape
class, createAreaCalculatorVisitor
class and
– AreaCalculatorVisitor = calculateArea
– PerimeterCalculatorVisitor = calculatePerimeter
– DiagonalDiameterCalculatorVisitor = calculateDiagonalDiameter
– CentroidCalculatorVisitor = calculateCenter
– ScalingCalculatorVisitor = calculateScaling or resize or scale etc
– ShapeDuplicatorVisitor = duplicateShape or clone etc
– RotationCalculatorVisitor = calculateRotation or rotate etc
– AreaComparerVisitor = compareArea
class AreaCalculatorVisitor implements ShapeVisitor<number> {
visit(circle: Circle): number;
visit(rectangle: Rectangle): number;
visit(square: Square): number;
visit(shape: Circle | Rectangle | Square): number {
if (shape instanceof Circle) {
return this.calculateCircleArea(shape);
} else if (shape instanceof Rectangle) {
return this.calculateRectangleArea(shape);
}
}
private calculateCircleArea(circle: Circle) {
return Math.PI * Math.pow(circle.radius, 2);
}
private calculateRectangleArea(rectangle: Rectangle) {
return rectangle.width * rectangle.height;
}
}
class PerimeterCalculatorVisitor implements ShapeVisitor<number> {
visit(circle: Circle): number;
visit(rectangle: Rectangle): number;
visit(square: Square): number;
visit(shape: Circle | Rectangle | Square): number {
if (shape instanceof Circle) {
return this.calculateCirclePerimeter(shape);
} else if (shape instanceof Rectangle) {
return this.calculateRectanglePerimeter(shape);
}
}
private calculateCirclePerimeter(circle: Circle) {
return 2 * Math.PI * circle.radius;
}
private calculateRectanglePerimeter(rectangle: Rectangle) {
return 2 * (rectangle.width + rectangle.height);
}
}
class DiagonalDiameterCalculatorVisitor implements ShapeVisitor<number> {
visit(circle: Circle): number;
visit(rectangle: Rectangle): number;
visit(square: Square): number;
visit(shape: Circle | Rectangle | Square): number {
if (shape instanceof Circle) {
return this.calculateCircleDiagonalDiameter(shape);
} else if (shape instanceof Rectangle) {
return this.calculateRectangleDiagonalDiameter(shape);
}
}
private calculateCircleDiagonalDiameter(circle: Circle): number {
return 2 * circle.radius;
}
private calculateRectangleDiagonalDiameter(rectangle: Rectangle): number {
return Math.sqrt(
rectangle.width * rectangle.width + rectangle.height * rectangle.height
);
}
}
class Point {
readonly x: number;
readonly y: number;
constructor(x: number, y: number) {
this.x = x;
this.y = y;
}
}
class CentroidCalculatorVisitor implements ShapeVisitor<Point> {
visit(circle: Circle): Point;
visit(rectangle: Rectangle): Point;
visit(square: Square): Point;
visit(shape: Circle | Rectangle | Square): Point {
if (shape instanceof Circle) {
return this.calculateCircleCenter(shape);
} else if (shape instanceof Rectangle) {
return this.calculateRectangleCenter(shape);
}
}
private calculateCircleCenter(circle: Circle): Point {
return new Point(0, 0); // Assuming the circle is centered at the origin
}
private calculateRectangleCenter(rectangle: Rectangle): Point {
return new Point(rectangle.width / 2, rectangle.height / 2); // Centroid is at half its width and half its height
}
}
class ScalingCalculatorVisitor implements ShapeVisitor<void> {
private scaleFactor: number;
constructor(scaleFactor: number) {
this.scaleFactor = scaleFactor;
}
visit(circle: Circle): void;
visit(rectangle: Rectangle): void;
visit(square: Square): void;
visit(shape: Circle | Rectangle | Square): void {
if (shape instanceof Circle) {
this.calculateCircleScaling(shape);
} else if (shape instanceof Rectangle) {
this.calculateRectangleScaling(shape);
}
}
private calculateCircleScaling(circle: Circle): void {
circle.radius *= this.scaleFactor;
}
private calculateRectangleScaling(rectangle: Rectangle): void {
rectangle.width *= this.scaleFactor;
rectangle.height *= this.scaleFactor;
}
}
class ShapeDuplicatorVisitor implements ShapeVisitor<VisitableShape> {
visit(circle: Circle): VisitableShape;
visit(rectangle: Rectangle): VisitableShape;
visit(square: Square): VisitableShape;
visit(shape: Circle | Rectangle | Square): VisitableShape {
if (shape instanceof Circle) {
this.duplicateCircle(shape);
} else if (shape instanceof Rectangle) {
this.duplicateRectangle(shape);
}
return undefined;
}
private duplicateCircle(circle: Circle): VisitableShape {
return new Circle(circle.radius);
}
private duplicateRectangle(rectangle: Rectangle): VisitableShape {
return new Rectangle(rectangle.width, rectangle.height);
}
}
class RotationCalculatorVisitor implements ShapeVisitor<void> {
visit(circle: Circle): void;
visit(rectangle: Rectangle): void;
visit(square: Square): void;
visit(shape: Circle | Rectangle | Square): void {
if (shape instanceof Circle) {
this.calculateCircleRotation(shape);
} else if (shape instanceof Rectangle) {
this.calculateRectangleRotation(shape);
}
}
private calculateCircleRotation(circle: Circle): void {
// For the Circle, rotation doesn't affect its dimensions.
// No changes as rotating a circle has no visual effect on its properties.
}
private calculateRectangleRotation(rectangle: Rectangle): void {
// For the Rectangle, we'll swap its width and height.
const temp = rectangle.width;
rectangle.width = rectangle.height;
rectangle.height = temp;
}
}
enum AreaComparison {
Larger,
Equal,
Smaller,
}
class AreaComparerVisitor implements ShapeVisitor<AreaComparison> {
private areaCalculator = new AreaCalculatorVisitor();
constructor(private secondShape: VisitableShape) {}
visit(circle: Circle): AreaComparison;
visit(rectangle: Rectangle): AreaComparison;
visit(square: Square): AreaComparison;
visit(shape: Circle | Rectangle | Square): AreaComparison {
if (shape instanceof Circle) {
return this.compareCircle(shape);
} else if (shape instanceof Rectangle) {
this.compareRectangle(shape);
}
}
private compareCircle(circle: Circle): AreaComparison {
return this.compareAreas(circle, this.secondShape);
}
private compareRectangle(rectangle: Rectangle): AreaComparison {
return this.compareAreas(rectangle, this.secondShape);
}
private compareAreas(
firstShape: VisitableShape,
secondShape: VisitableShape
): AreaComparison {
const firstShapeArea = firstShape.accept(this.areaCalculator);
const secondShapeArea = secondShape.accept(this.areaCalculator);
if (firstShapeArea > secondShapeArea) {
return AreaComparison.Larger;
} else if (firstShapeArea < secondShapeArea) {
return AreaComparison.Smaller;
} else {
return AreaComparison.Equal;
}
}
}
Application
function printShapeDetails(shapes: VisitableShape[]) {
const perimeterCalculator = new PerimeterCalculatorVisitor();
const areaCalculator = new AreaCalculatorVisitor();
const diagonalDiameterCalculator = new DiagonalDiameterCalculatorVisitor();
const centroidCalculator = new CentroidCalculatorVisitor();
const scalingCalculator = new ScalingCalculatorVisitor(2);
const shapeDuplicator = new ShapeDuplicatorVisitor();
const rotationCalculator = new RotationCalculatorVisitor();
const circle: VisitableShape = new Circle(5);
const areaComparer = new AreaComparerVisitor(circle);
for (const shape of shapes) {
const name = `${shape.constructor.name}`;
const shapePerimeter = shape.accept(perimeterCalculator);
const shapeArea = shape.accept(areaCalculator);
const shapeDiagonalDiameter = shape.accept(diagonalDiameterCalculator);
const shapeCenter = shape.accept(centroidCalculator);
const duplicatedShape = shape.accept(shapeDuplicator);
console.log(`Perimeter of ${name}: ${shapePerimeter}`);
console.log(`Area of ${name}: ${shapeArea}`);
console.log(`Diagonal Diameter of ${name}: ${shapeDiagonalDiameter}`);
console.log(`Center of ${name}: ${shapeCenter}`);
console.log(
`Duplicated shape is created: ${JSON.stringify(duplicatedShape)}`
);
shape.accept(scalingCalculator);
console.log(`Resized dimensions of ${name}: ${JSON.stringify(shape)}`);
shape.accept(rotationCalculator);
console.log(`Rotated shape: ${JSON.stringify(shape)}`);
const comparisonResult = shape.accept(areaComparer);
console.log(`Area comparison result: ${comparisonResult}`);
}
}
const circle: VisitableShape = new Circle(5);
const rectangle: VisitableShape = new Rectangle(4, 6);
const square: VisitableShape = new Square(4);
const shapes: VisitableShape[] = [circle, rectangle, square];
printShapeDetails(shapes);
Use case 2: Cost of adding new class in hierarchy
Triangle
class is added in hierarchy and 8 Visitor classes and 1 Visitor interface are updated. So, adding new class will affect all visitor classes.
class Triangle implements VisitableShape {
constructor(public base: number, public height: number) {}
accept<T>(visitor: ShapeVisitor<T>): T {
return visitor.visit(this);
}
}
interface ShapeVisitor<T> {
visit(circle: Circle): T;
visit(rectangle: Rectangle): T;
visit(square: Square): T;
visit(triangle: Triangle): T;
}
class AreaCalculatorVisitor implements ShapeVisitor<number> {
visit(circle: Circle): number;
visit(rectangle: Rectangle): number;
visit(square: Square): number;
visit(triangle: Triangle): number;
visit(shape: Circle | Rectangle | Square | Triangle): number {
if (shape instanceof Circle) {
return this.calculateCircleArea(shape);
} else if (shape instanceof Rectangle) {
return this.calculateRectangleArea(shape);
} else if (shape instanceof Triangle) {
return this.calculateTriangleArea(shape);
}
}
private calculateCircleArea(circle: Circle) {
return Math.PI * Math.pow(circle.radius, 2);
}
private calculateRectangleArea(rectangle: Rectangle) {
return rectangle.width * rectangle.height;
}
private calculateTriangleArea(triangle: Triangle) {
return 0.5 * triangle.base * triangle.height;
}
}
class PerimeterCalculatorVisitor implements ShapeVisitor<number> {
visit(circle: Circle): number;
visit(rectangle: Rectangle): number;
visit(square: Square): number;
visit(triangle: Triangle): number;
visit(shape: Circle | Rectangle | Square | Triangle): number {
if (shape instanceof Circle) {
return this.calculateCirclePerimeter(shape);
} else if (shape instanceof Rectangle) {
return this.calculateRectanglePerimeter(shape);
} else if (shape instanceof Triangle) {
return this.calculateTrianglePerimeter(shape);
}
}
private calculateCirclePerimeter(circle: Circle) {
return 2 * Math.PI * circle.radius;
}
private calculateRectanglePerimeter(rectangle: Rectangle) {
return 2 * (rectangle.width + rectangle.height);
}
private calculateTrianglePerimeter(triangle: Triangle) {
// For a right triangle: perimeter = base + height + hypotenuse
const hypotenuse = Math.sqrt(triangle.base**2 + triangle.height**2);
return triangle.base + triangle.height + hypotenuse;
}
}
class DiagonalDiameterCalculatorVisitor implements ShapeVisitor<number> {
visit(circle: Circle): number;
visit(rectangle: Rectangle): number;
visit(square: Square): number;
visit(triangle: Triangle): number;
visit(shape: Circle | Rectangle | Square | Triangle): number {
if (shape instanceof Circle) {
return this.calculateCircleDiagonalDiameter(shape);
} else if (shape instanceof Rectangle) {
return this.calculateRectangleDiagonalDiameter(shape);
} else if (shape instanceof Triangle) {
return this.calculateTriangleDiagonalDiameter(shape);
}
}
private calculateCircleDiagonalDiameter(circle: Circle): number {
return 2 * circle.radius;
}
private calculateRectangleDiagonalDiameter(rectangle: Rectangle): number {
return Math.sqrt(
rectangle.width * rectangle.width + rectangle.height * rectangle.height
);
}
private calculateTriangleDiagonalDiameter(triangle: Triangle): number {
// For a right triangle, the hypotenuse is the longest side.
return Math.sqrt(triangle.base**2 + triangle.height**2);
}
}
class Point {
readonly x: number;
readonly y: number;
constructor(x: number, y: number) {
this.x = x;
this.y = y;
}
}
class CentroidCalculatorVisitor implements ShapeVisitor<Point> {
visit(circle: Circle): Point;
visit(rectangle: Rectangle): Point;
visit(square: Square): Point;
visit(triangle: Triangle): number;
visit(shape: Circle | Rectangle | Square | Triangle): number {
if (shape instanceof Circle) {
return this.calculateCircleCenter(shape);
} else if (shape instanceof Rectangle) {
return this.calculateRectangleCenter(shape);
} else if (shape instanceof Triangle) {
return this.calculateTriangleCenter(shape);
}
}
private calculateCircleCenter(circle: Circle): Point {
return new Point(0, 0); // Assuming the circle is centered at the origin
}
private calculateRectangleCenter(rectangle: Rectangle): Point {
return new Point(rectangle.width / 2, rectangle.height / 2); // Centroid is at half its width and half its height
}
private calculateTriangleCenter(triangle: Triangle): Point {
// For a right triangle with base along the x-axis and height along y-axis:
return { x: triangle.base / 3, y: triangle.height / 3 };
}
}
class ScalingCalculatorVisitor implements ShapeVisitor<void> {
private scaleFactor: number;
constructor(scaleFactor: number) {
this.scaleFactor = scaleFactor;
}
visit(circle: Circle): void;
visit(rectangle: Rectangle): void;
visit(square: Square): void;
visit(triangle: Triangle): number;
visit(shape: Circle | Rectangle | Square | Triangle): number {
if (shape instanceof Circle) {
this.calculateCircleScaling(shape);
} else if (shape instanceof Rectangle) {
this.calculateRectangleScaling(shape);
} else if (shape instanceof Triangle) {
return this.calculateTriangleScaling(shape);
}
}
private calculateCircleScaling(circle: Circle): void {
circle.radius *= this.scaleFactor;
}
private calculateRectangleScaling(rectangle: Rectangle): void {
rectangle.width *= this.scaleFactor;
rectangle.height *= this.scaleFactor;
}
private calculateTriangleScaling(triangle: Triangle): void {
const scaledBase = triangle.base * this.scaleFactor;
const scaledHeight = triangle.height * this.scaleFactor;
triangle.base = scaledBase;
triangle.height = scaledHeight;
}
}
class ShapeDuplicatorVisitor implements ShapeVisitor<VisitableShape> {
visit(circle: Circle): VisitableShape;
visit(rectangle: Rectangle): VisitableShape;
visit(square: Square): VisitableShape;
visit(triangle: Triangle): number;
visit(shape: Circle | Rectangle | Square | Triangle): number {
if (shape instanceof Circle) {
this.duplicateCircle(shape);
} else if (shape instanceof Rectangle) {
this.duplicateRectangle(shape);
} else if (shape instanceof Triangle) {
this.duplicateTriangle(shape);
}
return undefined;
}
private duplicateCircle(circle: Circle): VisitableShape {
return new Circle(circle.radius);
}
private duplicateRectangle(rectangle: Rectangle): VisitableShape {
return new Rectangle(rectangle.width, rectangle.height);
}
private duplicateTriangle(triangle: Triangle): VisitableShape {
return new Triangle(triangle.base, triangle.height);
}
}
class RotationCalculatorVisitor implements ShapeVisitor<void> {
visit(circle: Circle): void;
visit(rectangle: Rectangle): void;
visit(square: Square): void;
visit(triangle: Triangle): number;
visit(shape: Circle | Rectangle | Square | Triangle): number {
if (shape instanceof Circle) {
this.calculateCircleRotation(shape);
} else if (shape instanceof Rectangle) {
this.calculateRectangleRotation(shape);
} else if (shape instanceof Triangle) {
this.calculateTriangleRotation(shape);
}
}
private calculateCircleRotation(circle: Circle): void {
// For the Circle, rotation doesn't affect its dimensions.
// No changes as rotating a circle has no visual effect on its properties.
}
private calculateRectangleRotation(rectangle: Rectangle): void {
// For the Rectangle, we'll swap its width and height.
const temp = rectangle.width;
rectangle.width = rectangle.height;
rectangle.height = temp;
}
private calculateTriangleRotation(triangle: Triangle): void {
// For simplicity, we'll rotate the right-angle vertex of the triangle
const rotatedVertex = this.rotatePoint({ x: triangle.base, y: triangle.height });
const newBase = rotatedVertex.x;
const newHeight = rotatedVertex.y;
triangle.base = newBase;
triangle.height = newHeight;
}
}
enum AreaComparison {
Larger,
Equal,
Smaller,
}
class AreaComparerVisitor implements ShapeVisitor<AreaComparison> {
private areaCalculator = new AreaCalculatorVisitor();
constructor(private secondShape: VisitableShape) {}
visit(circle: Circle): AreaComparison;
visit(rectangle: Rectangle): AreaComparison;
visit(square: Square): AreaComparison;
visit(triangle: Triangle): number;
visit(shape: Circle | Rectangle | Square | Triangle): number {
if (shape instanceof Circle) {
return this.compareCircle(shape);
} else if (shape instanceof Rectangle) {
this.compareRectangle(shape);
} else if (shape instanceof Triangle) {
this.compareTriangle(shape);
}
}
private compareCircle(circle: Circle): AreaComparison {
return this.compareAreas(circle, this.secondShape);
}
private compareRectangle(rectangle: Rectangle): AreaComparison {
return this.compareAreas(rectangle, this.secondShape);
}
private compareTriangle(triangle: Triangle): AreaComparison {
return this.compareAreas(triangle, this.secondShape);
}
private compareAreas(
firstShape: VisitableShape,
secondShape: VisitableShape
): AreaComparison {
const firstShapeArea = firstShape.accept(this.areaCalculator);
const secondShapeArea = secondShape.accept(this.areaCalculator);
if (firstShapeArea > secondShapeArea) {
return AreaComparison.Larger;
} else if (firstShapeArea < secondShapeArea) {
return AreaComparison.Smaller;
} else {
return AreaComparison.Equal;
}
}
}
const circle: VisitableShape = new Circle(5);
const rectangle: VisitableShape = new Rectangle(4, 6);
const square: VisitableShape = new Square(4);
const triangle: VisitableShape = new Triangle();
const shapes: VisitableShape[] = [circle, rectangle, square, triangle];
printShapeDetails(shapes);