流畅的界面体验?我需要你的意见! [英] Experience with fluent interfaces? I need your opinion!

查看:179
本文介绍了流畅的界面体验?我需要你的意见!的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

对不起,这个问题很长,因为我要求的东西可能没有一个非常具体的答案。

我的主要问题是:


如何编写一个在基类中没有完全定义的流畅的接口,使得使用流畅接口的程序可以在现有结构中添加新的单词,并且仍然保持引导接口,智能感知只列出了实际应用的关键字。







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

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



我正在寻找流畅的界面进行配置,而且我还想在界面中构建完整的可扩展性(或者摆脱它,我不愿意这么做),所以我需要以不同的方式做事。


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




  • 使用流畅接口的代码通常很容易阅读


    换句话说,

      ServiceContainer.Register< ISomeService>()
    .From.ConcreteType< SomeService>()
    。 For.Policy(DEBUG)
    .With.Scope.Container()
    .And.With.Parameters
    .Add< String>(connectionString,Provider = ... 。)
    .Add<布尔>(optimizeSql,true);

    比这更容易阅读:

    <$ ServiceContainer.Register(typeof(ISomeService),typeof(SomeService),
    DEBUG,ServiceScope.Container,new Object [] {Provider = ...,true });

    所以可读性是一个问题。然而,程序员的指导是另一个,通过阅读现有的代码,在网络上或在编辑器中不容易理解。

    基本上,当我输入这个:

    / p>

      ServiceContainer.Register< ISomeService>()
    .From。
    $ -cursor here

    然后intellisense会显示可用的分辨率类型。我选择了一个,然后写:

      ServiceContainer.Register< ISomeService>()
    .From。 ConcreteType< SomeService>()
    .For。|

    然后我只在For关键字之后得到可用的东西,比如Policy等。 / p>

    然而,这是一个大问题吗?你有流畅的接口吗?定义接口的显而易见的方法是用所有的关键字和所有的东西来创建一个类或者一个接口,这样每个逗号后面的intellisense就包含了所有的东西,但是这也可能导致这是合法的(比如,编译)代码:

      ServiceContainer.Register< ISomeService>()
    .From.ConcreteType< SomeService>()$ ()=> new SomeService())
    .From.With.For.Policy(Test); b $ b .From.Delegate

    所以我想构造流畅的接口,以便在指定解决一个服务,你不能再这样做。




    • 换句话说,流畅的接口很容易使用,因为他们引导你去做你能做的事情。


    但这是典型的吗?因为我想能够添加一堆这些关键字,如解析器(ConcreteType,Delegate等)的类型,作为扩展方法的范围类型(​​Factory,Container,Singleton,Cache等),以便程序可以定义自己的方法来做到这一点,而不必进入和更改基类,这意味着我需要提供所有中间停靠点的接口,并让实际的重要关键字。这些关键字的实现然后必须选择一个中间停止接口返回,适当的。

    所以看起来像我需要定义一个接口:


    $ b $ ul
  • xyz.From。

  • xyz.From。<这里解析> 。

  • <此处是解析器> .With。

  • <这里解析> .For。



  • 等。但是对我来说看起来是碎片化的。



    任何有流畅界面经验的人都可以回头阅读我附近的引用答案,并试图给我一个简短的答案吗?两个东西:扩展方法和嵌套闭包。他们应该涵盖所有的可扩展性和intellisense清晰度的需求。






    如果您有兴趣,体验流利的NHibernate



    方法链应该是保持在最低限度。除了其他方面,它导致了死胡同和无限期的结束。



    例如,死胡同: > Database
    .ConnectionString
    .User(name)
    .Password(xxx)
    .Timeout(100)//不可能

    你不能回到 Database 链一旦你进入了 ConnectionString 链,因为没有办法返回所有与连接字符串相关的方法返回 ConnectionString code>。



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

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

    在这种情况下, Done 会返回数据库实例,返回到主链。再次,丑陋。



    如上所述,喜欢嵌套闭包
    $ b $

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

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



    您还应该注意不要尝试使你的流畅接口像英文一样阅读 UseThis.And.Do.That.With.This.BecauseOf.That 链只会使你的界面复杂化,当动词

      Database 
    .UsingDriver< DatabaseDriver>()
    .And.Using。方言< SQL>()
    .If.IsTrue(someBool)

    数据库
    。驱动程序< DatabaseDriver>()
    .Dialect< SQL>()








    $如果(someBool)

    intellisense中的可发现性降低,因为人们倾向于查找动词,找到它。 FNH的一个例子是 WithTableName 方法,在这个方法中,人们倾向于查找单词 table 而没有找到它,因为方法以



    您的界面对于非母语英语使用者而言也变得更加困难。虽然大多数非母语的人会知道他们正在寻找的技术术语,但额外的单词可能并不清楚。


    Sorry for this long question, it is flagged wiki since I'm asking for something that might not have a very concrete answer. If it is closed, so be it.

    My main question is this:

    How would you write a fluent interface that isn't fully defined in the base classes, so that programs that uses the fluent interfaces can tack on new words inside the existing structure, and still maintain a guiding interface so that after a dot, the intellisense only lists the keywords that actually apply at this point.


    I'm on my 3rd iteration of rewriting my IoC container. The 2nd iteration was to improve performance, this third iteration will be to solve some extensibility problems, and separation-problems.

    Basically, the problem with extensibility is that there is none. I recently wanted to use a service that had a lifetime, and after the lifetime had expired, resolve a fresh copy. For instance, read a config file every minute, but not more often. This was not supported by my current IoC solution, but the only way to add it was to go into the base class library and add support for it there. This means to me that I've failed to build an extensible class library. In all fairness, I didn't intend to build extensibility into it, but then I didn't fully appreciate how much pain it would be to go in and add something like this later.

    I'm looking at my fluent interface for configuration, and since I want to build full extensibility into the interface as well (or get rid of it, which I'm loath to do) I need to do things differently.

    As such, I need your opinion. I have very little experience actually using fluent interfaces, but I've seen quite a bit of code that uses them, and as such there is one obvious benefit right out of the box:

    • Code that uses fluent interfaces are usually very easy to read

    In other words, this:

    ServiceContainer.Register<ISomeService>()
        .From.ConcreteType<SomeService>()
        .For.Policy("DEBUG")
        .With.Scope.Container()
        .And.With.Parameters
            .Add<String>("connectionString", "Provider=....")
            .Add<Boolean>("optimizeSql", true);
    

    is easier to read than this:

    ServiceContainer.Register(typeof(ISomeService), typeof(SomeService),
        "DEBUG", ServiceScope.Container, new Object[] { "Provider=...", true });
    

    So readability is one issue.

    However, programmer guidance is another, something which isn't easily understood by reading existing code, on the web or in an editor.

    Basically, when I type this:

    ServiceContainer.Register<ISomeService>()
        .From.|
              ^-cursor here
    

    and then intellisense will show the available resolution types. After I've picked that one, and write:

    ServiceContainer.Register<ISomeService>()
        .From.ConcreteType<SomeService>()
        .For.|
    

    then I only get things available after the "For" keyword, like "Policy" and such.

    However, is this a big issue? Have fluent interfaces you've used been like this? The obvious cop-out to define the interface is to make a class, or an interface, with all the keywords, and everything, so that intellisense after each comma contains everything, but this could also lead to this being legal (as in, it compiles) code:

    ServiceContainer.Register<ISomeService>()
        .From.ConcreteType<SomeService>()
        .From.Delegate(() => new SomeService())
        .From.With.For.Policy("Test");
    

    so I'd like to structure the fluent interfaces such that after you've specified how to resolve a service, you cannot do that again.

    • In other words, fluent interfaces are very easy to use, since they guide you towards what you can do.

    But is this typical? Since I want to be able to add a bunch of these keywords, like the type of resolver (ConcreteType, Delegate, etc.), the type of scope (Factory, Container, Singleton, Cache, etc.) as extension methods, so that programs can define their own ways to do this without having to go in and change the base classes, it means I'll need to provide interfaces for all the intermediate stops, and let the actual important keywords be. The implementation for those keywords then have to pick one intermediate-stop-interface to return, as appropriate.

    So it looks like I need to define an interface for:

    • xyz.From.
    • xyz.From.<Resolver here>.
    • <Resolver here>.With.
    • <Resolver here>.For.

    etc. but that looks fragmented to me.

    Can anyone with experience with fluent interfaces go back and read my quoted answer near the top and try to give me a short answer?

    解决方案

    Two things: Extension methods and nested closures. They should cover all your extensibility and intellisense clarity needs.


    If you're interested, here's a couple of tips from my experience building Fluent NHibernate.

    Method chaining should be kept to a minimum. It leads to dead-ending and an indefinite end to the call-chain, among other things. Prefer nested closures.

    For example, dead-ending:

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

    You can't get back to the Database chain once you've entered the ConnectionString chain, because there's no way back up with all the connection-string related methods returning an instance of ConnectionString.

    You could rewrite it with a definite-end method, but they're ugly.

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

    Where in this case, Done would return the Database instance, returning you to the primary chain. Again, ugly.

    As suggested, prefer nested closures.

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

    That pretty much covers your intellisense issues, as closures are fairly self-contained. Your top-level object will only contain the methods that take closures, and those closures only contain the methods specific to that operation. Extensibility is also easy here, because you can add extension methods just to the types that are exposed inside the closures.

    You should also be aware to not try to make your fluent interface read like english. UseThis.And.Do.That.With.This.BecauseOf.That chains only serve to complicate your interface when the verbs would suffice.

    Database
      .Using.Driver<DatabaseDriver>()
      .And.Using.Dialect<SQL>()
      .If.IsTrue(someBool)
    

    Versus:

    Database
      .Driver<DatabaseDriver>()
      .Dialect<SQL>()
      .If(someBool)
    

    Discoverability in intellisense is reduced, because people tend to look for the verb and fail to find it. An example of this from FNH would be the WithTableName method, where people tend to look for the word table and not find it because the method starts with with.

    Your interface also becomes more difficult to use for non-native english language speakers. While most non-native speakers will know the technical terms for what they're looking for, the extra words may not be clear to them.

    这篇关于流畅的界面体验?我需要你的意见!的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

    查看全文
    登录 关闭
    扫码关注1秒登录
    发送“验证码”获取 | 15天全站免登陆