Как правильно выставлять API, обрабатывать зависимости DLL и придерживаться SOLID?

Каков наилучший подход для предоставления API моей распространяемой библиотеки DLL и обработки ее зависимостей, учитывая, что эти зависимости должны обрабатываться не клиентами, а самой библиотекой DLL, в то же время в соответствии с SOLID и другими хорошими практиками (быть тестируемыми и т. Д.)?

Единственный способ, которым я мог себе представить, - это представить конструктор без параметров с помощью Injection от Poor Man со статической фабрикой, например:

public class MyService
{
    public MyService()
        : this(DependencyFactory.CreateObjectA(), DependencyFactory.CreateObjectB())
    {
    }

    internal MyService(IObjectA objectA, IObjectB objectB)
    {
    }
}

internal static class DependencyFactory
{
    internal static IObjectA CreateObjectA()
    {
        return new ObjectA();
    }

    internal static IObjectB CreateObjectB()
    {
        return new ObjectB();
    }
}

Это правильный путь?

2 ответа

Решение

Я предлагаю вам оставить один публичный конструктор для ваших компонентов, например так:

public class MyService
{
    public MyService(IObjectA objectA, IObjectB objectB)
    {
    }
}

А затем создайте фабрику по умолчанию, которую клиенты вашей библиотеки могут использовать для удобства, например так:

public static class MyConvenientFactory
{
    public static MyService CreateDefaultMyService()
    {
        return new MyService(new ObjectA(), new ObjectB());
    }
}

И клиенты будут создавать ваши компоненты так:

var service = MyConvenientFactory.CreateDefaultMyService();

Или более продвинутый клиент сделает что-то вроде этого:

var service =
    new MyService(
        new CachingDecoratorForA(
            new ObjectA()),
        new LoggingDecoratorForB(
            new PerformanceRecorderForB(
                new ObjectB())));

куда CachingDecoratorForA, LoggingDecoratorForB, а также PerformanceRecorderForB являются декораторами, которые создаются вами как поставщиком библиотеки или самим клиентом.

Это позволяет клиенту настраивать ваш компонент, составляя по-другому. Это одно из преимуществ применения принципов SOLID. См. Эту статью для некоторого обсуждения о составе объекта и SOLID.

Если по какой-либо причине вы не хотите, чтобы такой продвинутый клиент настраивал ваш компонент с помощью композиции, измените модификатор доступа конструктора на internal как это:

public class MyService
{
    internal MyService(IObjectA objectA, IObjectB objectB)
    {
    }
}

К сведению, благодаря отличному ответу Якуба Массада и статье о беглых компоновщиках, я смог реализовать компоновщик с использованием шаблона Builder и шаблона Decorator с использованием свободно распространяемого API. Вот как это использовать:

var myService = MyServiceBuilder
    .Create()
    .WithCachingForA()
    .WithLoggingForB()
    .WithPerformanceRecorderForB()
    .Build();

Реализация:

public class MyServiceBuilder
{
    private IObjectA ObjectA;
    private IObjectB ObjectB;

    private MyServiceBuilder(IObjectA objectA, IObjectB objectB)
    {
        ObjectA = objectA;
        ObjectB = objectB;
    }

    public static MyServiceBuilder Create()
    {
        return new MyServiceBuilder(new ObjectA(), new ObjectB());
    }

    public MyServiceBuilder WithCachingForA()
    {
        ObjectA = new CachingDecoratorForA(ObjectA);
        return this;
    }

    public MyServiceBuilder WithLoggingForB()
    {
        ObjectB = new LoggingDecoratorForB(ObjectB);
        return this;
    }

    public MyServiceBuilder WithPerformanceRecorderForB()
    {
        ObjectB = new PerformanceRecorderForB(ObjectB);
        return this;
    }

    public MyService Build()
    {
        return new MyService(ObjectA, ObjectB);
    }
}
Другие вопросы по тегам