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" }));
interface IExpr { bool Eval(IReadOnlySet<string> tags); }
sealed record HasTag(string Tag) : IExpr
{
public bool Eval(IReadOnlySet<string> tags) => tags.Contains(Tag);
}
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));
interface IExpr { bool Eval(IReadOnlySet<string> tags); }
sealed record HasTag(string Tag) : IExpr
{
public bool Eval(IReadOnlySet<string> tags) => tags.Contains(Tag);
}
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.
Related reading
Each example is a separate .NET 10 console program.
Footnotes
- Gamma, Helm, Johnson, Vlissides. Design Patterns (1994). Publisher record. Behavioral patterns, Interpreter. β©