Design Patterns - Interpreter
In our previous blog of the design pattern series, we had discussed the command design pattern, which falls under the category of behavioral design patterns. Today, we will explore another design pattern from the same category called Interpreter Design Pattern.
Table of contents
- Introduction to Interpreter design pattern
- Implementation of Interpreter design pattern
- Conclusion
Introduction to Interpreter design pattern
An interpreter design pattern can be used to parse a certain input and interpret it as a different one based on the needs. Said differently, it can be used in language parsing, just like how Google detects a foreign language as we type and automatically points out if you meant the same in English. Such functionalities can be achieved using the interpreter design pattern.
Interpreter patterns can also be used in simple scenarios such as formatting the date to a different format than what is provided. For instance, if the date format is provided as DD-MM-YYYY, using the interpreter pattern, you can convert it to any desired format, such as MM-DD-YYYY or MM-DD-YY or MM-YYYY. It gives you the ability to choose the type of output that needs to be created from the input.
Implementation of Interpreter design pattern
The interpreter design pattern mainly consists of:
- Abstract Expression
- Terminal Expression
- Non-Terminal Expression
- Context
- Client
Here is a template that we can use to show the implementation of interpreter design pattern - using System;
namespace PARTECH_Interpreter
{
internal class Program
{
static void Main(string[] args)
{
Context ctx = new Context("PARTECH_INTERPRETER_TEMPLATE");
var parent = new NonTerminalExpression();
parent.Expression1 = new TerminalExpression();
parent.Expression2 = new TerminalExpression();
parent.Interpret(ctx);
Console.ReadLine();
}
public class Context
{
public string Name { get; set; }
public Context(string name)
{
Name = name;
}
}
public interface IExpression
{
void Interpret(Context context);
}
public class TerminalExpression : IExpression
{
public void Interpret(Context context)
{
Console.WriteLine($"Reached Terminal for context { context.Name }");
}
}
public class NonTerminalExpression : IExpression
{
public IExpression Expression1 { get; set; }
public IExpression Expression2 { get; set; }
public void Interpret(Context context)
{
Console.WriteLine($"Reached Non-Terminal for context { context.Name }");
Expression1.Interpret(context);
Expression2.Interpret(context);
}
}
}
}
Here, Context is the class that has the input and output. This is operated by the interpreter. Expression is the interface or abstract class which defines the operation of the Interpreter. The IExpression interface is implemented by each of its subclasses.
Non-Terminal Expression is a class that implements the interface IExpression, which has the option to have other instances of the expression.
Terminal Expression is a class that implements the interface IExpression and does not have the option to hold the other instances.
The main method is the client. Here the object for both terminal and non-terminal expressions are created, and terminal expression objects are assigned as properties to the non-terminal expression objects.
On executing the above template, the below output gets generated.
Now that we have understood the basic template to implement the Interpreter design pattern let us implement the same through a practical scenario.
All of us would have come across Roman numerals during our school days. Imagine you have to generate Roman numerals for one of your projects (to address a particular requirement). So, the goal is to generate these numbers dynamically based on the provided input (any regular number).
Let us now see how to implement this using the Interpreter design pattern.
using System;
using System.Collections.Generic;
namespace PARTECH_Interpreter
{
internal class Program
{
static void Main(string[] args)
{
var context = new Context(99);
List<IExpression> expressionTree = new List<IExpression>();
expressionTree.Add(new DoubleDigitRomanExpression());
expressionTree.Add(new SingleDigitRomanExpression());
expressionTree.ForEach(x => x.Interpret(context));
Console.WriteLine(context.Output);
Console.ReadLine();
}
public class Context
{
public Context(int input)
{
Input = input;
}
public int Input { get; set; }
public string Output { get; set; }
}
public interface IExpression
{
public void Interpret(Context context);
}
public class SingleDigitRomanExpression : IExpression
{
public void Interpret(Context value)
{
while ((value.Input - 9) >= 0)
{
value.Output += "IX";
value.Input -= 9;
}
while ((value.Input - 5) >= 0)
{
value.Output += "V";
value.Input -= 5;
}
while ((value.Input - 4) >= 0)
{
value.Output += "IV";
value.Input -= 4;
}
while ((value.Input - 3) >= 0)
{
value.Output += "III";
value.Input -= 3;
}
while ((value.Input - 2) >= 0)
{
value.Output += "II";
value.Input -= 2;
}
while ((value.Input - 1) >= 0)
{
value.Output += "I";
value.Input -= 1;
}
}
}
public class DoubleDigitRomanExpression : IExpression
{
public void Interpret(Context value)
{
while ((value.Input - 90) >= 0)
{
value.Output += "XC";
value.Input -= 90;
}
while ((value.Input - 50) >= 0)
{
value.Output += "L";
value.Input -= 50;
}
while ((value.Input - 40) >= 0)
{
value.Output += "XL";
value.Input -= 40;
}
while ((value.Input - 30) >= 0)
{
value.Output += "XXX";
value.Input -= 30;
}
while ((value.Input - 20) >= 0)
{
value.Output += "XX";
value.Input -= 20;
}
while ((value.Input - 10) >= 0)
{
value.Output += "X";
value.Input -= 10;
}
}
}
}
}
In the above example, DoubleDigitRomanExpression and SingleDigitRomanExpression are the Terminal expressions. IExpression is the interface that holds the signature for implementation. Context is the class that has the properties to store input and output data. The main method is the client class here.
The above code holds good and converts the numbers to roman numerals up to 99. Above this number, separate logic needs to be implemented under a new class called DoubleDigitRomanExpression which again implements the IExpression.
Here, the DoubleDigitRomanExpression class holds the logic to convert the number into two digits. The SingleDigitRomanExpression class holds the logic to convert the number into single digits. On executing the above code, the below output gets printed.
Similarly, the interpreter design pattern can be used to achieve the opposite functionality - providing roman numerals as input and converting them to numbers or in any other scenarios where parsing is required. The only downside of the Interpreter design pattern is that not highly efficient.
Conclusion
And that’s what an Interpreter Behavioral design pattern is all about. In this post, we have also seen how to implement it in a real-world scenario. Stay tuned for the next blog on the Design Patterns series.