体验流畅的界面?我需要你的意见!

时间:2009-12-27 17:45:07

标签: fluent-nhibernate extension-methods extensibility

对于这个长期的问题感到抱歉,它被标记为维基,因为我要求的东西可能没有一个非常具体的答案。如果它关闭了,那就这样吧。

我的主要问题是:

  

如何编写一个未在基类中完全定义的流畅接口,以便使用流畅接口的程序可以处理现有结构中的新单词,并且仍然保持引导接口以便在点之后,intellisense仅列出此时实际应用的关键字。


我正在重写我的IoC容器的第三次迭代。第二次迭代是为了提高性能,第三次迭代将解决一些可扩展性问题和分离问题。

基本上,可扩展性的问题在于没有。我最近想要使用具有生命周期的服务,并在生命周期结束后,解决一个新的副本。例如,每分钟读取一个配置文件,但不是更频繁。我目前的IoC解决方案不支持这种方法,但添加它的唯一方法是进入基类库并在那里添加对它的支持。这对我来说意味着我无法构建可扩展的类库。平心而论,我并不打算在其中构建可扩展性,但后来我并没有充分意识到在以后添加类似内容会有多大的痛苦。

我正在查看我的流畅的配置界面,因为我想在界面中构建完全可扩展性(或者摆脱它,我不愿意这样做)我需要以不同的方式做事。 / p>

因此,我需要你的意见。我实际上使用流畅的接口的经验很少,但我已经看到了很多使用它们的代码,因此开箱即用有一个明显的好处:

  • 使用流畅界面的代码通常很容易阅读

换句话说,这个:

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”关键字之后提供可用的内容,例如“Policy”等。

然而,这是一个大问题吗?您使用过的流畅界面是这样的吗?明确定义接口的警告是使用所有关键字和所有内容创建一个类或接口,以便每个逗号后的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 个答案:

答案 0 :(得分:6)

两件事:扩展方法和嵌套闭包。它们应该涵盖您的所有可扩展性和智能感知清晰度需求。


如果您有兴趣,请参阅我构建Fluent NHibernate的经验中的一些提示。

方法链应该保持在最低限度。除其他外,它会导致死胡同和无限期结束。 首选嵌套闭包。

例如,死胡同:

Database
  .ConnectionString
    .User("name")
    .Password("xxx")
  .Timeout(100) // not possible

一旦进入Database链,就无法返回ConnectionString链,因为无法使用所有与连接字符串相关的方法返回ConnectionString的实例1}}。

你可以用一种明确的方法重写它,但它们很难看。

Database
  .ConnectionString
    .User("name")
    .Pass("xxx")
    .Done()
  .Timeout(100)

在这种情况下,Done将返回Database实例,返回主链。再次,丑陋。

根据建议,更喜欢嵌套闭包

Database
  .ConnectionString(cs =>
    cs.User("name");
      .Pass("xxx"))
  .Timeout(100);

这几乎涵盖了你的intellisense问题,因为闭包是相当独立的。您的顶级对象将仅包含采用闭包的方法,而这些闭包仅包含特定于该操作的方法。可扩展性在这里也很容易,因为您可以只在闭包内公开的类型中添加扩展方法。

您还应该注意不要尝试将您的流畅界面读成英语。 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)

智能感知中的可发现性降低了,因为人们倾向于寻找动词而未能找到动词。 FNH的一个例子是WithTableName方法,人们倾向于寻找 table 这个词,而不是找到它,因为该方法以 with 开头。

您的界面也变得更难以用于非母语英语使用者。虽然大多数非母语人士都会知道他们所寻找的技术术语,但他们可能并不清楚这些额外的词语。

答案 1 :(得分:0)

基于answer提供的@James Gregory,我为我的IoC容器创建了一个新的原型流畅界面,这就是我最终的语法。

这解决了我当前的问题:

  1. 可扩展性,我可以使用扩展方法添加新的分辨率类型,新范围类型等
  2. 易于编写流畅的界面,无需复制导致相同路径后缀的关键字
  3. 与我的第一次和第二次迭代实施相比,代码少得多
  4. 所有代码都在我的沙箱中编译,所以它都是合法的语法,没有什么是伪造的,除了当然这些方法当然没有做任何事情。

    我决定修复的一件事是流畅界面的指导部分,它限制了您在界面移动时的选择。因此,写这个是完全有效的:

    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
            }
        }
    }