2017-01-16 3 views
0

Я изучаю, как включить IObservable в мой код. Ниже приведены два разных подхода к простому классу, который выводит самое последнее слово из IObservable<string>. Какой подход является более чистым? Мне не нравится WordPrinterWithCache, потому что он вводит дополнительную переменную состояния (_lastWord), и интересный код теперь разбросан по всему классу. Я предпочитаю WordPrinterWithExtraSubject, потому что интересный код локализуется в конструкторе. Но в основном это кажется более последовательно функциональным и «реактивным»; Я реагирую на комбинацию двух «событий»: (1) испускаемое новое слово и (2) вызов метода PrintMostRecent.Превращение вызова метода в наблюдаемое событие, хорошая идея?

Однако я читал, что использование Субъектов нежелательно, если это не является строго необходимым, и я представляю ненужный Subject<Unit>. По существу, то, что я здесь делаю, генерирует наблюдаемый из вызова метода, поэтому я могу использовать наблюдаемые функции комбинатора в большем количестве мест. Мне нравится идея создания моего кода с использованием наблюдаемых комбинаторов и подписки, а не с использованием деревьев вызова метода «старого стиля». Я не знаю, хорошая ли это идея.

public class WordPrinterWithCache 
{ 
    string _lastWord = string.Empty; 

    public WordPrinterWithCache(IObservable<string> words) 
    { 
     words.Subscribe(w => _lastWord = w); 
    } 

    public void PrintMostRecent() => Console.WriteLine(_lastWord); 
} 

public class WordPrinterWithExtraSubject 
{ 
    Subject<Unit> _printRequest = new Subject<Unit>(); 

    public WordPrinterWithExtraSubject(IObservable<string> words) 
    { 
     _printRequest 
      .WithLatestFrom(words.StartWith(string.Empty), (_, w) => w) 
      .Subscribe(w => Console.WriteLine(w)); 
    } 

    public void PrintMostRecent() => _printRequest.OnNext(Unit.Default); 
} 

Фактический сценарий в моем коде является то, что у меня есть ICommand. Когда пользователь вызывает команду (может быть, нажав кнопку), я хочу принять самое последнее значение определенного наблюдаемого. Например, возможно, ICommand представляет Delete, и я хочу удалить выбранный элемент в списке, который представлен IObservable<Guid>. Ужасно держать кучу переменных кэша «последнего испускаемого значения» для каждого наблюдаемого.

Подход, к которому я склоняюсь, представляет собой реализацию вида ICommand, как то, что вы видите ниже. Это позволяет мне писать код, как deleteCommand.WithLatestFrom(selectedItems,(d,s)=>s).Subscribe(selected=>delete(selected));

public class ObservableCommand : ICommand, IObservable<object> 
{ 
    bool _mostRecentCanExecute = true; 
    Subject<object> _executeRequested = new Subject<object>(); 

    public ObservableCommand(IObservable<bool> canExecute) 
    { 
     canExecute.Subscribe(c => _mostRecentCanExecute = c); 
    } 

    public event EventHandler CanExecuteChanged; // not implemented yet 

    public bool CanExecute(object parameter) => _mostRecentCanExecute; 

    public void Execute(object parameter) => _executeRequested.OnNext(parameter); 

    public IDisposable Subscribe(IObserver<object> observer) => _executeRequested.Subscribe(observer); 
} 

ответ

0

Хотя я не уверен, что я хотел бы использовать термин «Превратить вызов метода в наблюдаемое событие», я был поклонником использования полностью декларативным, функциональным и Rx-код на некоторое время.

Ваше описание реализации ICommand очень близко совпадает с тем, которое я написал пару лет назад, и использовал много раз и очень успешно. Кроме того, это стало основой для шаблона, который я называю «реактивным поведением», который дает множество преимуществ. Из моего блога:

  • Содействует поведение инициативе разработки и модульного тестирования.
  • Продвигает функциональные и потокобезопасные методы программирования.
  • Снижает риск (и если все сделано хорошо, может устранить) побочные эффекты, так как конкретные поведения изолированы одним методом.
  • Останавливает код «rot», поскольку все поведение инкапсулируется в специально названных методах. Хотите нового поведения? Добавьте новый метод. Не хотите больше заниматься конкретным поведением? Просто удалил его. Хотите изменить конкретное поведение? Измените один метод и узнайте, что вы ничего не сломали.
  • Предоставляет сжатые механизмы для объединения нескольких входных данных и повышает асинхронные процессы до состояния первого класса.
  • Снижает необходимость использования классов полезности, поскольку данные могут передаваться по конвейеру как строго типизированные анонимные классы.
  • Предотвращает утечку памяти, так как все действия возвращают одноразовое, которое при удалении удаляет все подписки и распределяет все управляемые ресурсы.

