建造者模式(又称生成器模式)是一种创建型模式,使你能够分步骤创建复杂对象。该模式允许你使用相同的创建代码生成不同类型和形式的对象。
假设有这样一个复杂对象,在对其进行构造时需要对诸多成员变量和嵌套对象进行繁复的初始化工作。这些初始化代码通常深藏于一个包含众多参数且让人基本看不懂的构造函数中;甚至还有更糟糕的情况,那就是这些代码散落在客户端代码的多个位置。
例如,我们来思考如何创建一个房屋House
对象。建造一栋简单的房屋,首先你需要建造四面墙和地板,安装房门和一套窗户,然后再建造一个屋顶。但是如果你想要一栋更宽敞更明亮的房屋,还要有院子和其他设施(例如暖气、排水和供电设备),那又该怎么办呢?
最简单的方法是扩展房屋
基类,然后创建一系列涵盖所有参数组合的子类。但最终你将面对相当数量的子类。任何新增的参数(例如门廊类型)都会让这个层次结构更加复杂。
另一种方法则无需生成子类。你可以在房屋
基类中创建一个包括所有可能参数的超级构造函数,并用它来控制房屋对象。这种方法确实可以避免生成子类,但它却会造成另外一个问题。
通常情况下,绝大部分的参数都没有使用,这使得对于构造函数的调用十分不简洁。例如,只有很少的房子有游泳池,因此与游泳池相关的参数十之八九是毫无用处的。
生成器模式建议将对象构造代码从产品类中抽取出来,并将其放在一个名为生成器的独立对象中。
该模式会将对象构造过程划分为一组步骤,比如buildWalls创建墙壁
和buildDoor创建房门
创建房门等。每次创建对象时,你都需要通过生成器对象执行一系列步骤。重点在于你无需调用所有步骤,而只需调用创建特定对象配置所需的那些步骤即可。
当你需要创建不同形式的产品时,其中的一些构造步骤可能需要不同的实现。例如,木屋的房门可能需要使用木头制造,而城堡的房门则必须使用石头制造。
在这种情况下,你可以创建多个不同的生成器,用不同方式实现一组相同的创建步骤。然后你就可以在创建过程中使用这些生成器(例如按顺序调用多个构造步骤)来生成不同类型的对象。
例如,假设第一个建造者使用木头和玻璃制造房屋,第二个建造者使用石头和钢铁,而第三个建造者使用黄金和钻石。在调用同一组步骤后,第一个建造者会给你一栋普通房屋,第二个会给你一座小城堡,而第三个则会给你一座宫殿。但是,只有在调用构造步骤的客户端代码可以通过通用接口与建造者进行交互时,这样的调用才能返回需要的房屋。
你可以进一步将用于创建产品的一系列生成器步骤调用抽取成为单独的主管类。主管类可定义创建步骤的执行顺序,而生成器则提供这些步骤的实现。
严格来说,你的程序中并不一定需要主管类。客户端代码可直接以特定顺序调用创建步骤。不过,主管类中非常适合放入各种例行构造流程,以便在程序中反复使用。
此外,对于客户端代码来说,主管类完全隐藏了产品构造细节。客户端只需要将一个生成器与主管类关联,然后使用主管类来构造产品,就能从生成器处获得构造结果了。
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160 | using System;using System.Collections.Generic;namespace RefactoringGuru.DesignPatterns.Builder.Conceptual{ // The Builder interface specifies methods for creating the different parts // of the Product objects. public interface IBuilder { void BuildPartA(); void BuildPartB(); void BuildPartC(); } // The Concrete Builder classes follow the Builder interface and provide // specific implementations of the building steps. Your program may have // several variations of Builders, implemented differently. public class ConcreteBuilder : IBuilder { private Product _product = new Product(); // A fresh builder instance should contain a blank product object, which // is used in further assembly. public ConcreteBuilder() { this.Reset(); } public void Reset() { this._product = new Product(); } // All production steps work with the same product instance. public void BuildPartA() { this._product.Add("PartA1"); } public void BuildPartB() { this._product.Add("PartB1"); } public void BuildPartC() { this._product.Add("PartC1"); } // Concrete Builders are supposed to provide their own methods for // retrieving results. That's because various types of builders may // create entirely different products that don't follow the same // interface. Therefore, such methods cannot be declared in the base // Builder interface (at least in a statically typed programming // language). // // Usually, after returning the end result to the client, a builder // instance is expected to be ready to start producing another product. // That's why it's a usual practice to call the reset method at the end // of the `GetProduct` method body. However, this behavior is not // mandatory, and you can make your builders wait for an explicit reset // call from the client code before disposing of the previous result. public Product GetProduct() { Product result = this._product; this.Reset(); return result; } } // It makes sense to use the Builder pattern only when your products are // quite complex and require extensive configuration. // // Unlike in other creational patterns, different concrete builders can // produce unrelated products. In other words, results of various builders // may not always follow the same interface. public class Product { private List<object> _parts = new List<object>(); public void Add(string part) { this._parts.Add(part); } public string ListParts() { string str = string.Empty; for (int i = 0; i < this._parts.Count; i++) { str += this._parts[i] + ", "; } str = str.Remove(str.Length - 2); // removing last ",c" return "Product parts: " + str + "\n"; } } // The Director is only responsible for executing the building steps in a // particular sequence. It is helpful when producing products according to a // specific order or configuration. Strictly speaking, the Director class is // optional, since the client can control builders directly. public class Director { private IBuilder _builder; public IBuilder Builder { set { _builder = value; } } // The Director can construct several product variations using the same // building steps. public void buildMinimalViableProduct() { this._builder.BuildPartA(); } public void buildFullFeaturedProduct() { this._builder.BuildPartA(); this._builder.BuildPartB(); this._builder.BuildPartC(); } } class Program { static void Main(string[] args) { // The client code creates a builder object, passes it to the // director and then initiates the construction process. The end // result is retrieved from the builder object. var director = new Director(); var builder = new ConcreteBuilder(); director.Builder = builder; Console.WriteLine("Standard basic product:"); director.buildMinimalViableProduct(); Console.WriteLine(builder.GetProduct().ListParts()); Console.WriteLine("Standard full featured product:"); director.buildFullFeaturedProduct(); Console.WriteLine(builder.GetProduct().ListParts()); // Remember, the Builder pattern can be used without a Director // class. Console.WriteLine("Custom product:"); builder.BuildPartA(); builder.BuildPartC(); Console.Write(builder.GetProduct().ListParts()); } }} |
---|
执行结果:
12345678 | Standard basic product:Product parts: PartA1Standard full featured product:Product parts: PartA1, PartB1, PartC1Custom product:Product parts: PartA1, PartC1 |
---|