понедельник, 11 января 2010 г.

Делегаты и события

На лекциях я обычно привожу пример с методом получающим массив и обрабатывающим его элементы при помощи делегата, являющегося вторым параметром данного метода.

Сегодня я покажу "обратный" пример иллюстрирующий позднее связывание. В одной из своих статей Джоэль Спольски рассматривал интересный пример делегатов. Мне он понравился и мы посмотрим его более расширенную версию.

Итак у нас с вам стоит задача разработать метод шеф-повар, который получает продукт и "рецепт" и на выходе вроде как мы должны получить готовое блюдо.
Начнем с описания ингредиента участвующего в приготовлении:
    abstract class Ingredient
    {
    }


* This source code was highlighted with Source Code Highlighter.

Т.е. все реальные ингредиенты будут потоками нашего класса Ingredient. Например, мясо может быть описано как нибудь так:
    class Meat : Ingredient
    {
      public MeatState State;
      public MeatTaste Taste;
      public float Temperature;
    }


* This source code was highlighted with Source Code Highlighter.

Как же будет выглядеть метод позволяющий приготовить ингридиент в соответвии с некоторым рецептом? Первый параметр у о будет соответственно Ingredient, а в качестве второго массив делегатов (я специально не использую List, для упрощения написания примера), принимающих все тот же Ingredient, и возвращающих как не странно его-же.
    static Ingredient Cook(Ingredient p_ingredient, Func<Ingredient, Ingredient>[] p_recipe)
    {
      Ingredient result = p_ingredient;
      for (int i = 0; i < p_recipe.Length; i++)
      {
        result = p_recipe[i](result);
      }
      return result;
    }


* This source code was highlighted with Source Code Highlighter.

Теперь давайте попробуем что нибудь приготовить:
    static void Main(string[] args)
    {
      // Мясо из холодильника
      Meat meat = new Meat { State = MeatState.Сырое, Taste = MeatTaste.Пресное, Temperature = -4 };
      // Операции
      //Func<Ingredient, Ingredient> defrost =
      // Рецепт
      Func<Ingredient, Ingredient>[] recipe =
      {
        // Разморозить
        ing => { Meat m = (Meat)ing; m.Temperature = 20; return m;},
        // Отбить
        ing => { Meat m = (Meat)ing; m.State = MeatState.Отбитое; return m;},
        // Посолить
        ing => { Meat m = (Meat)ing; m.Taste = MeatTaste.Посоленное; return m;},
        // пожарить
        ing => { Meat m = (Meat)ing; m.State = MeatState.Жаренное; m.Temperature = 95; return m;},
        // Поперчить
        ing => { Meat m = (Meat)ing; m.Taste = MeatTaste.Вкусное; return m;}
      };
      // Запускаем готовку
      Console.WriteLine("Мясо до готовки {0} {1}, t={2}", meat.State, meat.Taste, meat.Temperature);
      Meat result = (Meat)Cook(meat, recipe);
      Console.WriteLine("Мясо до готовки {0} {1}, t={2}", meat.State, meat.Taste, meat.Temperature);
      Console.ReadKey();
    }


* This source code was highlighted with Source Code Highlighter.

Запустив приложение, увидим:



Как видим, наш метод который ничего не знал про мясо и как его готовить получив ингредиент и рецепт приготовил вкусное мясо.
На сегодня наверно, все. Есть еще пара идей по демонстрации позднего связывания на делегатах, но это наверно в другой раз.

2 комментария:

  1. Классный пример, здорово :D

    ОтветитьУдалить
  2. :)
    Что то я совсем вчера был не внимателен, столько опечаток... Поправил все, кроме опечатки в листинге, где после готовки говорится "мясо до готовки". Но лень переделывать картинку.

    ОтветитьУдалить