设计模式基于C#的实现与扩展——创建型模式(五)

本文深入解析生成器模式,探讨其在创建复杂对象时的优势与应用,通过实例展示如何分离构建过程与表示,实现多变性控制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

5. 生成器模式

Separate the construction of a complex object from its representation so that the same construction process can create different representations.
将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
《设计模式:可复用面向对象软件的基础》

如果说之前提及的工程模式、抽象工厂模式和单件模式都是在创建对象的话,那么生成器模式就是一个“精工细作”的工匠,因为它用于创建复杂对象,从独立创建对象的每个部分到最后的组装,它承担每个步骤的工作。
由于它把创建每个部分都独立为一个单一的过程,因此不仅可以完成较为“精细”的创建,还可以编排创建步骤,生产不同的目标实例。

经典模式

生成器模式解决的问题是产品局部加工过程变化较大,但组装过程相对固定。例如:

  • 组装PC机,虽然声卡、显卡、内存、硬盘、机箱、CPU等有很多不同,每个部分的制造和生产过程都不同。但无论是品牌机还是个人攒机,组装一台计算机的过程相对固定。
  • 汽车的生产与此类似,虽然不同品牌、不同档次的汽车在轮胎、发动机、车身的制作工艺上有很大区别,但组装的过程相对而言非常稳定。

— 类图在此 —

public class Car
{
    public Car()
    {
        StepSequence = new List<string>();
    }

    public Wheel Wheel { get; set; }
    public Engine Engine { get; set; }
    public Body Body { get; set; }

    public IList<string> StepSequence { get; private set; }
}

public interface IBuilder
{
    void BuildBody();
    void BuildEngine();
    void BuildWheel();
}

public abstract class BuilderBase : IBuilder
{
    protected Car _Car;

    public BuilderBase()
    {
        _Car = new Car();
    }

    public Car GetProduct()
    {
        return _Car;
    }

    public virtual void BuildBody()
    {
        _Car.StepSequence.Add("Body");
    }

    public virtual void BuildEngine()
    {
        _Car.StepSequence.Add("Engine");
    }

    public virtual void BuildWheel()
    {
        _Car.StepSequence.Add("Wheel");
    }
}

public class CarBuilder : BuilderBase
{
    public override void BuildBody()
    {
        base.BuildBody();
        _Car.Body = new Body();
    }

    public override void BuildEngine()
    {
        base.BuildEngine();
        _Car.Engine = new Engine();
    }

    public override void BuildWheel()
    {
        base.BuildWheel();
        _Car.Wheel = new Wheel();
    }
}

public class AdvanceCarBuilder : BuilderBase
{
    public override void BuildBody()
    {
        base.BuildBody();
        _Car.Body = new AdvancedBody();
    }

    public override void BuildEngine()
    {
        base.BuildEngine();
        _Car.Engine = new AdvancedEngine();
    }

    public override void BuildWheel()
    {
        base.BuildWheel();
        _Car.Wheel = new AdvancedWheel();
    }
}

public class SUVBuilder : BuilderBase
{
    public override void BuildBody()
    {
        base.BuildBody();
        _Car.Body = new SUVBody();
    }

    public override void BuildEngine()
    {
        base.BuildEngine();
        _Car.Engine = new SUVEngine();
    }

    public override void BuildWheel()
    {
        base.BuildWheel();
        _Car.Wheel = new SUVWheel();
    }
}

public class Director
{
    public void BuildCar(IBuilder builder)
    {
        builder.BuildBody();
        builder.BuildEngine();
        builder.BuildWheel();
    }
}    

参与者:

  • Builder(IBuilder):负责描述创建一个产品各个组成的抽象接口。
  • Concrete Builder(CarBuilder, AdvancedBuilder, SUVBuilder):实现Builder要求的内容,并且提供一个获得产品的方法。
  • Director:虽然Builder定义了构造产品的每个步骤,但是Director告诉如何借助Builder生产产品的过程,它对Builder的操作完全基于Builder的抽象方法。

最终在使用时需要将三者结合起来,构建一个复杂的对象。

[TestClass]
public class TestBuilder
{
    private Director _CarDirector = new Director();

    [TestMethod]
    public void Test_CarBuilder()
    {
        CarBuilder builder = new CarBuilder();
        _CarDirector.BuildCar(builder);

        Car car = builder.GetProduct();
        Assert.IsNotNull(car);

        Assert.AreEqual("Normal", car.Engine.Name);
        Assert.AreEqual(27, car.Engine.Power);

        Assert.AreEqual("Normal", car.Body.Name);
        Assert.AreEqual(27, car.Body.Size);

        Assert.AreEqual("Normal", car.Wheel.Name);
        Assert.AreEqual(27, car.Wheel.Size);
    }

