zoukankan      html  css  js  c++  java
  • 设计模式之建造者模式——Builder

    一、概述

    Builder模式,中文名为建造者模式,又名生成器模式、构建者模式等,是创建型设计模式之一。用于将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。

    1.适用性:

    • 对象的创建比较复杂、有多种创建形式时
    • 创建复杂对象的算法与对象内部组成和装配是相对独立的

    2.UML类图

    • Builder:定义创建Product各个部件的抽象接口
    • ConcreteBuilder:继承自Builder,实现创建具体类型的Product所有部件的接口,并提供一个获取最终产品的接口。
    • Director:借助Builder,内部封装创建产品的具体流程。

    3.UML序列图

    1. Client创建一个ConcreteBuilder
    2. Client通过传入ConcreteBuilder创建一个Director
    3. Client使用Director构造产品
    4. Client使用ConcreteBuilder组装并获取最终产品

    二、实例

    想象一下,你作为造物主,你需要创造各种各样的动物,比如狗和猫。查看源码

    1.在创造前,要先想好狗和猫大体是什么样子,很显然,它们应该有头、身体和脚。

    abstract class Animal
    {
        public string Head { get; set; }
        public string Body { get; set; }
        public string Foots { get; set; }
        
        public abstract void Display();
    }
    
    class Dog : Animal
    {
        public override void Display()
        {
            Console.WriteLine("-------------- 狗的基本信息 -------------------");
            Console.WriteLine($"头:{Head}");
            Console.WriteLine($"身体:{Body}");
            Console.WriteLine($"脚:{Foots}");
        }
    }
    
    class Cat : Animal
    {
        public override void Display()
        {
            Console.WriteLine("-------------- 猫的基本信息 -------------------");
            Console.WriteLine($"头:{Head}");
            Console.WriteLine($"身体:{Body}");
            Console.WriteLine($"脚:{Foots}");
        }
    }
    

    2.动物的基本特征确定了,也就明确了要干什么活儿了——创建动物的头、身体和脚。

    interface IAnimalBuilder
    {
        void SetHead();
        void SetBody();
        void SetFoots();
    }
    

    3.接下来就是考虑狗和猫各部位的创建细节了。不过,别着急编码,先来考虑一件事情:
      DogBuilder里面除了需要实现IAnimalBuilder的所有接口外,还需要提供一个GetDog()的方法,用来把创建好的Dog给外部;同样的CatBuilder中也需要提供一个GetCat()方法。这世间动物有多少种?一千?一万?远远不止!在编码的过程中很容易就忘记提供这个方法了。所以我们在抽象Builder和具体Builder中间插入一个抽象层IAnimalBuilder<TAnimal>,所有的ConcreteBuilder都继承自这个接口。

    interface IAnimalBuilder<TAnimal> : IAnimalBuilder where TAnimal : Animal 
    {
        TAnimal GetAnimal();
    }
    
    class DogBuilder : IAnimalBuilder<Dog>
    {
        private readonly Dog _dog = new Dog();
    
        public void SetHead()
        {
            _dog.Head = "狗的头";
        }
    
        public void SetBody()
        {
            _dog.Body = "狗的身体";
        }
    
        public void SetFoots()
        {
            _dog.Foots = "狗的脚";
        }
    
        public Dog GetAnimal()
        {
            return _dog;
        }
    }
    
    class CatBuilder : IAnimalBuilder<Cat>
    {
        private readonly Cat _cat = new Cat();
    
        public void SetHead()
        {
            _cat.Head = "猫的头";
        }
    
        public void SetBody()
        {
            _cat.Body = "猫的身体";
        }
    
        public void SetFoots()
        {
            _cat.Foots = "猫的脚";
        }
    
        public Cat GetAnimal()
        {
            return _cat;
        }
    }
    

    4.最后,只剩Director了,它来规划Builder要创建哪些东西。

    class Director
    {
        private readonly IAnimalBuilder _builder;
    
        public Director(IAnimalBuilder builder)
        {
            _builder = builder;
        }
    
        public void Construct()
        {
            _builder.SetHead();
            _builder.SetBody();
            _builder.SetFoots();
        }
    }
    

    大功告成!接下来就尝试一下吧

    var dogBuilder = new DogBuilder();
    var dogDirector = new Director(dogBuilder);
    dogDirector.Construct();
    dogBuilder.GetAnimal().Display();
    
    var catBuilder = new CatBuilder();
    var catDirector = new Director(catBuilder);
    catDirector.Construct();
    catBuilder.GetAnimal().Display();
    

    三、变种

    实际开发过程中,经常会遇到一种需求:创建对象时,对象的一些部分可以自由选择设置或不设置,但是对象一旦创建完成,便不能对其进行任何修改。例如:ASP.NET Core框架中WebHost的创建。接下来,我们来模拟一下女娲造人。
    1.同样的,我们先设定好人的基本属性

    class Person
    {
        public string Name { get; set; }
        public int Gender { get; set; }
        public int Age { get; set; }
    }
    

    2.接下来,我们来定义IPersonBuilder接口,除了Build方法外,我们都返回了IPersonBuilder,对于习惯于使用Linq的.Net开发人员来说,再熟悉不过了,它最大的好处就是能够让我们使用Fluent的方式来编写代码。

    public interface IPersonBuilder
    {
        //注意,这里有问题
        Person Build();
        IPersonBuilder SetName(string name);
        IPersonBuilder SetGender(int gender);
        IPersonBuilder SetAge(int age);
    }
    

    你发现什么问题了吗?没错,这违背了面向对象五大原则之一——依赖倒置(抽象不应该依赖具体);而且,这也不满足“对象创建后不得进行更改”的需求。所以,我们需要提取出一个抽象层IPersion

    public interface IPerson
    {
        string Name { get; }
        int Gender { get; }
        int Age { get; }
    }
    
    class Person : IPerson
    {
        public string Name { get; set; }
        public int Gender { get; set; }
        public int Age { get; set; }
    }
    
    public interface IPersonBuilder
    {
        IPerson Build();
        IPersonBuilder SetName(string name);
        IPersonBuilder SetGender(int gender);
        IPersonBuilder SetAge(int age);
    }
    

    3.下一步就是构建PersonBuilder了,用来实现IPersonBuilder接口的具体逻辑。

    class PersonBuilder : IPersonBuilder
    {
        private readonly Person _person = new Person();
    
        public IPerson Build()
        {
            return _person;
        }
    
        public IPersonBuilder SetName(string name)
        {
            _person.Name = name;
            return this;
        }
    
        public IPersonBuilder SetGender(int gender)
        {
            _person.Gender = gender;
            return this;
        }
    
        public IPersonBuilder SetAge(int age)
        {
            _person.Age = age;
            return this;
        }      
    }
    
    //提供一个辅助类,用来帮 Client 创建 PersonBuilder
    public class PersonBuilderHelper
    {
        public static IPersonBuilder CreatePersonBuilder()
        {
            return new PersonBuilder();
        }
    }
    

    4.Ok,大功告成!来测试一下吧

    var person = PersonBuilderHelper.CreatePersonBuilder()
        .SetAge(20)
        .SetName("jjj")
        .Build();
    //输出:jjj,0,20
    Console.WriteLine($"{person.Name},{person.Gender},{person.Age}");
    

    四、总结

    经典版与变种版本的区别:

    • 经典版倾向于将构建规划封装起来,Client不关心内部逻辑;而变种版本消除了Director,使得Client拥有更多的主动权,可以自由地进行构建。
    • 经典版需要使用Director进行构建,然后使用Builder进行组装返回结果;变种版本则直接使用Builder进行链式构建并组装返回结果,结构更加清晰明了。
    • 经典版常用于多种产品的构建是比较固定的情况,Director种类也不宜过多,且必须适应所有产品;而变种版更倾向于某一种产品的构建,构建方式不固定、相当复杂的情况。

    查看源码

    如果有新发现会进行补充!

  • 相关阅读:
    args4 1.4.12 编写一个程序,有序打印给定的两个有序数组(含有N 个int 值)中的所有公共元素,程序在最坏情况下所需的运行时间应该和N 成正比。
    优化斐波那契数列递归的计算
    Java中BO、DAO、DO、DTO、PO、POJO、VO的概念
    并查集算法Union-Find的思想、实现以及应用
    计算机网络中一些比较重要的概念
    [转]架构初探之架构的几种设计模式
    常见排序算法的思想以及稳定性分析
    数据库基础知识整理与复习总结
    Java面试之Java集合相关问题答案口述整理
    Java面试之Java基础问题答案口述整理
  • 原文地址:https://www.cnblogs.com/xiaoxiaotank/p/11022816.html
Copyright © 2011-2022 走看看