流利地设置C#属性和链接方法

Joh*_*lla 7 c# xml fluent-interface .net-3.5

我正在使用.NET 3.5.我们有一些复杂的第三方类,它们是自动生成的,不受我的控制,但我们必须将它们用于测试目的.我看到我的团队在我们的测试代码中进行了很多深度嵌套的属性获取/设置,而且它变得非常麻烦.

为了解决这个问题,我想建立一个流畅的界面来设置分层树中各种对象的属性.这个第三方库中有大量属性和类,手动映射所有内容太繁琐了.

我最初的想法是只使用对象初始化器.Red,, BlueGreen是属性,并且Mix()是一种方法,将第四个属性设置为Color具有该混合颜色的最接近的RGB安全颜色.涂料在使用Stir()前必须均匀化.

Bucket b = new Bucket() {
  Paint = new Paint() {
    Red = 0.4;
    Blue = 0.2;
    Green = 0.1;
  }
};
Run Code Online (Sandbox Code Playgroud)

这有助于初始化Paint,但我需要链接Mix()和其他方法.下一次尝试:

Create<Bucket>(Create<Paint>()
  .SetRed(0.4)
  .SetBlue(0.2)
  .SetGreen(0.1)
  .Mix().Stir()
)
Run Code Online (Sandbox Code Playgroud)

但这不能很好地扩展,因为我必须为我想要设置的每个属性定义一个方法,并且所有类中都有数百个不同的属性.另外,C#没有办法在C#4之前动态定义方法,所以我认为我不能以某种方式自动挂钩.

第三次尝试:

Create<Bucket>(Create<Paint>().Set(p => {
    p.Red = 0.4;
    p.Blue = 0.2;
    p.Green = 0.1;
  }).Mix().Stir()
)
Run Code Online (Sandbox Code Playgroud)

这看起来并不太糟糕,似乎它是可行的.这是一种可行的方法吗?是否可以编写一种Set以这种方式工作的方法?或者我应该采取其他策略?

dah*_*byk 9

这有用吗?

Bucket b = new Bucket() {
  Paint = new Paint() {
    Red = 0.4;
    Blue = 0.2;
    Green = 0.1;
  }.Mix().Stir()
};
Run Code Online (Sandbox Code Playgroud)

假设Mix()Stir()定义为返回一个Paint对象.

要调用返回的方法,void可以使用扩展方法,该方法允许您对传入的对象执行其他初始化:

public static T Init<T>(this T @this, Action<T> initAction) {
    if (initAction != null)
        initAction(@this);
    return @this;
}
Run Code Online (Sandbox Code Playgroud)

可以使用类似于Set()的描述:

Bucket b = new Bucket() {
  Paint = new Paint() {
    Red = 0.4;
    Blue = 0.2;
    Green = 0.1;
  }.Init(p => {
    p.Mix().Stir();
  })
};
Run Code Online (Sandbox Code Playgroud)


pdr*_*pdr 5

我会这样想的:

您基本上希望链中的最后一个方法返回一个Bucket.在你的情况下,我认为你希望这个方法是Mix(),因为你可以随后搅拌()桶

public class BucketBuilder
{
    private int _red = 0;
    private int _green = 0;
    private int _blue = 0;

    public Bucket Mix()
    {
        Bucket bucket = new Bucket(_paint);
        bucket.Mix();
        return bucket;
    }
}
Run Code Online (Sandbox Code Playgroud)

因此,在调用Mix()之前,您需要设置至少一种颜色.让我们用一些Syntax接口来强制它.

public interface IStillNeedsMixing : ICanAddColours
{
     Bucket Mix();
}

public interface ICanAddColours
{
     IStillNeedsMixing Red(int red);
     IStillNeedsMixing Green(int green);
     IStillNeedsMixing Blue(int blue);
}
Run Code Online (Sandbox Code Playgroud)

让我们将它们应用于BucketBuilder

public class BucketBuilder : IStillNeedsMixing, ICanAddColours
{
    private int _red = 0;
    private int _green = 0;
    private int _blue = 0;

    public IStillNeedsMixing Red(int red)
    {
         _red += red;
         return this;
    }

    public IStillNeedsMixing Green(int green)
    {
         _green += green;
         return this;
    }

    public IStillNeedsMixing Blue(int blue)
    {
         _blue += blue;
         return this;
    }

    public Bucket Mix()
    {
        Bucket bucket = new Bucket(new Paint(_red, _green, _blue));
        bucket.Mix();
        return bucket;
    }
}
Run Code Online (Sandbox Code Playgroud)

现在你需要一个初始静态属性来启动链

public static class CreateBucket
{
    public static ICanAddColours UsingPaint
    {
        return new BucketBuilder();
    }
}
Run Code Online (Sandbox Code Playgroud)

这就是它,你现在有一个流畅的界面与可选的RGB参数(只要你输入至少一个)作为奖金.

CreateBucket.UsingPaint.Red(0.4).Green(0.2).Mix().Stir();
Run Code Online (Sandbox Code Playgroud)

Fluent Interfaces的问题在于它们并不容易组合在一起,但是开发人员很容易编写代码并且它们非常易于扩展.如果你想在不改变所有调用代码的情况下为其添加Matt/Gloss标志,那么很容易做到.

此外,如果您的API提供商更改了您下面的所有内容,您只需要重写这一段代码; 所有的callin代码都可以保持不变.