- Given a language, define a representation for its grammar along with an interpreter that uses the representation to interpret sentences in the language.
- The Interpreter design pattern solves this particular problem – that of creating a scripting language that allows the end user to customize their solution.
- The truth is that if you really need this type of control it is probably easier and faster to use an existing command interpreter or expression evaluator tool out of the box.
- Certain types of problems lend themselves to be characterized by a language.
- This language describes the problem domain which should be well-understood and well-defined.
- In addition, this language needs to be mapped to a grammar.
- Grammars are usually hierarchical tree-like structures that step through multiple levels but end up with terminal nodes (also called literals).
- This type of problem, expressed as a grammar, can be implemented using the Interpreter design pattern.
- The well-known Towers of Hanoi puzzle is an example of the type of problem that can be encoded by a simple grammar and implemented using the Interpreter design pattern.
Participants
AbstractExpression
(IExpression)
- Declares an interface for executing an operation
TerminalExpression
(Sandwich, CondimentList, IngredientList)
- Implements an Interpret operation associated with terminal symbols in the grammar.
- An instance is required for every terminal symbol in the sentence.
NonterminalExpression
(IBread, ICondiment, IIngredient)
- Inherits the type
AbstractExpression
.
- Implements the Interpret operation for all nonterminal expressions.
- Implements an Interpret operation for nonterminal symbols in the grammar.
Context
(Context)
- Contains information that is global to the interpreter.
var topBread = new WheatBread();
var topCondiments = new CondimentList(new List<ICondiment> { new MayoCondiment(), new MustardCondiment() });
var ingredients = new IngredientList(new List<IIngredient> { new LettuceIngredient(), new ChickenIngredient() });
var bottomCondiments = new CondimentList(new List<ICondiment> { new KetchupCondiment() });
var bottomBread = new WheatBread();
var sandwhich = new Sandwhich(topBread, topCondiments, ingredients, bottomCondiments, bottomBread);
var context = new Context();
sandwhich.Interpret(context);
public class Context
{
public string Output { get; set; }
}
public interface IExpression
{
void Interpret(Context context);
}
public class Sandwhich : IExpression
{
private readonly IExpression _TopBread;
private readonly IExpression _TopCondiments;
private readonly IExpression _Ingredients;
private readonly IExpression _BottomCondiments;
private readonly IExpression _BottomBread;
public Sandwhich(
IExpression topBread,
IExpression topCondiments,
IExpression ingredients,
IExpression bottomCondiments,
IExpression bottomBread
)
{
_TopBread = topBread;
_TopCondiments = topCondiments;
_Ingredients = ingredients;
_BottomCondiments = bottomCondiments;
_BottomBread = bottomBread;
}
public void Interpret(Context context)
{
context.Output += "|";
_TopBread.Interpret(context);
context.Output += "|";
context.Output += "<--";
_TopCondiments.Interpret(context);
context.Output += "-";
_Ingredients.Interpret(context);
context.Output += "-";
_BottomCondiments.Interpret(context);
context.Output += "-->";
context.Output += "|";
_BottomBread.Interpret(context);
context.Output += "|";
}
}
public interface IIngredient : IExpression { }
public class IngredientList : IExpression
{
private readonly List<IIngredient> _Ingredients;
public IngredientList(List<IIngredient> ingredients) => _Ingredients = ingredients;
public void Interpret(Context context)
{
foreach (IIngredient ingredient in _Ingredients)
ingredient.Interpret(context);
}
}
public class TomatoIngredient : IIngredient
{
public void Interpret(Context context) => context.Output += string.Format(" {0} ", "Tomato");
}
public class LettuceIngredient : IIngredient
{
public void Interpret(Context context) => context.Output += string.Format(" {0} ", "Lettuce");
}
public class ChickenIngredient : IIngredient
{
public void Interpret(Context context) => context.Output += string.Format(" {0} ", "Chicken");
}
public class CondimentList : IExpression
{
private readonly List<ICondiment> _Condiments;
public CondimentList(List<ICondiment> condiments) => _Condiments = condiments;
public void Interpret(Context context)
{
foreach (ICondiment condiment in _Condiments)
condiment.Interpret(context);
}
}
public interface ICondiment : IExpression { }
public class MayoCondiment : ICondiment
{
public void Interpret(Context context) => context.Output += string.Format(" {0} ", "Mayo");
}
public class MustardCondiment : ICondiment
{
public void Interpret(Context context) => context.Output += string.Format(" {0} ", "Mustard");
}
public class KetchupCondiment : ICondiment
{
public void Interpret(Context context) => context.Output += string.Format(" {0} ", "Ketchup");
}
public interface IBread : IExpression { }
public class WhiteBread : IBread
{
public void Interpret(Context context) => context.Output += string.Format(" {0} ", "White-Bread");
}
public class WheatBread : IBread
{
public void Interpret(Context context) => context.Output += string.Format(" {0} ", "Wheat-Bread");
}
Rules of Thumb
- Considered in its most general form (E.g. an operation distributed over a class hierarchy based on the Composite pattern), nearly every use of the Composite pattern will also contain the Interpreter pattern.
- But the Interpreter pattern should be reserved for those cases in which you want to think of this class hierarchy as defining a language.
- Interpreter can use State to define parsing contexts.
- The abstract syntax tree of Interpreter is a Composite (therefore Iterator and Visitor are also applicable).
- Terminal symbols within Interpreter’s abstract syntax tree can be shared with Flyweight.
- The pattern doesn’t address parsing. When the grammar is very complex, other techniques (such as a parser) are more appropriate.