【问题标题】:Fluent interface building different concrete typesFluent 界面构建不同的具体类型
【发布时间】:2012-10-25 18:29:59
【问题描述】:

我需要一些关于如何构建作为 Builder 的流畅接口的建议,负责根据调用的方法返回不同的具体类型。

想象一下,我需要使用我的 ProductBuilder(流利地)创建以下类型之一:Product、ServiceProduct、PackagedProduct(都派生自 Product)。

我正在考虑使用这样的流利语法(非常欢迎其他建议):

创建产品:

var product = new ProductBuilder()
   .Create("Simple product")
   .WithPrice(12.5)

创建服务产品

var product = new ProductBuilder()
   .Create("Service product")
   .WithPrice(12.5)
   .AsServiceProduct()
       .ServiceProductSpecificMethods...()

然后 PackagedProduct 调用 AsPackagedProduct() 而不是 AsServiceProduct() 等。你明白了。

我还没有找到展示这方面最佳做法的示例。只有最终构建返回相同类型的样本。

有什么建议吗?

【问题讨论】:

  • 你对这个接口有完整的要求吗?您是否正在寻求帮助将它们组织成流畅的界面?
  • 建造者模式真正吸引你的是什么?您要解决什么问题
  • @GlennFerrieLive:我想知道如何调用例如AsServiceProduct() “通知” ProductBuilder 上的 Build 方法以实际创建具有特定属性的 ServiceProduct 实例(由构建器上的后续方法调用提供,在 AsServiceProduct() 之后),而不是最简单形式的 Product。我可以在 AsServiceProduct 方法中设置一个私有属性,告诉构建器创建 ServiceProduct 而不是 Product,但我认为有比这更好的解决方案。
  • @32bitkid:在这个简单的案例中什么都没有。我正在研究设计这些界面的方法,以确定它是否对我们有用。

标签: c# fluent fluent-interface


【解决方案1】:

我在这里看到两个选项。

如果有有限数量的产品是固定的,而不是为扩展而设计的,那么只需为每个产品创建一个Create 方法:

var product = new ProductBuilder()
   .CreateSimple()
   .WithPrice(12.5);

var product = new ProductBuilder()
   .CreateService()
   .WithPrice(12.5)
   .ServiceProductSpecificMethods...();

如果您不想(或不能)ProductBuilder 了解所有产品类型,那么我会使用泛型:

public class Product {}
public class SimpleProduct : Product {}
public class ServiceProduct : Product {}

var product = new ProductBuilder<SimpleProduct>()
   .WithPrice(12.5);

这是设计的起点:

public class Product
{
    public decimal Price { get; set; }
}
public class SimpleProduct : Product { }
public class ServiceProduct : Product
{
    public string Service { get; set; }
}

public class ProductBuilder<T> where T : Product, new()
{
    private List<Action<T>> actions = new List<Action<T>>();

    public T Build()
    {
        T product = new T();
        foreach (var action in actions)
        {
            action(product);
        }

        return product;
    }
    public void Configure(Action<T> action)
    {
        actions.Add(action);
    }
}

public static class ProductExtensions
{
    public static ProductBuilder<T> WithPrice<T>(this ProductBuilder<T> builder, decimal price)
        where T : Product
    {
        builder.Configure(product => product.Price = price);
        return builder;
    }

    public static ProductBuilder<T> WithService<T>(this ProductBuilder<T> builder, string service)
            where T : ServiceProduct
    {
        builder.Configure(product => product.Service = service);
        return builder;
    }
}

【讨论】:

  • 这是一个有趣的解决方案Servy。非常感谢你。但是,它确实要求产品属性的设置器是公开/受保护的。你有没有办法避免这种情况?
  • @TommyJakobsen 好吧,最简单的方法是将导致突变的所有内容都设为internal,并将所有这些内容移到它自己的.dll 中。为避免这种情况,您最终可能只会返回接口,而不是具体对象,或者使用反射(这样您就可以将设置器设为私有并仍然在类外访问它们)。
  • 害怕那个答案 :) 但是,是的,我知道为什么而且可能没有办法解决它。
  • @TommyJakobsen 另一种选择是使所有产品类型不可变。使所有返回具有附加值的新产品的 Actions Funcs 相当简单。实际上,我认为流畅的语法主要对不可变数据类型有用。对于可变数据类型,您也可以使用更传统的语法。
  • 毫无疑问,我认为你是对的,在这种情况下更传统的语法会是最好的。但是你的方法很好,在某些时候它可能会派上用场。感谢您的分享。我会把这个问题留一两天,看看是否会出现其他方法。
【解决方案2】:

如果我的理解正确,我会在这里使用泛型,这样我就可以编写如下内容:

var product = new ProductBuilder()
.Create<Serviceproduct>()
.WithPrice(12.5)
   .ServiceProductSpecificMethods...()

您还可以在调用特定服务方法之前添加 Build 方法,以便它实际创建最终产品:

var product = new ProductBuilder()
.Create<Serviceproduct>()
.WithPrice(12.5)
.Build()
   .ServiceProductSpecificMethods...()

【讨论】:

  • Create() 方法会返回什么?在您的第二个示例中,对 Build 的调用是否返回 ServiceProduct 的实例?在这种情况下,您正在构建无效的 ServiceProduct,因为在构建 ServiceProduct 之前需要在构建器上设置所需的属性(通过方法链),以确保构建器始终构建有效的产品。
  • 抱歉回复晚了。第一种(简单)方法的想法是您在 .Create() 调用上创建 Serviceproduct 并使用扩展方法来配置产品,然后调用特定方法。扩展方法应该有明确定义的约束才能正常工作。
  • 在第二个示例中,想法与@Servy 的回答非常相似。 ProductBuilder().Create() 将返回类 ProductBuilder (可以从答案@Servy 中获取)。实际上,您将拥有 new ProductBuilder().Create().WithPrice(12.5).Build() 与 new ProductBuilder().WithPrice(12.5).Build() 的唯一区别。在我的实现中,您将获得一个额外的课程。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-11-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-02-13
相关资源
最近更新 更多