Обычно используемый объект с конфигурационной зависимостью - PullRequest
2 голосов
/ 03 ноября 2010

У нас есть довольно общий объект в нашем приложении. В этом случае мы будем называть это мячом. Шарики работают нормально, но в некоторых конфигурациях они действуют по-разному. В настоящее время он настроен так:

class Ball
{
    private static readonly bool BallsCanExplode;
    static Ball()
    {
        bool.TryParse(ConfigurationManager.AppSettings["ballsCanExplode"], 
            out BallsCanExplode);
    }
    public Ball(){}
}

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

Самое простое решение - просто отделить шар и конфигурацию:

class Ball
{
    private readonly bool CanExplode;
    public Ball(bool canExplode);
}

Проблема в том, что то, что когда-то было изолированной зависимостью в классе Ball, теперь распространилось на каждый класс, который создает Ball. Если это вводит зависимость, то знание взрывающихся шариков должно быть введено везде.

Та же проблема существует с BallFactory. В то время как каждый класс мог просто идти new Ball(), теперь он должен знать о BallFactory, который нужно вводить везде. Другой вариант - использовать сервисный локатор, который уже встроен в приложение:

class Ball
{
    private readonly bool CanExplode;
    public Ball()
    {
        CanExplode = ServiceLocator.Get<IConfiguration>().Get("ballsCanExplode");
    }
}

Это все еще сохраняет зависимость конфигурации в шаре, но позволяет вводить тестовую конфигурацию. Однако шары используются настолько часто, что кажется излишним находить службу при каждом вызове new Ball().

Каков наилучший способ сохранить этот тестируемый объект так же легко, как создать экземпляр?

Примечание. В приложении есть как структура внедрения зависимостей, так и локатор служб, которые часто используются.

Ответы [ 5 ]

5 голосов
/ 03 ноября 2010

Классы, создающие экземпляры шаров, должны получить BallFactory в качестве зависимости.BallFactory может быть соответствующим образом настроен при запуске приложения, независимо от того, нужно ли производить взрывающиеся или не взрывающиеся шарики.

Не заставляйте BallFactory читать файл конфигурации приложения, чтобы определить, какие типы шариков следует использовать.производить.Это должно быть введено в BallFactory.

Сервисные локаторы являются антишаблоном.Не используйте их.

2 голосов
/ 03 ноября 2010

Как я правильно понимаю, все шары в вашем приложении всегда действуют одинаково. Либо они взрываются, либо нет, что определяется переключателем конфигурации. Что вы можете сделать, это настроить это в вашей структуре DI. В зависимости от структуры проводка в корне приложения может выглядеть следующим образом: *

bool ballsCanExplode =
    bool.Parse(ConfigurationManager.AppSettings["ballsCanExplode"]);

container.Register<Ball>(() => new Ball(ballsCanExplode)); 

Когда вы делаете это, вы можете использовать шаблон Service Locator, чтобы получить новый экземпляр шара, как вы уже привыкли:

ServiceLocator.Get<Ball>();

Но лучше было бы позволить инфраструктуре DI внедрить Ball зависимостей в конструктор другого типа (гораздо проще для тестирования).

2 голосов
/ 03 ноября 2010

Я бы использовал что-то как ваш ServiceLocator для установки статического DefaultBallsCanExplode, тогда, возможно, имел бы перегруженный конструктор, который может принимать bool ballsCanExplode в качестве опции.

Будьте проще!

0 голосов
/ 03 ноября 2010

Примерно так:

internal interface IBallConfigurer
{
    bool CanExplode { get; }
}

internal class BallConfigurer : IBallConfigurer
{
    public bool CanExplode
    {
        get
        {
            bool BallsCanExplode;
            bool.TryParse(ConfigurationManager.AppSettings["ballsCanExplode"],
        out BallsCanExplode);
            return BallsCanExplode;

        }
    }
}

public class Ball
{
    private bool canExplode;

    public Ball()
        :this(new BallConfigurer())
    {

    }

    internal Ball(IBallConfigurer ballConfigurer)
    {
        this.canExplode = ballConfigurer.CanExplode;
    }
}

Таким образом, вы можете сделать внутренности класса шаров видимыми для вашей сборки модульного теста и внедрить пользовательский ballconfigurer.

0 голосов
/ 03 ноября 2010

Я голосую за путь обслуживания конфигурации. Издержки не должны быть очень высокими для типичной реализации локатора службы, и вы можете кэшировать службу конфигурации, если она понадобится вам позже. Более того, используйте инфраструктуру внедрения зависимостей, и вам не нужно будет явно определять местоположение службы.

...