Опыт работы с беглыми интерфейсами? Мне нужно ваше мнение!
Извините за этот длинный вопрос, он помечен вики, потому что я прошу что-то, на что не может быть очень конкретного ответа. Если он закрыт, пусть будет так.
Мой главный вопрос заключается в следующем:
Как бы вы написали свободный интерфейс, который не полностью определен в базовых классах, чтобы программы, использующие свободные интерфейсы, могли привязывать новые слова в существующей структуре и при этом поддерживать направляющий интерфейс, чтобы после точки интеллигентность только перечисляет ключевые слова, которые на самом деле применяются в данный момент.
Я на третьей итерации переписывания контейнера IoC. Вторая итерация должна была повысить производительность, а третья - решить некоторые проблемы расширяемости и разделения.
По сути, проблема с расширяемостью заключается в том, что ее нет. Недавно я хотел использовать службу, срок службы которой истек, и после ее истечения разрешить новую копию. Например, читайте файл конфигурации каждую минуту, но не чаще. Это не было поддержано моим текущим IoC-решением, но единственный способ добавить его - пойти в библиотеку базовых классов и добавить туда поддержку. Для меня это означает, что мне не удалось создать расширяемую библиотеку классов. Честно говоря, я не собирался внедрять в него расширяемость, но тогда я не до конца осознавал, насколько больно было бы входить и добавлять что-то подобное позже.
Я смотрю на свой свободный интерфейс для настройки, и, поскольку я хочу встроить в интерфейс полную расширяемость (или избавиться от него, чего я не хочу делать), мне нужно действовать по-другому.
Поэтому мне нужно ваше мнение. У меня очень мало опыта в использовании беглых интерфейсов, но я видел довольно много кода, который их использует, и поэтому есть одно очевидное преимущество прямо из коробки:
- Код, который использует свободные интерфейсы, обычно очень легко читается
Другими словами, это:
ServiceContainer.Register<ISomeService>()
.From.ConcreteType<SomeService>()
.For.Policy("DEBUG")
.With.Scope.Container()
.And.With.Parameters
.Add<String>("connectionString", "Provider=....")
.Add<Boolean>("optimizeSql", true);
легче читать, чем это:
ServiceContainer.Register(typeof(ISomeService), typeof(SomeService),
"DEBUG", ServiceScope.Container, new Object[] { "Provider=...", true });
Таким образом, читаемость является одной из проблем.
Тем не менее, руководство программиста - это другое, что не легко понять, читая существующий код, в Интернете или в редакторе.
В основном, когда я набираю это:
ServiceContainer.Register<ISomeService>()
.From.|
^-cursor here
и затем intellisense покажет доступные типы разрешения. После того, как я выбрал это, и напишите:
ServiceContainer.Register<ISomeService>()
.From.ConcreteType<SomeService>()
.For.|
тогда я получаю доступ к вещам только после ключевого слова "For", например, "Политика" и тому подобное.
Тем не менее, это большая проблема? Были ли у вас свободно используемые интерфейсы? Очевидное препятствие для определения интерфейса состоит в том, чтобы создать класс или интерфейс со всеми ключевыми словами и всем таким, чтобы значение intellisense после каждой запятой содержало все, но это также могло бы привести к тому, что это допустимо (например, компилирует) код:
ServiceContainer.Register<ISomeService>()
.From.ConcreteType<SomeService>()
.From.Delegate(() => new SomeService())
.From.With.For.Policy("Test");
поэтому я хотел бы структурировать текущие интерфейсы так, чтобы после того, как вы указали, как разрешить службу, вы не сможете сделать это снова.
- Другими словами, беглые интерфейсы очень просты в использовании, поскольку они направляют вас к тому, что вы можете сделать.
Но это типично? Поскольку я хочу иметь возможность добавлять несколько таких ключевых слов, например, тип решателя (ConcreteType, Delegate и т. Д.), Тип области действия (Factory, Container, Singleton, Cache и т. Д.) В качестве методов расширения, чтобы программы могут определять свои собственные способы сделать это без необходимости входить и изменять базовые классы, это означает, что мне нужно будет предоставить интерфейсы для всех промежуточных остановок и позволить фактическим важным ключевым словам быть. Реализация для этих ключевых слов затем должна выбрать один промежуточный-стоп-интерфейс для возврата, в зависимости от ситуации.
Похоже, мне нужно определить интерфейс для:
- xyz.From.
xyz.From.<Resolver here>.
<Resolver here>.With.
<Resolver here>.For.
и т.д., но это выглядит фрагментированным для меня.
Может ли кто-нибудь, имеющий опыт работы с беглыми интерфейсами, вернуться и прочитать мой цитируемый ответ в верхней части страницы и попытаться дать мне короткий ответ?
2 ответа
Две вещи: методы расширения и вложенные замыкания. Они должны охватывать все ваши потребности в расширяемости и ясности.
Если вам интересно, вот пара советов из моего опыта создания Fluent NHibernate.
Метод цепочки должен быть сведен к минимуму. Помимо прочего, это ведет к тупику и неопределенному концу цепочки вызовов. Предпочитают вложенные замыкания.
Например, тупик:
Database
.ConnectionString
.User("name")
.Password("xxx")
.Timeout(100) // not possible
Вы не можете вернуться к Database
цепь, как только вы вошли в ConnectionString
цепочка, потому что нет способа создать резервную копию со всеми методами, связанными со строкой соединения, возвращающими экземпляр ConnectionString
,
Вы можете переписать его с помощью метода определенного конца, но они безобразны.
Database
.ConnectionString
.User("name")
.Pass("xxx")
.Done()
.Timeout(100)
Где в этом случае Done
вернет Database
экземпляр, возвращающий вас в первичную цепочку. Опять безобразно.
Как предполагается, предпочитайте вложенные замыкания.
Database
.ConnectionString(cs =>
cs.User("name");
.Pass("xxx"))
.Timeout(100);
Это в значительной степени покрывает ваши интеллигентские проблемы, так как замыкания довольно замкнуты. Ваш объект верхнего уровня будет содержать только методы, которые принимают замыкания, а эти замыкания содержат только методы, специфичные для этой операции. Расширяемость здесь также проста, потому что вы можете добавлять методы расширения только к тем типам, которые представлены внутри замыканий.
Вы также должны знать, чтобы не пытаться сделать ваш беглый интерфейс читаемым как на английском. UseThis.And.Do.That.With.This.BecauseOf.That
цепочки только усложняют ваш интерфейс, когда глаголов будет достаточно.
Database
.Using.Driver<DatabaseDriver>()
.And.Using.Dialect<SQL>()
.If.IsTrue(someBool)
Против:
Database
.Driver<DatabaseDriver>()
.Dialect<SQL>()
.If(someBool)
Обнаруживаемость в intellisense уменьшается, потому что люди склонны искать глагол и не могут его найти. Примером этого от FNH будет WithTableName
метод, где люди склонны искать таблицу слов и не находят ее, потому что метод начинается с.
Ваш интерфейс также становится все труднее использовать для не носителей английского языка. В то время как большинство не носителей языка будут знать технические термины для того, что они ищут, дополнительные слова могут быть им не понятны.
Основываясь на ответе, предоставленном James Gregory, я создал новый прототип плавного интерфейса для моего контейнера IoC, и на этом я остановился на синтаксисе.
Это исправляет мои текущие проблемы:
- Расширяемость, я могу добавлять новые типы разрешения, новые типы областей и т. Д. С помощью методов расширения
- Простой и понятный интерфейс, не нужно дублировать ключевые слова, которые приводят к одинаковому суффиксу пути
- Гораздо меньше кода по сравнению с моей 1-й и 2-й итерациями
Весь код компилируется в моей песочнице, так что это весь допустимый синтаксис, ничего не выдумано, за исключением того, что методы, конечно, ничего не делают в данный момент.
Одна вещь, которую я решил не исправлять, это руководящая часть беглого интерфейса, которая ограничивает ваш выбор при перемещении по интерфейсу. Таким образом, совершенно правильно написать это:
IoC.Register<ILogger>()
.From(f => f.ConcreteType<TestLogger>())
.From(f => f.ConcreteType<AnotherLogger>()); // note, two From-clauses
Предположительно мне придется выбрать, будет ли выброшено исключение (объект разрешения уже установлен) или победит последний.
Пожалуйста, оставьте комментарии.
Вот код:
using System;
namespace IoC3rdIteration
{
public class Program
{
static void Main()
{
// Concrete type
IoC.Register<ILogger>()
.From(f => f.ConcreteType<TestLogger>());
// Concrete type with parameters
IoC.Register<ILogger>()
.From(f => f.ConcreteType<DatabaseLogger>(ct => ct
.Parameter<String>("connectionString", "Provider=...")
.Parameter<Boolean>("cacheSql", true)));
// Policy
IoC.Register<ILogger>()
.From(f => f.ConcreteType<TestLogger>())
.Policy("DEBUG");
// Policy as default policy
IoC.Register<ILogger>()
.From(f => f.ConcreteType<TestLogger>())
.Policy("RELEASE", p => p.DefaultPolicy());
// Delegate
IoC.Register<ILogger>()
.From(f => f.Delegate(() => new TestLogger()));
// Activator
IoC.Register<ILogger>()
.From(f => f.Activator("IoC3rdIteration.TestService"));
// Instance
IoC.Register<ILogger>()
.From(f => f.Instance(new TestLogger()));
// WCF-wrapper
IoC.Register<ILogger>()
.From(f => f.WCF());
// Sinkhole service
IoC.Register<ILogger>()
.From(f => f.Sinkhole());
// Factory
IoC.Register<IServiceFactory<ILogger>>()
.From(f => f.ConcreteType<LoggerFactory>());
IoC.Register<ILogger>()
.From(f => f.Factory());
// Chaining
IoC.Register<IDebugLogger>()
.From(f => f.ConcreteType<DatabaseLogger>());
IoC.Register<ILogger>()
.From(f => f.ChainTo<IDebugLogger>());
// now "inherits" concrete type
// Generic service
IoC.Register(typeof(IGenericService<>))
.From(f => f.ConcreteType(typeof(GenericService<>)));
// Multicast
IoC.Register<ILogger>()
.From(f => f.Multicast(
r1 => r1.From(f1 => f1.ConcreteType<TestLogger>()),
r2 => r2.From(f2 => f2.Delegate(() => new TestLogger())),
r3 => r3.From(f3 => f3.Instance(new DebugLogger()))));
// Factory-scope
IoC.Register<ILogger>()
.From(f => f.ConcreteType<TestLogger>())
.Scope(s => s.Factory());
// Thread-scope
IoC.Register<ILogger>()
.From(f => f.ConcreteType<TestLogger>())
.Scope(s => s.Thread());
// Session-scope (ASP.NET)
IoC.Register<ILogger>()
.From(f => f.ConcreteType<TestLogger>())
.Scope(s => s.Session());
// Request-scope (ASP.NET)
IoC.Register<ILogger>()
.From(f => f.ConcreteType<TestLogger>())
.Scope(s => s.Request());
// Singleton-scope
IoC.Register<ILogger>()
.From(f => f.ConcreteType<TestLogger>())
.Scope(s => s.Singleton());
// Singleton-scope with lifetime
IoC.Register<ILogger>()
.From(f => f.ConcreteType<TestLogger>())
.Scope(s => s.Singleton(si => si.LifeTime(10000)));
// Container-scope
IoC.Register<ILogger>()
.From(f => f.ConcreteType<TestLogger>())
.Scope(s => s.Container());
// Container-scope with lifetime
IoC.Register<ILogger>()
.From(f => f.ConcreteType<TestLogger>())
.Scope(s => s.Container(c => c.LifeTime(10000)));
// Pooled-scope
IoC.Register<ILogger>()
.From(f => f.ConcreteType<TestLogger>())
.Scope(s => s.Pool(p => p
.Minimum(1) // always one instance in pool
.Typical(5) // reduce down to 5 if over 5
.Maximum(10) // exception if >10 in pool
.AutoCleanup() // remove on background thread >5
.Timeout(10000))); // >5 timeout before removal
}
}
}