【问题标题】:builder pattern - methods with preconditions建造者模式 - 有先决条件的方法
【发布时间】:2015-07-14 20:51:06
【问题描述】:

出于测试的目的,我有一个Factory,它使用Builder 生成Products。每个Product 都可以有一个状态(Available/InUse/Disposed/ 等)。我需要生产各种状态的产品。

我的问题是,为了让我产生一个Product,比如说Disposed,它首先需要是InUse(必须使用new ProductBuilder().CheckIn().Dispose().Build();创建它,而不仅仅是new ProductBuilder().Dispose().Build();

我如何(或必须?)为构建器方法强制执行此前提条件并保持圈复杂度为 1(因此不需要进一步测试) .

不希望在每种可能的情况下都使用if (product.Status == Product.INUSE) {...} 和抛出exceptions 之类的东西(不同的状态需要不同的先决条件)。

由于构建器是私有的,我需要强制执行吗?我是否只是依靠程序员知道需要调用方法的顺序,并在每个构建器方法之前添加一些 cmets?我应该选择不同的模式(哪个?)。

public static class ProductFactory
{
    private class ProductBuilder
    {
        private Product product;

        public ProductBuilder()
        {
            product = new Product {Status = product.AVAILABLE};
        }

        public ProductBuilder Dispose()
        {
            product.Status = product.DISPOSED; return this;
        }

        public ProductBuilder CheckIn()
        {
            product.Status = product.INUSE; return this;
        }

        public Product Build()
        {
            return product;
        }
    }

    public static Product CreateAvailable()
    {
        return new ProductBuilder().Build();
    }

    public static Product CreateInUse()
    {
        return new ProductBuilder().CheckIn().Build();
    }

    public static Product CreateDisposed()
    {
        return new ProductBuilder().CheckIn().Dispose().Build();
    }
}

【问题讨论】:

  • 这里使用构建器模式似乎没有太多好处。为什么不直接退货?
  • 我同意@Enigmativity。没有太多要封装的。如果您想将构建器注入许多不同的客户端,构建器模式很有用 - 这里只有 一个 客户端,即工厂。
  • 构建器内部的方法实际上有点复杂,我觉得有必要以某种方式提取它们,我希望能够在构建对象时使用构建器提供的流畅性。此外,在我的真实案例中,我需要通过组合相同的方法来获得更多状态(顺序很重要,提供不同的结果)。不过我会考虑你的建议...

标签: c# design-patterns methods builder prerequisites


【解决方案1】:

首先,您必须在多个接口之间隔离这些方法(CheckInDisposed):

public interface IBaseBuilder
{
    Product Build();
}

public interface IProductBuilder : IBaseBuilder
{
    ICheckedInProductBuilder CheckIn();
}

public interface ICheckedInProductBuilder : IBaseBuilder
{
    IDisposedProductBuilder Dispose();
}

public interface IDisposedProductBuilder : IBaseBuilder
{

}

这样,给定一个初始的IProductBuilder

  • 您只能按特定顺序调用CheckIn -> Dispose
  • 一旦CheckIn被调用,就不能再次调用了
  • 一旦Dispose被调用,就不能再调用了
  • 您也可以随时致电Build 来创建产品。

为了使事情更容易实现,您可以让一个类实现所有三个接口,并使用主要的IProductBuilder 接口将其注入其客户端。或者,您可以让不同的类实现接口。我会把它留作练习。

作为一个现实世界的例子,这种技术被广泛用于 Moq 和 FluentAssertions 以实现流畅的 API。

相关:Making my class 'fluent'

【讨论】:

  • 从我读到的内容来看,显然这是要走的路......虽然我认为类图会爆炸(我有多个“Product”)......所以,对于每个实体有各种状态,我需要 M x N 接口......那种很糟糕......但我想这是流畅的代价......
  • @user3215557 确实,流畅不是一件容易的事 :/ 正如我所说,你可以有多个小的、细粒度的接口,然后有一个实现其中大部分的类——这可能有助于减少复杂性。
【解决方案2】:

您可以通过返回一个接口来抽象出实现,而不是返回ProductBuilder。这样,您可以仅通过首先接收该接口来使某些方法可用。

让我用代码解释一下。假设一个Disposed 功能应该只有在InUse 之后才可用,那么你可以这样做:

public interface IInUseProductBuilder
{
    IDisposedProductBuilder Dispose();
}

public interface IDisposedProductBuilder
{
    IBuiltProductBuilder Build();
}

当你实现它时:

public class ProductBuilder : IInUseProductBuilder, IDisposedProductBuilder
{
    public IDisposedProductBuiler Dispose()
    {
        product.Status = product.DISPOSED; 
        return this;
    }
}

这样,用户将只能使用在该特定构建器上声明的方法,您将根据工作流程公开这些方法。

【讨论】:

  • 感谢您的努力 :)。我明白了。我希望我可以标记 2 个答案。但这次我会选择 dcastro 的答案。
  • @user 当然可以!很高兴它有帮助
猜你喜欢
  • 2020-11-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-05-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多