“Патерн Вівторка” #25: Інтерпретер (Interpreter)
|
ІНТЕРПРЕТЕР
Інтерпретер це дизайн патерн, що дозволяє описати граматику певної мови, за допомогою чого можна записати речення на цій мові та інтерпретувати його значення.
Говорячи про наш дещо дивний приклад, мовою буде вантажівка/упаковка/різні речі, реченням буде поточне заповнення вантажівки, а значенням речення буде загальна ціна речей всередині.
Існує два види виразів у мові: такі які можна зрозуміти одразу, такі вирази називаються термінальними (terminal expressions), та такі які вимагають застосування граматичних правил мови, їх називають нетермінальними виразами (nonterminal expressions).
Проводячи паралелі із нашим прикладом, термінальним виразом буде старий телевізор, ноут або ліжко, оскільки ми зараз знаємо ціну на них, а нетермінальни виразом буде упаковка у яку закинули 3 ноутбуки, в такому випадку потрібно буде ще рахувати скільки коштуватиме вміст такої упаковки.
Глянемо на вихідний код прикладу:
Ось вирази які використовуються у нашій мові:
// Abstract expression
public abstract class Goods
{
public abstract int Interpret(CurrentPricesContext context);
}
// Nonterminal expression
public class GoodsPackage : Goods
{
public List<Goods> GoodsInside { get; set; }
public override int Interpret(CurrentPricesContext context)
{
var totalSum = 0;
foreach (var goods in GoodsInside)
{
totalSum += goods.Interpret(context);
}
return totalSum;
}
}
// Terminal expression
internal class TV : Goods
{
public override int Interpret(CurrentPricesContext context)
{
int price = context.GetPrice("TV");
Console.WriteLine("TV: {0}", price);
return price;
}
}
// Here other terminal expressions go (Laptop, Bed)
Як можна побачити GoodsPackage знає про те як його можуть зрозуміти, а саме коли він просумує ціну речей всередині. У нашому прикладу мова дуже вже проста із тільки одним правилом, але у інших мовах усе буде набагато складніше. Для прикладу у якійсь уявній мові пов’язаній із обрахунками такими правилами нетермінальними виразами зможуть бути “+”, “-”, “/”, “*”, “Sqrt”, “Integral”, або ще щось інше. Така мова також буде мати ширший вибір термінальних виразів.
Залишилися ще дві речі які відіграють помітну роль у патерні. Це контекст (context), який відображає глобальну інформацію для процесу інтерпретування. У нашому прикладі це контекст сьогоднішніх цін у певному місті. Ще однією роллю у патерні є клієнт, який відповідає за отримання (створення) речення та виклику методу інтерпретації. Нижче наводиться тільки код клієнту, оскільки код контексту не є дуже важливим.
public void RunInterpreterDemo()
{
// create syntax tree that represents sentence
var truckWithGoods = PrepareTruckWithGoods();
// get latest context
var pricesContext = GetRecentPricesContext();
// invoke Interpret
var totalPriceForGoods = truckWithGoods.Interpret(pricesContext);
Console.WriteLine("Total: {0}", totalPriceForGoods);
}
private CurrentPricesContext GetRecentPricesContext()
{
var pricesContext = new CurrentPricesContext();
pricesContext.SetPrice("Bed", 400);
pricesContext.SetPrice("TV", 100);
pricesContext.SetPrice("Laptop", 500);
return pricesContext;
}
public GoodsPackage PrepareTruckWithGoods()
{
var truck = new GoodsPackage() { GoodsInside = new List<Goods>() };
var bed = new Bed();
var doubleTriplePackedBed = new GoodsPackage() { GoodsInside = new List<Goods>() { new GoodsPackage() { GoodsInside = new List<Goods>() { bed } } } };
truck.GoodsInside.Add(doubleTriplePackedBed);
truck.GoodsInside.Add(new TV());
truck.GoodsInside.Add(new TV());
truck.GoodsInside.Add(new GoodsPackage() { GoodsInside = new List<Goods>() { new Laptop(), new Laptop(), new Laptop() } });
return truck;
}
А ось вивід:
Bed: 400
TV: 100
TV: 100
Laptop: 500
Laptop: 500
Laptop: 500
Total: 2100
І ще одне дуже важливе: Інтерпретер це такий дизайн патерн, який швидше за все вам ніколи не пригодиться у житті. Він має дещо специфічне застосування.