建造者模式 - 有前提条件的方法

builder pattern - methods with preconditions

出于测试 目的,我有一个 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(不同的状态需要不同的先决条件)。

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

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();
    }
}

首先,您必须跨多个接口隔离这些方法(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 来创建产品。

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

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

相关:

您可以通过返回接口来抽象实现,而不是返回 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;
    }
}

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