Last active
December 30, 2019 17:15
-
-
Save cdanyl/03b81c5968e6ab831eee36237019fdb8 to your computer and use it in GitHub Desktop.
Visitor Pattern
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /* | |
| * Visitor pattern solves the problem elegantly. Start by modifying our base Fruit class: | |
| */ | |
| interface FruitVisitor { | |
| visit(fruit: Orange): void; | |
| visit(fruit: Apple): void; | |
| visit(fruit: Banana): void; | |
| } | |
| abstract class Fruit { | |
| abstract accept(visitor: FruitVisitor): void | |
| } | |
| class Orange extends Fruit { | |
| public accept(visitor: FruitVisitor): void { | |
| visitor.visit(this); | |
| } | |
| } | |
| class Apple extends Fruit { | |
| public accept(visitor: FruitVisitor): void { | |
| visitor.visit(this); | |
| } | |
| } | |
| class Banana extends Fruit { | |
| public accept(visitor: FruitVisitor): void { | |
| visitor.visit(this); | |
| } | |
| } | |
| /* | |
| * It looks like we're copy pasting code, but note the derived classes are all calling different overloads (the Apple calls Visit(Apple), the Banana calls Visit(Banana), and so on). | |
| * Implement the visitor: | |
| */ | |
| class FruitPartitioner implements FruitVisitor { | |
| public oranges: Orange[] = []; | |
| public apples: Apple[] = []; | |
| public bananas: Banana[] = []; | |
| public visit(fruit: Orange): void { | |
| this.oranges.push(fruit); | |
| } | |
| public visit(fruit: Apple): void { | |
| this.apples.push(fruit); | |
| } | |
| public visit(fruit: Banana): void { | |
| this.bananas.push(fruit); | |
| } | |
| } | |
| /* | |
| * And let's say we create a Fruit[]: | |
| */ | |
| const fruits: Fruit[] = []; | |
| fruits.push(new Orange()); | |
| fruits.push(new Apple()); | |
| fruits.push(new Banana()); | |
| fruits.push(new Banana()); | |
| fruits.push(new Orange()); | |
| /* | |
| * Now you can partition your fruits without a type-test: | |
| */ | |
| const fruitPartitioner = new FruitPartitioner(); | |
| for (const fruit of fruits) { | |
| fruit.accept(fruitPartitioner); | |
| } | |
| console.log(fruitPartitioner.oranges); | |
| console.log(fruitPartitioner.apples); | |
| console.log(fruitPartitioner.bananas); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /** | |
| * Visitor Pattern in Typescript. | |
| * So you've probably read a bajillion different explanations of the visitor pattern, and you're probably still saying "but when would you use it!" | |
| * Traditionally, visitors are used to implement type-testing without sacrificing type-safety, | |
| * so long as your types are well-defined up front and known in advance. | |
| */ | |
| /* | |
| * Let's say we have a few classes as follows: | |
| */ | |
| abstract class Fruit {} | |
| class Orange extends Fruit {} | |
| class Apple extends Fruit {} | |
| class Banana extends Fruit {} | |
| /* | |
| * And let's say we create a Fruit[]: | |
| */ | |
| const fruits: Fruit[] = []; | |
| fruits.push(new Orange()); | |
| fruits.push(new Apple()); | |
| fruits.push(new Banana()); | |
| fruits.push(new Banana()); | |
| fruits.push(new Orange()); | |
| /* | |
| * I want to partition the list in to three lists, each containing oranges, apples, or bananas. | |
| * How would you do it? Well, the easy solution would be a type-test: | |
| */ | |
| const oranges: Orange[] = []; | |
| const apples: Apple[] = []; | |
| const bananas: Banana[] = []; | |
| fruits.forEach((fruit) => { | |
| if (fruit instanceof Orange) { | |
| oranges.push(fruit); | |
| } else if (fruit instanceof Apple) { | |
| apples.push(fruit); | |
| } else if (fruit instanceof Banana) { | |
| bananas.push(fruit); | |
| } | |
| }); | |
| /* | |
| * It works, but there are lots of problems with this code: | |
| * For a start, its ugly. | |
| * Its not type-safe, we won't catch type errors until runtime. | |
| * Its not maintainable. If we add a new derived instance of Fruit, we need to do a global search for every place which performs a fruit type-test, otherwise we might miss types. | |
| */ | |
| console.log(oranges); | |
| console.log(apples); | |
| console.log(bananas); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment