Всемогущий, Google, найди мне чего-нибудь!

вторник, 7 декабря 2010 г.

Монадический синтаксис и Методы-расширения

Приквел
Давным-давно Начиная с первого дня работы, приходилось переписывать, оптимизировать и рефакторить много кода, так как до моего прихода разработка велась только на .NET 2.0. Каждый разработчик писал код для себя (без комментариев, без всякой структуры, никаких стандартов названия классов, переменных и пр.), думая что никто никогда его читать и разбирать не будет. Естественно, получалось так, что много классов и методов повторялись. Как только увидел несколько тысяч строк кода (это только процентов 5 из того, что пришлось оптимизировать) без комментариев и без всяких отступов, то кровь застыла в жилах и мурашки по коже побежали.
Но через какую-то неделю (не без моего настойчивого требования) было принято решение переходить на .NET 3.5. И тут началось раздолье! Однако, сложилось такое ощущение, что только меня это и радовало. Все, как писали на 2.0, так и писали...


История
Параллельно читая знаменитый всему айтишному миру Habr, наткнулся на статью Дмитрия про реализацию "Монадического синтаксиса" из фукнционального программирования на методах-раширениях. Стало очень интересно про сии цепочные методы. Прочитал всю статью взахлеб! Взял на вооружение все методы, но чаще всего пользуюсь только четырьмя. Хочу ими с вами поделиться (комментарии кода были опущены).
  1. public static TInput If<TInput>(this TInput source, Func<TInput, bool> evaluator) 
    where TInput : class
     {
      if (source == null)
      {
       return null;
      }
    
      return evaluator(source) ? source : null;
     }
    
  2. public static TInput Do<TInput>(this TInput source, Action<TInput> action)
    where TInput : class
    {
     if (source == null)
     {
      return null;
     }
    
     action(source);
    
     return source;
    }
    
  3. public static TResult With<TInput, TResult>(this TInput source, Func<TInput, TResult> evaluator)
    where TInput : class
    where TResult : class
    {
     return source == null ? null : evaluator(source);
    }
    
  4. public static TResult Return<TInput, TResult>(this TInput source, Func<TInput, TResult> evaluator, TResult defaultValue)
    where TInput : class
    {
     return source == null ? defaultValue : evaluator(source);
    }
    

Сиквел ака "А теперь как сие счастье можно использовать?"
  1. Самый простой пример: предположим, что у вас в переменной хранится путь к файлу, но неизвестно, существует ли он. Используя методы, указанные выше, можно написать следующим образом:
    file.If(File.Exists).Do(File.Delete);
    
  2. Далее данные методы можно использовать при формировании sql-запроса: предположим, что у вас есть много полей, которые отвечают за определенные поля в базе данных. Например, таблица пользователей (упрощенный вариант) состоит из 3 полей (ID, NAME, AGE). На форме у вас существует 3 поля: подстрока имени и две границы возраста. Теперь напишем формирование запроса, используя наши методы-расширения:
    StringBuilder query = new StringBuilder("select * from USERS where 1=1");
     this.txtName.With(box => box.Text)
                 .If(name => !string.IsNullOrEmpty(name))
                 .Do(name => query.AppendFormat(" and NAME like '%{0}%'", name));
    
     this.txtMinAge.With(box => box.Text)
                 .If(minAge => !string.IsNullOrEmpty(minAge))
                 .Do(minAge => query.AppendFormat(" and AGE >= {0}", minAge));
    
     this.txtMaxAge.With(box => box.Text)
                 .If(maxAge=> !string.IsNullOrEmpty(maxAge))
                 .Do(maxAge=> query.AppendFormat(" and AGE <= {0}", maxAge));
    
     query.Append(" order by AGE desc");
    
  3. Также можно определять уровень доступа текущего пользователя (его роль). Если он анонимный, то выдавать ему минимальный уровень. Например так:
    return this.User.Return(user => user.AccessLevel, "0");
    или
    return this.User.Return(user => user.Role, Roles.Anonymous);
    
  4. Я использую эти методы для вызова событий, так как данные методы содержат внутренню проверку на null:
    this.OnRequestConfirmed.Do(ev => ev());
    

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

P.S. Ссылка на оригинальную статью: Паттерны методов расширения

Комментариев нет: