我正在尝试使用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
。关于如何使签名类型可选的任何建议都非常受欢迎。我一如既往地感谢你。
可以看到完整代码here。
答案 0 :(得分:3)
首先,通常您无法使用某种语言机制来实现设计模式,因为设计模式不能直接在语言类型系统和语法中表达。设计模式描述了解决软件开发中反复出现的问题的特定方法。很快,由于语言提供了直接表达设计模式的机制,因此不再将其视为设计模式。因此,某种语言中的设计模式成为另一种语言的机制。例如,汇编语言中的循环是一种设计模式,但在大多数现代语言中,它是一种语法结构。设计模式的存在通常表明缺乏特定语言或编程范例的表达性。但是,无论你的语言有多么富有表现力,总会有抽象,无法使用语言机制直接实现。
您还应该了解GoF设计模式是在考虑OOP语言的情况下编写的,具有当时OOP语言的特殊性和局限性。因此,在OCaml / Reason或任何其他具有参数多态性和一流功能的语言中,它们并不总是适用甚至不需要。
特别是,Builder模式试图解决的问题是缺少一流的构造函数和参数多态。由于我们在Reason中都有,因此我们通常不会为设计复杂的类型层次结构而烦恼。 OOP的另一个限制是缺少代数数据类型,它们是实现复杂复合数据结构的理想语言机制,例如抽象语法树(表达式解析树)等。
尽管如此,您仍然可以在Reason中使用Builder模式,但很可能您实际上并不需要它,因为该语言提供了更好,更可表达的解决问题的机制。让我们使用维基百科的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 Pattern。
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)
};
};
最后,让我们实现一位导演。
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版本)。