Вы можете прочитать полную статью on my blog.

+0

Я создаю свою собственную реактивную структуру MVVM, чтобы узнать, могу ли я прийти к нирване сверхпростого декларативного кода. Подход к вашему блогу довольно увлекательный. Он отличается от того, что у меня есть несколькими способами. (1) Я выставляю ObservableProperty непосредственно в представление как свойство только для чтения, а затем привязываюсь к свойству Value - меньше кода. (2) Моя команда принимает IObservable , чтобы определить, включена ли она - более декларативная. (3) Использование одного метода для поведения - это действительно крутая идея; мой конструктор стал слишком большим и функциональным, это спагетти. – JustinM

+0

Привет, Джастин, рад, что вы нашли сообщение в блоге информативным. Пара вопросов о ваших комментариях: (1) Осуществляет ли ваш ObservableProperty INotifyPropertyChange? (2) Моя команда - это IObserver , а не IObservable как параметр конструктора и имеющая (внутреннюю и потенциально протекающую) подписку на наблюдаемую. (3) Я склонен помещать объявление поведения в вызов Initialize вместо конструктора, но это был всего лишь пример. Удачи вам в вашей карьере! – ibebbs

+1

О, также я бы рекомендовал написать «библиотеку», а не «фреймворк». Отличный пост по этому вопросу (и разница!) Здесь: http://tomasp.net/blog/2015/library-frameworks/ Просто о чем подумать, пока вы занимаетесь архитектурой ... – ibebbs

2

В качестве общего правила (рекомендации) я настоятельно рекомендую не иметь IObservable<T> в качестве параметра для метода. Очевидное предостережение заключается в том, что этот метод является новым оператором Rx, например. Select, MySpecialBuffer, Debounce и т.д.

Теория здесь заключается в том, что IObservable<T> является механизмом обратного вызова. Это позволяет что-то обращать внимание на другую вещь, о которой она иначе ничего не знает. Однако в этом случае у вас есть что-то, что знает как об IObservable<T> (параметр), так и о другом (WordPrinterWithCache). Так почему же этот дополнительный слой косвенности? Что бы то ни было, то, что нажатие значений на IObservable<T> могло просто вызвать метод экземпляра WordPrinterWithCache.

В этом случае просто вызвать метод на другую вещь

public class WordPrinterWithCache 
{ 
    private string _lastWord = string.Empty; 

    public void SetLastWord(string word) 
    { 
     _lastWord = word; 
    } 

    public void PrintMostRecent() => Console.WriteLine(_lastWord); 
} 

Теперь этот класс начинает выглядеть довольно бессмысленно, но это может быть нормально. Просто хорошо.

Используйте Rx, чтобы помочь вам с расслоением.

Уровни восходящего потока зависят от слоев вниз. Они будут напрямую вызывать методы (команды выпуска) на нижестоящих слоях.

Нижестоящие слои не имеют доступа к верхним слоям. Таким образом, чтобы предоставить им данные, они могут либо возвращать значения из методов, либо подвергать обратные вызовы. Участник WSF Observer, .NET Events и Rx - это способы предоставления обратных вызовов восходящим слоям.

+0

Мой вопрос был Method (int x) {do_y} похож на IObservable , на который вы подписываете (x => {do_y}). rx-функции хороши для сложных условий, таких как b и c, do_something. В моем примере Subject - это способ сделать что-то с помощью тех же функций rx. Обе работы - не уверен, что лучше. – JustinM

+0

My ICommand принимает IObservable для управления включением или отключением команды. Мне это нравится, поскольку, когда я определяю команду, я декларативно указываю, включен ли она или нет. Я думаю, вы рекомендуете подход, когда какой-то код вне команды подписывается на IObservable и устанавливает состояние CanExecute, вызывая метод в команде или может сделать команду IObserver . Оба работают. Мне нужно подумать о вашем расслоенном аргументе; не пытайтесь это сделать. – JustinM

+0

Я думаю, что вы подразумеваете под слоем, это - время, чтобы определить события, когда вы действительно не знаете, кто ваши потребители. если вы знаете, кто ваши потребители, не беспокойтесь о том, чтобы собрать мероприятие, просто чтобы оно прошло вместе с его предполагаемым получателем. то же самое касается использования iobservable; если вы знаете, кто слушает, отправьте сообщение прямо на них. – JustinM

Смежные вопросы