   [TestMethod]
    public void Test_AdvancedCarBuilder()
    {
        AdvanceCarBuilder builder = new AdvanceCarBuilder();
        _CarDirector.BuildCar(builder);
        
        Car car = builder.GetProduct();
        Assert.IsNotNull(car);

        Assert.AreEqual("Advanced", car.Engine.Name);
        Assert.AreEqual(43, car.Engine.Power);

        Assert.AreEqual("Advanced", car.Body.Name);
        Assert.AreEqual(28, car.Body.Size);

        Assert.AreEqual("Advanced", car.Wheel.Name);
        Assert.AreEqual(28, car.Wheel.Size);
    }

    [TestMethod]
    public void Test_SUVBuilder()
    {
        SUVBuilder builder = new SUVBuilder();
        _CarDirector.BuildCar(builder);

        Car car = builder.GetProduct();
        Assert.IsNotNull(car);

        Assert.AreEqual("SUV", car.Engine.Name);
        Assert.AreEqual(60, car.Engine.Power);

        Assert.AreEqual("SUV", car.Body.Name);
        Assert.AreEqual(30, car.Body.Size);

        Assert.AreEqual("SUV", car.Wheel.Name);
        Assert.AreEqual(30, car.Wheel.Size);

        Assert.AreEqual(3, car.StepSequence.Count);
        Assert.AreEqual("Body", car.StepSequence[0]);
        Assert.AreEqual("Engine", car.StepSequence[1]);
        Assert.AreEqual("Wheel", car.StepSequence[2]);
    }
}

优点:

  • 生成器模式将复杂对象的每个组成步骤暴露出来,借助Director既可以选择其执行次序,也可以选择要执行的步骤,比起工厂模式和抽象工厂模式,生成器模式更适合创建“更为复杂且每个组成部分变化较多”的类型。
  • 向客户程序屏蔽了对象创建过程中的多变性。

缺点:

  • 生成器模式会暴露出更多的执行步骤,需要Director具有更多的领域知识,使用不慎很容易造成紧密的耦合。

扩展

回想一下,现实中我们组装一台机器或组装一部汽车的时候,经常采取的方法是检查产品自己的说明书,然后开始执行组装工作。对于待加工的对象是否也可以考虑采取类似的方法呢?

看说明书加工的生成器

.NET平台有个很好的机制——“贴标签”,使用Attribute来表示扩展目标产品类型的创建信息,这样我们可以创建一个非常通用的Director类,由它“看着说明书”加工多种多样的产品。
— 类图在此 —

[AttributeUsage(AttributeTargets.Class)]
public class InstructionAttribute : Attribute { }

