Как правильно выставлять 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);
}
}