Lab
Visitor
// One method for each kind of node an article is made of.
interface Visitor {
visitText(node: TextNode): void;
visitImage(node: ImageNode): void;
visitGroup(node: GroupNode): void;
}
interface ArticleNode {
accept(visitor: Visitor): void;
}
class TextNode implements ArticleNode {
text: string;
constructor(text: string) {
this.text = text;
}
accept(visitor: Visitor): void {
visitor.visitText(this);
}
}
class ImageNode implements ArticleNode {
src: string;
alt: string;
constructor(src: string, alt: string) {
this.src = src;
this.alt = alt;
}
accept(visitor: Visitor): void {
visitor.visitImage(this);
}
}
class GroupNode implements ArticleNode {
draft: boolean;
children: ArticleNode[];
constructor(draft: boolean, children: ArticleNode[]) {
this.draft = draft;
this.children = children;
}
// Dispatch only: the visitor decides whether to walk the children.
accept(visitor: Visitor): void {
visitor.visitGroup(this);
}
}
// One operation: the readable text, without unpublished drafts.
class TextExport implements Visitor {
lines: string[] = [];
visitText(node: TextNode): void {
this.lines.push(node.text);
}
visitImage(node: ImageNode): void {
if (node.alt !== '') this.lines.push(node.alt);
}
visitGroup(node: GroupNode): void {
if (node.draft) return;
for (const child of node.children) child.accept(this);
}
}
// Another: every image file, drafts included, so the build uploads them all.
class AssetInventory implements Visitor {
files: string[] = [];
visitText(_node: TextNode): void {}
visitImage(node: ImageNode): void {
this.files.push(node.src);
}
visitGroup(node: GroupNode): void {
for (const child of node.children) child.accept(this);
}
}
// The callers: a fresh visitor for each run.
function exportText(root: ArticleNode): string[] {
const visitor = new TextExport();
root.accept(visitor);
return visitor.lines;
}
function listAssets(root: ArticleNode): string[] {
const visitor = new AssetInventory();
root.accept(visitor);
return visitor.files;
}
const article = new GroupNode(false, [
new TextNode('Trail guide'),
new ImageNode('/cover.png', 'A branching trail'),
new GroupNode(true, [
new TextNode('Unpublished notes'),
new ImageNode('/chart.png', 'Elevation chart'),
]),
]);
const text = exportText(article);
const assets = listAssets(article);
console.log(`text: ${text.join(' | ')}`);
console.log(`assets: ${assets.join(' | ')}`);
TODO
Visual to come