31 травня 2011 р.

"Патерн Вівторка" #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


І ще одне дуже важливе: Інтерпретер це такий дизайн патерн, який швидше за все вам ніколи не пригодиться у житті. Він має дещо специфічне застосування.


Моя табличка Патернів
Developer's RoadMap To Success

Немає коментарів:

Дописати коментар