[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
public class BuildStepAttribute : Attribute, IComparable<BuildStepAttribute>
{
    public int Sequence { get; private set; }
    public int Times { get; private set; }

    public BuildStepAttribute(int seuqence, int times = 1)
    {
        Sequence = seuqence;
        Times = times;
    }

    public int CompareTo(object target)
    {
        if (target == null || !(target is BuildStepAttribute))
            throw new ArgumentException("target");

        return this.CompareTo(target as BuildStepAttribute);
    }

    public int CompareTo(BuildStepAttribute target)
    {
        if (target == null)
            throw new ArgumentException("target");

        return this.Sequence - target.Sequence;
    }
}

[Instruction]
public class SUVBuilder : BuilderBase
{
    [BuildStep(1)]
    public override void BuildBody()
    {
        base.BuildBody();
        _Car.Body = new SUVBody();
    }

    [BuildStep(3)]
    public override void BuildEngine()
    {
        base.BuildEngine();
        _Car.Engine = new SUVEngine();
    }

    [BuildStep(2)]
    public override void BuildWheel()
    {
        base.BuildWheel();
        _Car.Wheel = new SUVWheel();
    }
}

public class Director<T>
    where T : class
{
    public void BuildUpByDefine(T builder)
    {
        if (builder == null) return;

        IEnumerable<MethodInfo> methods = this.GetInterfaceMethods(builder);

        Type builderType = builder.GetType();
        InstructionAttribute iAttribute = builderType.GetCustomAttribute<InstructionAttribute>();

        if (iAttribute != null)
            methods = this.SortMethodByStep(methods);

        foreach (MethodInfo m in methods)
            m.Invoke(builder, null);
    }

    private IEnumerable<MethodInfo> GetInterfaceMethods(T builder)
    {
        List<MethodInfo> methods = new List<MethodInfo>();

        Type builderType = builder.GetType();
        MethodInfo[] typeMethods = builderType.GetMethods(BindingFlags.Instance | BindingFlags.Public);

        Type interfaceType = typeof(T);
        MethodInfo[] interfaceMethods = interfaceType.GetMethods();

        foreach (MethodInfo im in interfaceMethods)
        {
            MethodInfo fm = typeMethods.First(m => m.Name == im.Name);
            if (fm == null) continue;

            methods.Add(fm);
        }

        return methods;
    }

    private IEnumerable<MethodInfo> SortMethodByStep(IEnumerable<MethodInfo> methods)
    {
        if (methods.Count() <= 0) return methods;

        Dictionary<BuildStepAttribute, MethodInfo> executeMethods =
            new Dictionary<BuildStepAttribute, MethodInfo>();

        foreach (MethodInfo m in methods)
        {
            BuildStepAttribute attribute = m.GetCustomAttribute<BuildStepAttribute>();
            if (attribute == null) continue;

            executeMethods.Add(attribute, m);
        }

        if (executeMethods.Count <= 0) return new List<MethodInfo>();

        return executeMethods.OrderBy(p => p.Key).Select(p => p.Value);
    }

    public virtual void TearDown() { }
}

Unit Test:

        [TestMethod]
        public void Test_GenericDirector()
        {
            SUVBuilder builder = new SUVBuilder();

            Director<IBuilder> director = new Director<IBuilder>();
            director.BuildUpByDefine(builder);

            Car car = builder.GetProduct();
            Assert.IsNotNull(car);

            Assert.AreEqual("SUV", car.Engine.Name);
            Assert.AreEqual(60, car.Engine.Power);

            Assert.AreEqual("SUV", car.Body.Name);
            Assert.AreEqual(30, car.Body.Size);

            Assert.AreEqual("SUV", car.Wheel.Name);
            Assert.AreEqual(30, car.Wheel.Size);

            Assert.AreEqual(3, car.StepSequence.Count);
            Assert.AreEqual("Body", car.StepSequence[0]);
            Assert.AreEqual("Wheel", car.StepSequence[1]);
            Assert.AreEqual("Engine", car.StepSequence[2]);
        }

        [TestMethod]
        public void Test_GenericDirector_2()
        {
            CarBuilder builder = new CarBuilder();

            Director<IBuilder> director = new Director<IBuilder>();
            director.BuildUpByDefine(builder);

            Car car = builder.GetProduct();
            Assert.IsNotNull(car);

            Assert.AreEqual("Normal", car.Engine.Name);
            Assert.AreEqual(27, car.Engine.Power);

            Assert.AreEqual("Normal", car.Body.Name);
            Assert.AreEqual(27, car.Body.Size);

            Assert.AreEqual("Normal", car.Wheel.Name);
            Assert.AreEqual(27, car.Wheel.Size);

            Assert.AreEqual(3, car.StepSequence.Count);
            Assert.AreEqual("Body", car.StepSequence[0]);
            Assert.AreEqual("Engine", car.StepSequence[1]);
            Assert.AreEqual("Wheel", car.StepSequence[2]);
        }

这个通用的Director可以继续扩展,做出更多的定制。

public class CarDirector : Director<IBuilder>
{
    public void BuildUp(IBuilder builder)
    {
        builder.BuildWheel();
        builder.BuildEngine();
        builder.BuildBody();
    }
}

Unit Test:

[TestMethod]
public void Test_CarDirector()
{
    // BuildUpByDefine
    SUVBuilder builder = new SUVBuilder();
    CarDirector director = new CarDirector();
    director.BuildUpByDefine(builder);

    Car car = builder.GetProduct();
    Assert.IsNotNull(car);

    Assert.AreEqual("SUV", car.Engine.Name);
    Assert.AreEqual(60, car.Engine.Power);

    Assert.AreEqual("SUV", car.Body.Name);
    Assert.AreEqual(30, car.Body.Size);

    Assert.AreEqual("SUV", car.Wheel.Name);
    Assert.AreEqual(30, car.Wheel.Size);

    Assert.AreEqual(3, car.StepSequence.Count);
    Assert.AreEqual("Body", car.StepSequence[0]);
    Assert.AreEqual("Wheel", car.StepSequence[1]);
    Assert.AreEqual("Engine", car.StepSequence[2]);

    // BuildUp
    builder = new SUVBuilder();
    director = new CarDirector();
    director.BuildUp(builder);

    car = builder.GetProduct();
    Assert.IsNotNull(car);

    Assert.AreEqual("SUV", car.Engine.Name);
    Assert.AreEqual(60, car.Engine.Power);

    Assert.AreEqual("SUV", car.Body.Name);
    Assert.AreEqual(30, car.Body.Size);

    Assert.AreEqual("SUV", car.Wheel.Name);
    Assert.AreEqual(30, car.Wheel.Size);

    Assert.AreEqual(3, car.StepSequence.Count);
    Assert.AreEqual("Wheel", car.StepSequence[0]);
    Assert.AreEqual("Engine", car.StepSequence[1]);
    Assert.AreEqual("Body", car.StepSequence[2]);
}

思考:
从整个使用过程来看,应用程序需要指定Concrete Builder,这点与创建型模式延迟实例化的思路是不一致的。可以考虑将工厂模式、抽象工厂模式与生成器模式结合使用。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值