Interpreter Pattern
Overview
Select items tagged article, or items tagged both video and captioned. Instead of copying nested conditionals into every caller, represent the conditions and their combinations as objects.
Interpreter gives each expression an operation that evaluates its meaning. A tag test is a terminal expression; AND and OR combine other expressions in this small rule language.
UML
HasTag, And and Or implement IExpr. And and Or refer to two child expressions and combine their results.
History
GoF cataloged Interpreter as a behavioral pattern in Design Patterns in 1994. A grammar representation carries operations that evaluate its meaning.1
Why use it?
A composed rule can be stored and reused as a structure. Callers evaluate the whole expression while each node determines how to use its children.
The example does not parse a rule string into that structure. A larger grammar or external input also calls for parser tooling and limits on expression size and depth.
Example
Start with one expression that checks a tag.
using System;
using System.Collections.Generic;
using System.Linq;
IExpr rule = new HasTag("article");
Console.WriteLine(rule.Eval(new HashSet<string> { "article" }));
// Different expression kinds evaluate the same input to a boolean result.
interface IExpr { bool Eval(IReadOnlySet<string> tags); }
sealed record HasTag(string Tag) : IExpr
{
public bool Eval(IReadOnlySet<string> tags) => tags.Contains(Tag);
}
// Delegate to child expressions and own only the combination rule.
sealed record And(IExpr Left, IExpr Right) : IExpr
{
public bool Eval(IReadOnlySet<string> tags) => Left.Eval(tags) && Right.Eval(tags);
}
sealed record Or(IExpr Left, IExpr Right) : IExpr
{
public bool Eval(IReadOnlySet<string> tags) => Left.Eval(tags) || Right.Eval(tags);
}
TrueHasTag is the terminal expression. It tests set membership rather than executing a string as code.
An extended example
Build article OR (video AND captioned) as an expression tree and evaluate three inputs.
using System;
using System.Collections.Generic;
using System.Linq;
IExpr rule = new Or(new HasTag("article"), new And(new HasTag("video"), new HasTag("captioned")));
foreach (var tags in new[]
{
new HashSet<string> { "article" },
new HashSet<string> { "video" },
new HashSet<string> { "video", "captioned" }
}) Console.WriteLine(rule.Eval(tags));
// Different expression kinds evaluate the same input to a boolean result.
interface IExpr { bool Eval(IReadOnlySet<string> tags); }
sealed record HasTag(string Tag) : IExpr
{
public bool Eval(IReadOnlySet<string> tags) => tags.Contains(Tag);
}
// Delegate to child expressions and own only the combination rule.
sealed record And(IExpr Left, IExpr Right) : IExpr
{
public bool Eval(IReadOnlySet<string> tags) => Left.Eval(tags) && Right.Eval(tags);
}
sealed record Or(IExpr Left, IExpr Right) : IExpr
{
public bool Eval(IReadOnlySet<string> tags) => Left.Eval(tags) || Right.Eval(tags);
}
True
False
TrueAnd and Or combine their child results. A video alone fails, while a captioned video passes. This is a content-classification example, not an authorization system.
TypeScript example
Expression objects separate a single tag test from AND and OR combinations. Callers compose an expression tree and evaluate it through eval() instead of rebuilding a large conditional.
// Different expression kinds evaluate the same input to a boolean result.
interface IExpr { eval(tags: ReadonlySet<string>): boolean; }
class HasTag implements IExpr {
constructor(private readonly tag: string) {}
eval(tags: ReadonlySet<string>): boolean { return tags.has(this.tag); }
}
// Delegate to child expressions and own only the combination rule.
class And implements IExpr {
constructor(private readonly left: IExpr, private readonly right: IExpr) {}
eval(tags: ReadonlySet<string>): boolean { return this.left.eval(tags) && this.right.eval(tags); }
}
class Or implements IExpr {
constructor(private readonly left: IExpr, private readonly right: IExpr) {}
eval(tags: ReadonlySet<string>): boolean { return this.left.eval(tags) || this.right.eval(tags); }
}
const rule: IExpr = new Or(new HasTag("article"), new And(new HasTag("video"), new HasTag("captioned")));
for (const tags of [["article"], ["video", "captioned"], ["video"]]) {
console.log(rule.eval(new Set(tags)));
}true
true
falseAND and OR evaluate their right operand only when needed. The example builds expressions directly as objects; it is not a string parser or an arbitrary-code evaluator.
Related reading
Each C# example is a separate .NET 10 console program.
Compile the TypeScript examples for ES2022 or later and run the emitted JavaScript with Node.js.
Footnotes
- Gamma, Helm, Johnson, Vlissides. Design Patterns (1994). Publisher record. Behavioral patterns, Interpreter. β©