суббота, 11 декабря 2010 г.

Проблема "глубокой" подписки (часть 2)

Коллекции всегда осложняют жизнь.

Определим следующее событие (с аргументами):
public delegate void CollectionChanged(object sender, CollectionChangedEventArgs args);

public class CollectionChangedEventArgs:EventArgs
{
 public CollectionChangedEventArgs(ActionType action, int itemIndex)
 {
  Action = action;
  Index = itemIndex;
 }

 public ActionType Action { get; private set; }
 public int Index { get; private set; }
}

public enum ActionType { Clear, Insert, Remove, Set }

Назначение свойств очевидно. Структура намеренно упрощена и не подразумевает одновременного изменения нескольких членов коллекции (не считая очистки всех).

Определим также следующий обобщенный класс коллекций, которые уведомляют о своих изменениях:
public interface IObservableCollection
{
 event CollectionChanged Changed;
}

public interface IObservableList<T>:IList<T>, IObservableCollection
{}

public class ObservableCollection<T> : Collection<T>, IObservableList<T>
{
 public event CollectionChanged Changed;

 public ObservableCollection()
 {}

 public ObservableCollection(IList<T> list):base(list)
 {}

 protected void OnChanged(CollectionChangedEventArgs args)
 {
  var handler = Changed;
  if (handler != null)
   handler(this, args);
 }

 #region change notification

 protected override void ClearItems()
 {
  base.ClearItems();
  OnChanged(new CollectionChangedEventArgs(ActionType.Clear, -1));
 }

 protected override void InsertItem(int index, T item)
 {
  base.InsertItem(index, item);
  OnChanged(new CollectionChangedEventArgs(ActionType.Insert, index));
 }

 protected override void SetItem(int index, T item)
 {
  base.SetItem(index, item);
  OnChanged(new CollectionChangedEventArgs(ActionType.Set, index));
 }

 protected override void RemoveItem(int index)
 {
  base.RemoveItem(index);
  OnChanged(new CollectionChangedEventArgs(ActionType.Remove, index));
 }

 #endregion
}

Соответственно изменим определение объектной модели в классах Page.cs и Block.cs:
// Page.cs
private IObservableList _items = new ObservableCollection();
public IObservableList Items...

// Block.cs
private IObservableList _items = new ObservableCollection();
public IObservableList Items...

Еще одно упрощение состоит в отсутствии проверки на null для коллекций _items. Я стараюсь избегать использования null, если есть возможность использовать пустой массив или шаблон Null object.

Как мы убедимся что решение работает

Мы напишем много тестов следующего вида:
[TestFixture]
public class ObserverTests
{
 [Test]
 public void WatchTextItems()
 {
  var testee = new Page { Size = new Size(10, 10) };
  var observer = new Mock<IRelocateObserver>();

  testee.Items.Add(new Block());
  testee.Items[0].Items.Add(new TextItem { Rect = new Rectangle(1, 2, 3, 4) });

  Observe(testee, observer.Object.Invalidate);

  // act
  testee.Items[0].Items[0].Rect = new Rectangle(3, 4, 5, 6);

  // verify
  observer.Verify(o => o.Invalidate(new Rectangle(1, 2, 3, 4)));
  observer.Verify(o => o.Invalidate(new Rectangle(3, 4, 5, 6)));
 }
}
Здесь Observe - функция которую мы собираемся тестировать. Вторым аргументом функции является Action, в качестве которого мы передадим мок-объект.

В следующей (последней) статье я рассмотрю проблему еще раз и решение.

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