0

我正在尝试使用 Reason 中的模块来遵循构建器设计模式。我有以下类型:

type userBuilderType = {
  mutable name: string,
};

以及签名类型:

module type UserBuilderType = {
  let name: string;
};

我将UserBuilderType签名类型作为函子传递给BuilderPattern

module BuilderPattern = fun(Builder: UserBuilderType) => {
  let builder = {
    name: "",
  };

  let setName = builder.name = Builder.name;
  let getName () => builder.name;
};

然后,我将适当的值作为模块传递,执行以下操作:

module SetOfMixedPairs = BuilderPattern({
  let name = "asd";
});

然而,为了让这个构建器设计模式真正成为一个构建器设计模式,签名类型需要是可选的。我正在为如何做到这一点而苦苦挣扎。例如,如果我将签名类型编辑为空:

module type UserBuilderType = {};

编译器会抱怨:Unbound value Builder.name. 任何关于如何使签名类型可选的建议都非常受欢迎。我一如既往的感谢。

完整的代码可以在这里看到。

4

1 回答 1

3

首先,通常你不能使用语言的某种机制来实现设计模式,因为设计模式不能直接用语言类型系统和语法来表达。设计模式描述了解决软件开发中反复出现的问题的特定方法。一旦语言提供了一种直接表达设计模式的机制,这不再被认为是设计模式。因此,在一种语言中是一种设计模式的东西,在另一种语言中变成了一种机制。例如,汇编语言中的循环是一种设计模式,尽管在大多数现代语言中它是一种句法结构。设计模式的存在通常表明缺乏特定语言或编程范式的表现力。虽然,无论你的语言多么富有表现力,总有抽象,那可以

您还应该了解,GoF 设计模式是在考虑 OOP 范式的情况下编写的,并考虑了当时 OOP 语言的特性和局限性。因此,在 OCaml/Reason 或任何其他具有参数多态性和一流函数的语言中,它们并不总是适用甚至不需要。

特别是,Builder 模式试图解决的问题是缺少一流的构造函数和参数多态性。由于我们在 Reason 中两者都有,我们通常不会为设计复杂的类型层次结构而烦恼。OOP 的另一个限制是缺少代数数据类型,这是实现复杂复合数据结构(例如抽象语法树(表达式解析树)等)的理想语言机制。

尽管如此,您仍然可以在 Reason 中使用 Builder 模式,但很可能您实际上并不需要它,因为该语言提供了更好、更易表达的机制来解决您的问题。让我们使用来自 Wikipedia 的SportsCarBuilder代码作为我们的工作示例,

/// <summary>
/// Represents a product created by the builder
/// </summary>
public class Car
{
    public string Make { get; }
    public string Model { get; }
    public int NumDoors { get; }
    public string Colour { get; }

    public Car(string make, string model, string colour, int numDoors)
    {
        Make = make;
        Model = model;
        Colour = colour;
        NumDoors = numDoors;
    }
}

/// <summary>
/// The builder abstraction
/// </summary>
public interface ICarBuilder
{
    string Colour { get; set; }
    int NumDoors { get; set; }

    Car GetResult();
}

/// <summary>
/// Concrete builder implementation
/// </summary>
public class FerrariBuilder : ICarBuilder
{
    public string Colour { get; set; }
    public int NumDoors { get; set; }

    public Car GetResult()
    {
        return NumDoors == 2 ? new Car("Ferrari", "488 Spider", Colour, NumDoors) : null;        
    }
}

/// <summary>
/// The director
/// </summary>
public class SportsCarBuildDirector
{
    private ICarBuilder _builder;
    public SportsCarBuildDirector(ICarBuilder builder) 
    {
        _builder = builder;
    }

    public void Construct()
    {
        _builder.Colour = "Red";
        _builder.NumDoors = 2;
    }
}

public class Client
{
    public void DoSomethingWithCars()
    {
        var builder = new FerrariBuilder();
        var director = new SportsCarBuildDirector(builder);

        director.Construct();
        Car myRaceCar = builder.GetResult();
    }
}

我们将提供从 C# 到 Reason 的一对一翻译,以展示 Reason 中 C# 机制的直接对应物。请注意,我们不会构建惯用的 Reason 代码,人们不太可能遵循 Reason 中的 Builder 模式。

该类Car定义了构建产品的接口。我们将在 Reason 中将其表示为模块类型:

module type Car = {
  type t;
  let make : string;
  let model : string;
  let numDoors : int;
  let colour: string;

  let create : (~make:string, ~model:string, ~numDoors:int, ~colour:string) => t;
};

我们决定将汽车类型抽象化(让实现者选择特定的实现,无论是记录、对象还是汽车 SQL 数据库的键。

我们现在将为汽车制造商定义一个相应的接口:

module type CarBuilder = {
   type t;
   type car;
   let setColour : (t,string) => unit;
   let getColour : t => string;
   let setNumDoors : (t,int) => unit;
   let getNumDoors : t => int;

   let getResult : t => car;
}

现在让我们实现一个具体的构建器。由于我们决定将汽车类型抽象化,因此我们需要使用汽车类型参数化我们的具体构建器。在 OCaml/Reason 中,当您需要使用类型参数化某些东西时,通常使用函子。

module FerariBuilder = (Car: Car) => {
  type t = {
    mutable numDoors: int,
    mutable colour: string
  };
  exception BadFerrari;
  let setColour = (builder, colour) => builder.colour = colour;
  let getColour = (builder) => builder.colour;
  let setNumDoors = (builder, n) => builder.numDoors = n;
  let getNumDoors = (builder) => builder.numDoors;
  let getResult = (builder) =>
    if (builder.numDoors == 2) {
      Car.create(~make="Ferrari", ~model="488 Spider", 
                 ~numDoors=2, ~colour=builder.colour)
    } else {
      raise(BadFerrari)
    };
};

最后,让我们实现一个director。

module Director = (Car: Car, Builder: CarBuilder with type car = Car.t) => {
  let construct = (builder) => {
    Builder.setColour(builder, "red");
    Builder.setNumDoors(builder, 2)
  };
};

我会让你实现用户代码作为练习。提示,您需要从接口的具体实现开始Car您可以在Try Reason中查看并使用代码(包括 OCaml 和 Javascript 版本)。

于 2017-10-30T14:04:02.480 回答