Back to: Design Patterns in C# With Real-Time Examples
Real-Time Examples of the Interpreter Design Pattern in C#
I will discuss the Real-Time Examples of the Interpreter Design Pattern in C# in this article. Please read our previous article discussing the basic concepts of Interpreter Design Patterns in C# with Examples. At the end of this article, you will understand the following Real-time Examples using the Interpreter Design Pattern in C#.
- Evaluate Permissions Based on a Simple Rule-Based Language
- Evaluating Mathematical Expressions
- Roman Numeral Interpreter
- Simple Markup Language
- Query Language for Filtering a Collection of Products
- Text-Based Role-Playing Game (RPG)
Real-Time Example of Interpreter Design Pattern in C#: Evaluate Permissions Based on a Simple Rule-Based Language
Suppose we are developing a system that evaluates permissions based on a simple rule-based language. This can be a feature in a more extensive content management system or application platform where administrators can define custom permission rules. For example, an organization wants to check if an employee can access a particular resource. Rules are defined as simple expressions:
- role = ‘admin’
- department = ‘finance’ AND years_of_service > 5
- role = ‘manager’ OR (department = ‘HR’ AND years_of_service >= 3)
To implement this, we will represent each rule as an expression and evaluate them. Let us see how we can implement the above example using the Interpreter Design Pattern in C#:
using System; using System.Collections.Generic; namespace InterpreterDesignPattern { //Expression: This is the basic unit of our interpreter. public interface IExpression { bool Interpret(Dictionary<string, dynamic> context); } //Terminal Expressions: These represent the terminal expressions in our rule grammar. public class EqualsExpression : IExpression { private string key; private dynamic value; public EqualsExpression(string key, dynamic value) { this.key = key; this.value = value; } public bool Interpret(Dictionary<string, dynamic> context) { return context.ContainsKey(key) && context[key] == value; } } public class GreaterThanExpression : IExpression { private string key; private dynamic value; public GreaterThanExpression(string key, dynamic value) { this.key = key; this.value = value; } public bool Interpret(Dictionary<string, dynamic> context) { return context.ContainsKey(key) && context[key] > value; } } //Non-Terminal Expressions: These combine other expressions. public class AndExpression : IExpression { private IExpression expr1; private IExpression expr2; public AndExpression(IExpression expr1, IExpression expr2) { this.expr1 = expr1; this.expr2 = expr2; } public bool Interpret(Dictionary<string, dynamic> context) { return expr1.Interpret(context) && expr2.Interpret(context); } } public class OrExpression : IExpression { private IExpression expr1; private IExpression expr2; public OrExpression(IExpression expr1, IExpression expr2) { this.expr1 = expr1; this.expr2 = expr2; } public bool Interpret(Dictionary<string, dynamic> context) { return expr1.Interpret(context) || expr2.Interpret(context); } } // Testing the Interpreter Design Pattern // Client Code public class Client { public static void Main() { // Rule: department = 'finance' AND years_of_service > 5 var rule1 = new AndExpression( new EqualsExpression("department", "finance"), new GreaterThanExpression("years_of_service", 5) ); // Sample employee data var employeeData = new Dictionary<string, dynamic> { { "role", "employee" }, { "department", "finance" }, { "years_of_service", 6 } }; // Evaluate the rule bool canAccess = rule1.Interpret(employeeData); Console.WriteLine($"Can Access: {canAccess}"); Console.ReadKey(); } } }
In the real world, the construction of these expressions might be driven by a user interface, configuration file, or another external source. This example showcases the core idea of the Interpreter pattern: breaking down a language (in this case, a rule language) into interpretable expressions. When you run the above code, you will get the following output.
Can Access: True
Real-Time Example of Interpreter Design Pattern in C#: Evaluating Mathematical Expressions
Let’s understand a different real-time example of the Interpreter design pattern in C# using the scenario of evaluating mathematical expressions. We are building a calculator application that allows users to input mathematical expressions, such as “3 + 5 – 2” or “4 * 2 / 2”, and get results. The application should be able to interpret these expressions and produce the correct output. Let us see how we can implement the above example using the Interpreter Design Pattern in C#:
using System; namespace InterpreterDesignPattern { //Expression Interface: The primary unit of our interpreter. public interface IExpression { double Interpret(); } //Terminal Expressions: These handle the basic units of our language, namely numbers. public class NumberExpression : IExpression { private double number; public NumberExpression(double number) { this.number = number; } public double Interpret() { return number; } } //Non-Terminal Expressions: These handle operations involving other expressions. public class AddExpression : IExpression { private IExpression leftExpression; private IExpression rightExpression; public AddExpression(IExpression left, IExpression right) { leftExpression = left; rightExpression = right; } public double Interpret() { return leftExpression.Interpret() + rightExpression.Interpret(); } } public class SubtractExpression : IExpression { // Similar to AddExpression, but subtraction is done private IExpression leftExpression; private IExpression rightExpression; public SubtractExpression(IExpression left, IExpression right) { leftExpression = left; rightExpression = right; } public double Interpret() { return leftExpression.Interpret() - rightExpression.Interpret(); } } public class MultiplyExpression : IExpression { // Similar structure, performing multiplication private IExpression leftExpression; private IExpression rightExpression; public MultiplyExpression(IExpression left, IExpression right) { leftExpression = left; rightExpression = right; } public double Interpret() { return leftExpression.Interpret() * rightExpression.Interpret(); } } public class DivideExpression : IExpression { // Similar structure, performing division and checking if the denominator is not zero // Similar structure, performing multiplication private IExpression leftExpression; private IExpression rightExpression; public DivideExpression(IExpression left, IExpression right) { leftExpression = left; rightExpression = right; } public double Interpret() { return leftExpression.Interpret() / rightExpression.Interpret(); } } //Parser: We will create a simple parser to construct our expressions based on the input. public class Parser { public IExpression Parse(string expression) { // This is a rudimentary parser for the sake of the example. In a real-world scenario, this would be more robust. var tokens = expression.Split(' '); IExpression leftExpression = new NumberExpression(double.Parse(tokens[0])); for (int i = 1; i < tokens.Length; i += 2) { switch (tokens[i]) { case "+": leftExpression = new AddExpression(leftExpression, new NumberExpression(double.Parse(tokens[i + 1]))); break; case "-": leftExpression = new SubtractExpression(leftExpression, new NumberExpression(double.Parse(tokens[i + 1]))); break; case "*": leftExpression = new MultiplyExpression(leftExpression, new NumberExpression(double.Parse(tokens[i + 1]))); break; case "/": leftExpression = new DivideExpression(leftExpression, new NumberExpression(double.Parse(tokens[i + 1]))); break; } } return leftExpression; } } // Testing the Interpreter Design Pattern // Client Code public class Client { public static void Main() { var parser = new Parser(); var expression = parser.Parse("3 + 5 - 2"); Console.WriteLine($"Result: {expression.Interpret()}"); Console.ReadKey(); } } }
In this example, we’ve created a rudimentary arithmetic expression interpreter. In real-world situations, the parser would need to handle more complex scenarios, possibly with parentheses or order of operations. But this showcases the core principles of the Interpreter pattern: representing and interpreting a language. When you run the above code, you will get the following output.
Result: 6
Real-Time Example of Interpreter Design Pattern in C#: Roman Numeral Interpreter
let’s understand another real-time example of the Interpreter design pattern: a “Roman numeral interpreter” that can convert Roman numerals to their decimal counterparts. We want a system where users can input Roman numerals like “IX” or “XLII”, and the system should be able to convert these numerals into decimal numbers, i.e., 9 and 42, respectively. Let us see how we can implement the above example using the Interpreter Design Pattern in C#:
using System; using System.Collections.Generic; namespace InterpreterDesignPattern { //Expression Interface: The primary unit of our interpreter. public abstract class Expression { public abstract string One(); public abstract string Four(); public abstract string Five(); public abstract string Nine(); public abstract int Multiplier(); public void Interpret(Context context) { if (context.Input.Length == 0) return; if (context.Input.StartsWith(Nine())) { context.Output += (9 * Multiplier()); context.Input = context.Input.Substring(2); } else if (context.Input.StartsWith(Four())) { context.Output += (4 * Multiplier()); context.Input = context.Input.Substring(2); } else if (context.Input.StartsWith(Five())) { context.Output += (5 * Multiplier()); context.Input = context.Input.Substring(1); } while (context.Input.StartsWith(One())) { context.Output += (1 * Multiplier()); context.Input = context.Input.Substring(1); } } } public class Context { public string Input { get; set; } public int Output { get; set; } } // Concrete Expressions: These handle specific Roman numerals public class ThousandExpression : Expression { public override string One() => "M"; public override string Four() => " "; public override string Five() => " "; public override string Nine() => " "; public override int Multiplier() => 1000; } public class HundredExpression : Expression { public override string One() => "C"; public override string Four() => "CD"; public override string Five() => "D"; public override string Nine() => "CM"; public override int Multiplier() => 100; } public class TenExpression : Expression { public override string One() => "X"; public override string Four() => "XL"; public override string Five() => "L"; public override string Nine() => "XC"; public override int Multiplier() => 10; } public class OneExpression : Expression { public override string One() => "I"; public override string Four() => "IV"; public override string Five() => "V"; public override string Nine() => "IX"; public override int Multiplier() => 1; } // Testing the Interpreter Design Pattern // Client Code public class Client { public static void Main() { string roman = "MCMXXVIII"; //1928 Context context = new Context { Input = roman }; // Build the 'parse tree' List<Expression> tree = new List<Expression> { new ThousandExpression(), new HundredExpression(), new TenExpression(), new OneExpression() }; // Interpret foreach (Expression exp in tree) { exp.Interpret(context); } Console.WriteLine($"{roman} = {context.Output}"); Console.ReadKey(); } } }
In this example, each numeral class (like ThousandExpression, HundredExpression, etc.) knows how to interpret specific symbols. The main program sets up the context and then uses these interpreter classes in a specific order, ensuring that larger numerals are processed before smaller ones. When you run the above code, you will get the following output.
MCMXXVIII = 1928
Real-Time Example of Interpreter Design Pattern in C#: Simple Markup Language
Imagine you are working on a lightweight documentation system that allows users to define document structures in a simple markup language. This language might define headers, paragraphs, and bold or italic text. Let us see how we can implement the above example using the Interpreter Design Pattern in C#:
using System; using System.Collections.Generic; namespace InterpreterDesignPattern { //Expression Interface: This will be our primary interpretative unit. public interface IExpression { string Interpret(string context); } //Concrete Expressions: These will handle specific patterns in our markup. public class HeaderExpression : IExpression { public string Interpret(string context) { return context.Replace("# ", "<h1>") + "</h1>"; } } public class BoldExpression : IExpression { public string Interpret(string context) { return context.Replace("*", "<b>"); } } public class ItalicExpression : IExpression { public string Interpret(string context) { return context.Replace("_", "<i>"); } } //Interpreter: This class will utilize our expressions to interpret the entire context. public class MarkupInterpreter { private List<IExpression> expressions; public MarkupInterpreter() { expressions = new List<IExpression> { new HeaderExpression(), new BoldExpression(), new ItalicExpression() }; } public string Interpret(string context) { foreach (var expression in expressions) { context = expression.Interpret(context); } return context; } } // Testing the Interpreter Design Pattern // Client Code public class Client { public static void Main() { string markup = @" # This is a header This is a regular paragraph. *This is a bold text.* _This is an italic text._"; var interpreter = new MarkupInterpreter(); var html = interpreter.Interpret(markup); Console.WriteLine(html); Console.ReadKey(); } } }
This is a very simple example. In a real-world scenario, more advanced parsing and error-handling mechanisms would be necessary to ensure the correctness and safety of the interpreted text. This example is to help you understand the core concept of interpreting content using the Interpreter design pattern. When you run the above code, you will get the following output.
Real-Time Example of Interpreter Design Pattern in C#: Query Language for Filtering a Collection of Products
Let’s consider another real-world example: interpreting a domain-specific query language for filtering a collection of products. Suppose we have an e-commerce platform and want to provide users with a way to filter products using a custom query language. Our DSL might be:
brand:Nike; color:blue; minprice:100; maxprice:300
This would filter products of the Nike brand that are blue and priced between 100 and 300. Let us see how we can implement the above example using the Interpreter Design Pattern in C#:
using System; using System.Collections.Generic; using System.Linq; namespace InterpreterDesignPattern { //Product Class: Represents a product in our e-commerce system. public class Product { public string Brand { get; set; } public string Color { get; set; } public decimal Price { get; set; } // ... other properties } //Expression Interface: The primary interface for our interpreter. public interface IExpression { bool Interpret(Product product); } //Concrete Expressions public class BrandExpression : IExpression { private readonly string brand; public BrandExpression(string brand) { this.brand = brand; } public bool Interpret(Product product) { return product.Brand == brand; } } public class ColorExpression : IExpression { private readonly string color; public ColorExpression(string color) { this.color = color; } public bool Interpret(Product product) { return product.Color == color; } } public class PriceRangeExpression : IExpression { private readonly decimal minPrice; private readonly decimal maxPrice; public PriceRangeExpression(decimal min, decimal max) { minPrice = min; maxPrice = max; } public bool Interpret(Product product) { return product.Price >= minPrice && product.Price <= maxPrice; } } //Compound Expression (For combining our criteria) public class AndExpression : IExpression { private readonly List<IExpression> expressions; public AndExpression(params IExpression[] expressions) { this.expressions = new List<IExpression>(expressions); } public bool Interpret(Product product) { return expressions.All(expr => expr.Interpret(product)); } } //Parser: Parser (to convert user's DSL to a compound expression) public class ProductFilterParser { public IExpression Parse(string input) { var criteria = input.Split(';'); var expressions = new List<IExpression>(); foreach (var criterion in criteria) { var parts = criterion.Trim().Split(':'); if (parts[0] == "brand") { expressions.Add(new BrandExpression(parts[1].Trim())); } else if (parts[0] == "color") { expressions.Add(new ColorExpression(parts[1].Trim())); } else if (parts[0] == "minprice") { decimal minPrice = decimal.Parse(parts[1]); expressions.Add(new PriceRangeExpression(minPrice, decimal.MaxValue)); } else if (parts[0] == "maxprice") { decimal maxPrice = decimal.Parse(parts[1]); expressions.Add(new PriceRangeExpression(decimal.MinValue, maxPrice)); } } return new AndExpression(expressions.ToArray()); } } // Testing the Interpreter Design Pattern // Client Code public class Client { public static void Main() { var products = new List<Product> { new Product { Brand = "Nike", Color = "blue", Price = 150M }, new Product { Brand = "Adidas", Color = "red", Price = 90M }, new Product { Brand = "Nike", Color = "black", Price = 250M }, // ... other products }; var query = "brand:Nike; color:blue; minprice:100; maxprice:300"; var parser = new ProductFilterParser(); var expression = parser.Parse(query); var filteredProducts = products.Where(p => expression.Interpret(p)).ToList(); foreach (var product in filteredProducts) { Console.WriteLine($"Found: {product.Brand} {product.Color} priced at ${product.Price}"); } Console.ReadKey(); } } }
This is a simplified approach for demonstration purposes. In a real-world scenario, you’d need more extensive error handling, possibly more complex parsing strategies, and other considerations. When you run the above code, you will get the following output.
Found: Nike blue priced at $150
Real-Time Example of Interpreter Design Pattern in C#: Text-Based Role-Playing Game (RPG)
let’s explore the problem of a text-based role-playing game (RPG) where players give commands in sentences to control their character. In our text-based RPG, players can issue commands like:
- move north 10 steps
- attack dragon with sword
- pick gold
We aim to interpret these sentences and convert them into actions in the game. Let us see how we can implement the above example using the Interpreter Design Pattern in C#:
using System; namespace InterpreterDesignPattern { //Context Class: //This class will encapsulate the current state or context of the interpretation. public class Context { public string Action { get; set; } public string Target { get; set; } public string ToolOrDirection { get; set; } public int Quantity { get; set; } // ... any other context-specific properties. } //Expression Interface: //This will define our basic contract for interpreting player commands. public interface IExpression { void Interpret(Context context); } //Concrete Expressions public class MoveExpression : IExpression { public void Interpret(Context context) { Console.WriteLine($"Character is moving {context.ToolOrDirection} by {context.Quantity} steps."); } } public class AttackExpression : IExpression { public void Interpret(Context context) { Console.WriteLine($"Character is attacking {context.Target} using {context.ToolOrDirection}."); } } public class PickExpression : IExpression { public void Interpret(Context context) { Console.WriteLine($"Character is picking up {context.Target}."); } } //Parser public class CommandInterpreter { public IExpression Parse(string command, Context context) { var words = command.Split(' '); if (command.StartsWith("move")) { context.Action = "move"; context.ToolOrDirection = words[1]; context.Quantity = Convert.ToInt32(words[2]); return new MoveExpression(); } else if (command.StartsWith("attack")) { context.Action = "attack"; context.Target = words[1]; context.ToolOrDirection = words[3]; return new AttackExpression(); } else if (command.StartsWith("pick")) { context.Action = "pick"; context.Target = words[1]; return new PickExpression(); } throw new ArgumentException("Invalid command"); } } // Testing the Interpreter Design Pattern // Client Code public class Client { public static void Main() { var commands = new[] { "move north 10 steps", "attack dragon with sword", "pick gold" }; var interpreter = new CommandInterpreter(); foreach (var command in commands) { var context = new Context(); var expression = interpreter.Parse(command, context); expression.Interpret(context); } Console.ReadKey(); } } }
This is a simple example, but it illustrates the principle. In a real-world RPG, you’d likely have more complex commands, richer context, and more sophisticated parsing logic. The Interpreter Design Pattern would help keep your parsing logic structured and maintainable. When you run the above code, you will get the following output.
Real-Time Examples of Interpreter Design Patterns:
The Interpreter design pattern primarily concerns representing and interpreting a language or grammar. Let’s go through some real-world scenarios where you might encounter the pattern, even if it’s not always called “Interpreter”:
- Regular Expressions: Regular expressions are a perfect example of the Interpreter pattern. A regex defines a specific pattern in a string. When you evaluate a regex against a string, you interpret that string against the pattern’s grammar. In C#, the System.Text.RegularExpressions.Regex class is a typical representation of this pattern.
- Expression Trees in LINQ: LINQ (Language Integrated Query) in C# uses expression trees to represent lambda expressions as data. These expression trees can be traversed and interpreted. The System.Linq.Expressions.Expression class represents an expression tree node.
- Configuration Parsers: Many applications need to interpret configuration files. These files have their own syntax and rules. Consider a simple configuration that dictates the behavior of a module. A custom parser could interpret settings like “logging=on” or “mode=verbose”.
- Custom Scripting Engines: Some applications allow users to script behaviors using custom scripting languages. These scripts then need to be parsed and executed by the application. A game might allow modders to script custom behaviors for characters using a simple scripting language. The game engine would interpret this language.
- Calculators: Simple calculators that evaluate mathematical expressions can be seen as using the Interpreter pattern. A calculator that takes an input like “5 + 3 * 2” and produces an output of 11.
- Business Rule Engines: Some systems allow users to define business rules in a domain-specific language. These rules are then interpreted to make business decisions. An insurance application might allow agents to define rules like “if age > 50 and smoker = true, then premium = high”.
Remember, the primary goal of the Interpreter pattern is to evaluate sentences or expressions based on a specific grammar. Any scenario where you see this kind of behavior can potentially be an application of the Interpreter pattern.
When to use the Interpreter Design Pattern in C#?
The Interpreter design pattern provides a way to evaluate language grammar or expressions, especially for domain-specific languages. While the pattern offers a clear and structured approach to solving specific problems, it isn’t always the most optimal or efficient solution. Here are some scenarios where the Interpreter pattern might be beneficial:
- Domain-Specific Languages (DSL): If you’re building a DSL for a specific problem domain (e.g., a configuration language, a query language, etc.), the Interpreter pattern can be a natural fit. DSLs are specialized languages with a narrow focus, and the pattern can help parse and evaluate this language.
- Grammar Representations: If simple grammar requires parsing and execution, the Interpreter pattern can be useful. The pattern works best with relatively simple grammars, as it can become unwieldy and complex for more complex grammars.
- Configurable Rule Engines: In scenarios where you need a configurable rule engine, where rules can be defined and changed dynamically, the Interpreter pattern can be a good choice. For example, defining business rules that can be parsed and evaluated on the fly.
- Expression Evaluation: When you need to evaluate mathematical or logical expressions, especially if they can be dynamically constructed, the Interpreter can be helpful.
However, there are also some points of caution:
- Complex Grammars: For more complex grammars or languages, tools like parser generators (e.g., ANTLR, Bison) or even hand-crafted parsers might be more appropriate than the Interpreter pattern.
- Performance: The Interpreter pattern isn’t always the most performance-optimized solution. Iterating over expressions and recursively evaluating them can be slower than other approaches, especially for large input.
- Maintenance: Maintaining the interpreter can become challenging as the grammar or rules become complex. Adding or modifying new rules may require changes in multiple places, making the system brittle.
So, while the Interpreter pattern offers a structured way to deal with certain problems, it’s essential to consider the nature and requirements of your problem before choosing it. If your situation aligns with the scenarios where the Interpreter pattern shines, it can be a valuable tool in your design arsenal. Otherwise, other approaches might be more appropriate.
Advantages and Disadvantages of Interpreter Design Pattern in C#
The Interpreter Design Pattern provides a standardized way to evaluate sentences in a language or expressions based on a specific grammar. Let’s discuss its advantages and disadvantages:
Advantages of Interpreter Design Pattern in C#:
- Separation of Concerns: The Interpreter pattern separates the interpretation and business logic. This makes the code more modular and easier to maintain.
- Flexibility: Encapsulating grammar rules as separate interpreter objects makes adding or modifying existing rules easy. Each rule can be handled by its own interpreter class.
- Extensibility: The design pattern is extensible, making it possible to introduce new ways to interpret expressions without altering existing code. This complies with the Open/Closed principle.
- Standardized Solution: For problems that involve parsing and interpreting domain-specific languages or grammar, the Interpreter pattern provides a structured and standardized solution.
- Clarity: The pattern can represent the grammar, benefitting understanding and debugging.
Disadvantages of Interpreter Design Pattern in C#:
- Performance Issues: Recursive evaluation and interpretation can be less efficient than other approaches, especially for larger input or complex grammar.
- Complexity: The Interpreter pattern can become unwieldy and hard to manage for more complex languages or grammars. Tools like parser generators or compilers might be more appropriate in such cases.
- Maintenance Overhead: As the number of grammar rules increases, maintaining the interpreters can become challenging. Small changes in the grammar can sometimes require modifications across multiple interpreter classes.
- Limited to Specific Problems: The Interpreter pattern is not a universal solution. It’s more suited for problems where you have a well-defined grammar or language that needs parsing and evaluation. It may not be the right choice for general-purpose programming.
- Risk of Overhead: There might be overhead regarding the number of classes introduced, especially if each grammar rule or expression requires its own class.
- Error Handling: Handling errors and providing meaningful feedback can be a challenge. The pattern doesn’t inherently provide mechanisms for robust error reporting or recovery.
So, in conclusion, the Interpreter pattern can be a powerful tool when used in the right context, especially when dealing with domain-specific languages or simple grammar. However, its applicability and efficiency diminish as the complexity of the language or the grammar grows. Before adopting the Interpreter pattern, it’s essential to consider the nature of the problem at hand and assess whether the pattern’s advantages outweigh its disadvantages for that specific problem.
In the next article, I will discuss the Mediator Design Pattern in C# with Examples. Here, in this article, I try to explain Real-Time Examples of Interpreter Design Patterns in C#. I hope you enjoy this Real-Time Example of the Interpreter Design Pattern using the C# article.