Feel Good.

23 ноября 2011

Adapter pattern. Понимание важности.

Перед большинством проектов рано или поздно встает главный вопрос: "Использовать стороннюю компоненту или реализовать свою?", и как это часто бывает, по причине того что использовать уже существующее гораздо проще, чем создавать это заново мы выбираем уже готовое решение.

Предположим, мы решили использовать уже готовую компоненту, тогда остается выбрать какую именно, но если выбор велик, то перед нами возникает проблема выбора, так как у каждой компоненты различные интерфейсы, различные характеристики по памяти/скорости работы, надежность, различная поддержка со стороны поставщика и цена. На изучении каждой у нас просто нет времени...

Итак, пришло время вспомнить о существовании такого шаблона проектирования как "Адаптер" (Adapter pattern). Я не буду здесь рассказывать про этот шаблон проектирования, а просто постараюсь выделить главную идею:

Всегда оборачивайте стороннюю компоненту своим интерфейсом и адаптируйте ее под себя.

Первая причина, почему это стоит делать очевидна - это устранение жестких зависимостей на стороннюю компоненту, а учитывая, что у зависимостей есть один неприятный момент: с ростом проекта они тоже разрастаются и чем дальше тем сложнее от них избавиться, и в конце концов можно стать "заложником" сторонней компоненты. Казалось бы простое правило, но на практике периодически сталкиваешься с обратным, да и сам порой забываешь обернуть, например, какой-нибудь logger, serializer(json, xml, yaml) или DI/IoC-контейнер, в надежде, что этот инструмент ты не сменишь никогда в проекте.

Вторая причина - это адаптация компоненты "под себя". Адаптируете компоненту таким образом, чтобы Вам было удобно ее использовать.

31 августа 2011

Mock для ICloneable типов

Буквально недавно я столкнулся с задачей, суть которой заключалась в том, чтобы создать mock объект реализующий интерфейс ICloneable, например средствами Moq.
В моей ситуации, было все просто: метод Clone должен возвращать объект (которого на самом деле нет), с наперед известными данными (которые не подлежали бы изменениям). Для наглядности лучше привести исходный код.
Пусть это будет следующий интерфейс:

public interface ISome : ICloneable

{

    // некое бизнес-поле

    int Value { get; }

}

И для этого интерфейса, построим mock объект реализующий его.
Итак, тесты вперед, поэтому напишем простой unit-test для метода Clone:

[TestMethod]

public void CloneTest()

{

    var original = MakeMockCloneableSome();

 

    // покажем, что и у клонированого объекта

    // метод Clone работает как надо

    var clone = (ISome)((ISome)((ISome)original.Clone()).Clone()).Clone();

 

    // проверим, что бизнес поле совпадает

    Assert.AreEqual(original.Value, clone.Value);

 

    // проверим, что это 2 отдельных объекта

    Assert.IsFalse(Object.ReferenceEquals(original, clone));

}

В тесте я трижды вызывают метод Clone для того, чтобы продемонстрировать, что и у клонированного объекта метод Clone тоже работает, это важно.
Осталось реализовать метод MakeMockCloneableSome():

ISome MakeMockCloneableSome()

{

    var mock = new Mock<ISome>();

 

    // Здесь привычный нам mock бизнес логики...

    mock

        .Setup(c => c.Value)

        .Returns(123);

 

    // Искомый mock метода ICloneable.Clone()

    mock

        .Setup(c => c.Clone())

        .Returns(MakeMockCloneableSome);

 

    return mock.Object;

}

12 мая 2011

Проверяем входные данные

Все программисты знают, что пришедшие из внешнего источника данные всегда надо проверять. Например в качестве входного параметра для метода (свойства, конструктора), может приходить ссылка, которую нужно проверить на null, число, которое должно быть из определенного диапазона, или определенного вида строка. Несмотря на то, что по этому поводу даже существует методика разработки через Code Contracts, по-прежнему, с высокой вероятностью в проекте можно встретить код проверки на null аналогичный следующему:

class Bar {}

 

class Foo

{

    Bar _bar;

 

    public Foo(Bar bar)

    {

        // bar может быть null

        if (bar == null)

            throw new ArgumentNullException("bar");

 

        _bar = bar;

    }

}