设计模式——建造者模式
本片博文通过尚硅谷韩老师《设计模式》课程所做,再次非常感谢!
基本介绍
- 建造者模式(Builder Pattern) 又叫生成器模式,是一种对象构建模式。它可以将复杂对象的建造过程抽象出来(抽象类别),使这个抽象过程的不同实现方法可以构造出不同表现(属性)的对象。
- 建造者模式 是一步一步创建一个复杂的对象,它允许用户只通过指定复杂对象的类型和内容就可以构建它们, 用户不需要知道内部的具体构建细节。
建造者模式的四个核心角色
- Product(产品角色): 一个具体的产品对象。
- Builder(抽象建造者): 创建一个 Product 对象的各个部件指定的 接口/抽象类,只需要指定建造产品的流程,建造的具体细节交给具体建造者来实现。
- ConcreteBuilder(具体建造者): 实现接口/抽象类的抽象方法,构建和装配各个部件。
- Director(指挥者): 构建一个使用 Builder 接口/抽象类的对象。它主要是用于创建一个复杂的对象,它主要有两个作用,一是:隔离了客户与对象的生产过程,二是:负责控制产品对象的生产过程。
细节和注意事项
- 客户端(使用程序)不必知道产品内部组成的细节,将产品本身与产品的创建过程解耦,使得相同的创建过程可以创建不同的产品对象;
- 每一个具体建造者都相对独立,而与其他的具体建造者无关,因此可以很方便地替换具体建造者或增加新的具体建造者, 用户使用不同的具体建造者即可得到不同的产品对象;
- 可以更加精细地控制产品的创建过程 。将复杂产品的创建步骤分解在不同的方法中,使得创建过程更加清晰, 也更方便使用程序来控制创建过程;
- 增加新的具体建造者无须修改原有类库的代码,指挥者类针对抽象建造者类编程,系统扩展方便,符合“开闭原则”;
- 建造者模式所创建的产品一般具有较多的共同点,其组成部分相似,如果产品之间的差异性很大,则不适合使用建造者模式,因此其使用范围受到一定的限制;
- 如果产品的内部变化复杂,可能会导致需要定义很多具体建造者类来实现这种变化,导致系统变得很庞大,因此在这种情况下,要考虑是否选择建造者模式;
是否感觉建造者模式与抽象工厂模式有点像?它们之间的异同如下
- 抽象工厂模式实现对产品家族的创建,一个产品家族是这样的一系列产品:具有不同分类维度的产品组合,采用抽象工厂模式不需要关心构建过程,只关心什么产品由什么工厂生产即可。
- 而建造者模式则是要求按照指定的蓝图建造产品,它的主要目的是通过组装零配件而产生一个新产品;
UML
建造者UML图如下
问题引入
我们现在需要完成如下的需求:
- 需要建房子:这一过程为选址、打桩、砌墙、封顶;
- 房子有各种各样的,比如普通房,高楼,别墅、海景房等,各种房子的建造过程一样,但是每个过程的具体细节是不相同的;
- 请编写程序,完成需求
分析:我们第一反应大部分都是定义一个基类,将建造的过程抽象为一个一个的方法,然后在基类中通过一个建造方法按照指定顺序调用即可完成建造
;要建造什么房子就先去继承该基类,并将其抽象方法进行实现,外部直接调用该基类的建造方法即可完成房子的构建;
如上的思路分析,优点是比较好理解,简单易操作。但是,设计的程序结构,过于简单,没有设计缓存层对象,程序的扩展和维护不好,把产品(即:房子) 和 创建产品的过程(即:建房子流程) 封装在一起,耦合性增强了。
那么我们使用建造者模式来解决这个问题;
建造者模式及其编码
首先我们应该建造者建造的产品(即房子)类创建出来,如下House类
:
package edu.hebeu.builder.product;
/**
* 房子类,相当于建造者模式的生成的产品
* @author 13651
*
*/
public class House {
private String baise;
private String wall;
private String roofed;
private String type;
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getBaise() {
return baise;
}
public void setBaise(String baise) {
this.baise = baise;
}
public String getWall() {
return wall;
}
public void setWall(String wall) {
this.wall = wall;
}
public String getRoofed() {
return roofed;
}
public void setRoofed(String roofed) {
this.roofed = roofed;
}
@Override
public String toString() {
return "House [baise=" + baise + ", wall=" + wall + ", roofed=" + roofed + ", type=" + type + "]";
}
}
为了能够更好的建地程序的耦合性和扩展,我们应该抽取出一个建造者基类HouseBuilder
,并将建造的产品类聚合进入(以后只要有建造者,就让它们去继承该方法,然后就可以直接操作基类中的产品类,此时每个建造者与产品类之间就没有关系了,而是产品类聚合建造者基类,程序的耦合度大大削减
),代码如下:
package edu.hebeu.builder.builder;
import edu.hebeu.builder.product.House;
/**
* 该类是所有建造者的基类
* @author 13651
*
*/
public abstract class HouseBuilder {
protected House house = new House();
/**
* 该方法用来给建造的房子选地
*/
public abstract void selectSite();
/**
* 这个方法是用来进行打地基的
*/
public abstract void piling();
/**
* 这个方法用来砌墙
*/
public abstract void buildWall();
/**
* 这个方法用来封顶
*/
public abstract void capping();
/**
* 该方法用来将建造的房子返回
* @return
*/
public abstract House buildHouse();
}
我们上面的第一个分析被淘汰的原因就是因为建造的过程和建造的产品在一起,耦合太高了,那么我们此处应该将建造的过程抽离出去,以削减程序的耦合,因此我们此处创建一个指挥者HouseDirector
用来按照指定的顺序调用抽象建造者的方法
,此时每个建造者也无需关心建造的步骤,只需要将建造者基类中的抽象方法实现即可,程序的耦合度因此削减
,指挥者HouseDirector
代码如下:
package edu.hebeu.builder.director;
import edu.hebeu.builder.builder.HouseBuilder;
import edu.hebeu.builder.product.House;
/**
* 指挥者,指挥房子的建造,无需关注每一步的细节
* @author 13651
*
*/
public class HouseDirector {
private HouseBuilder builder;
public HouseDirector() {
// TODO Auto-generated constructor stub
}
/**
* 通过构造器传入要建造的房子类型
* @param builder
*/
public HouseDirector(HouseBuilder builder) {
this.builder = builder;
}
/**
* 通过setter传入要建造的类型
* @param builder
*/
public void setBuilder(HouseBuilder builder) {
this.builder = builder;
}
public House startBuild() {
System.out.println("-------指挥者:选址阶段--------");
builder.selectSite();
System.out.println("-------指挥者:选址结束,打地基阶段开始--------");
builder.piling();
System.out.println("-------指挥者:地基阶段完成,砌墙阶段开始--------");
builder.buildWall();
System.out.println("-------指挥者:砌墙阶段完成,封顶阶段开始--------");
builder.capping();
System.out.println("-------指挥者:封顶阶段完成,建造成功!---------");
return builder.buildHouse();
}
}
此时程序的主体骨架就已经搭建完毕了,我们想要建什么房子,只需要创建什么建造者,并将该建造者继承基类建造者HouseBuilder即可
,我们此处创建普通房子建造者CommonHouseBuilder
、高房子建造者HighHouseBuilder
、海景房建造者SeaviewHouseBuilder
,如下:
package edu.hebeu.builder.builder;
import edu.hebeu.builder.product.House;
/**
* 该类是建造普通房子的建造者
* @author 13651
*
*/
public class CommonHouseBuilder extends HouseBuilder{
@Override
public void selectSite() {
System.out.println("建造普通房子,选择地点:郊区");
house.setType("普通房子");
}
@Override
public void piling() {
System.out.println("准备1吨沙石和4吨水");
System.out.println("准备20000块砖");
house.setBaise("面积100平米、深度20米的地基");
}
@Override
public void buildWall() {
System.out.println("准备2吨沙石和4吨水");
System.out.println("准备80000块砖");
house.setWall("20米高、0.4米厚的墙");
}
@Override
public void capping() {
System.out.println("准备1.5吨沙石和3吨水");
System.out.println("准备24000块砖");
house.setRoofed("面积100平米、厚度0.3米的房顶");
}
@Override
public House buildHouse() {
System.out.println("普通房建造并交付成功!");
return house;
}
}
package edu.hebeu.builder.builder;
import edu.hebeu.builder.product.House;
/**
* 该类是建造高房子的建造者
* @author 13651
*
*/
public class HighHouseBuilder extends HouseBuilder {
@Override
public void selectSite() {
System.out.println("建造高房子,选择地点:城区");
house.setType("高房子");
}
@Override
public void piling() {
System.out.println("准备3000吨沙石和8000吨水");
System.out.println("准备400000块砖");
house.setBaise("面积1000平米、深度100米的地基");
}
@Override
public void buildWall() {
System.out.println("准备8000吨沙石和10000吨水");
System.out.println("准备800000块砖");
house.setWall("800米高、1米厚的墙");
}
@Override
public void capping() {
System.out.println("准备4000吨沙石和8000吨水");
System.out.println("准备600000块砖");
house.setRoofed("面积1000平米、厚度0.5米的房顶");
}
@Override
public House buildHouse() {
System.out.println("高房子建造并交付成功!");
return house;
}
}
package edu.hebeu.builder.builder;
import edu.hebeu.builder.product.House;
/**
* 该类是用来建造海景房的建造者
* @author 13651
*
*/
public class SeaviewHouseBuilder extends HouseBuilder{
@Override
public void selectSite() {
System.out.println("建造海景房,选择地点:海边");
house.setType("海景房");
}
@Override
public void piling() {
System.out.println("准备3000吨沙石和2000000吨水");
System.out.println("准备380000块砖");
house.setBaise("面积800平米、深度120米的地基");
}
@Override
public void buildWall() {
System.out.println("准备6000吨沙石和2200000吨水");
System.out.println("准备600000块砖");
house.setWall("30米高、0.4米厚的墙");
}
@Override
public void capping() {
System.out.println("准备3000吨沙石和6000吨水");
System.out.println("准备4500000块砖");
house.setRoofed("面积800平米、厚度0.5米的房顶");
}
@Override
public House buildHouse() {
System.out.println("海景房建造并交付成功!");
return house;
}
}
至此,整个建造者模式就完毕了,我们编写一个测试类来测试一下,如下:
package edu.hebeu.builder;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Scanner;
import java.util.Set;
import edu.hebeu.builder.builder.CommonHouseBuilder;
import edu.hebeu.builder.builder.HighHouseBuilder;
import edu.hebeu.builder.builder.SeaviewHouseBuilder;
import edu.hebeu.builder.director.HouseDirector;
import edu.hebeu.builder.product.House;
/**
*
* @author 13651
*
*/
public class Client {
private static HouseDirector DIRECTOR = new HouseDirector();
private static Scanner SCANNER = new Scanner(System.in);
private static boolean IS_CONTINUE = true;
private static Map<String, House> HOUSES = new HashMap<>(); // 存放建造好的房子
private static int HOUSE_COUNT = 0; // 用来表示当前是第几个房子,用来给房子编号使用
public static void main(String[] args) {
while(true) {
System.out.println("查看我的房子(s)");
System.out.println("建造房子(b)");
System.out.println("建退出程序(e)");
String keyword = SCANNER.next();
switch(keyword) {
case "s":
System.out.println("----------------我的房子---------------");
if(HOUSES.size() == 0) {
System.out.println("你没有建造任何房子");
System.out.println();
}
Set<Entry<String, House>> houseSet = HOUSES.entrySet();
for(Entry<String, House> myHouse : houseSet) {
System.out.println("房子序号:" + myHouse.getKey());
System.out.println("----------------房子信息---------------");
System.out.println(myHouse.getValue());
System.out.println();
}
break;
case "b":
toBuildHouse();
break;
case "e":
System.out.println("bye~~~");
break;
default:
System.exit(0);
}
}
}
private static void toBuildHouse() {
while(true) {
IS_CONTINUE = true;
System.out.println();System.out.println();
System.out.println("建造普通房子(c)");
System.out.println("建造高房子(h)");
System.out.println("建造海景房(s)");
System.out.println("退出(e)");
String keyword = SCANNER.next();
switch(keyword) {
case "c":
DIRECTOR.setBuilder(new CommonHouseBuilder());
break;
case "h":
DIRECTOR.setBuilder(new HighHouseBuilder());
break;
case "s":
DIRECTOR.setBuilder(new SeaviewHouseBuilder());
break;
case "e":
IS_CONTINUE = false;
break;
default:
break;
}
if(!IS_CONTINUE) {
break;
}
House house = DIRECTOR.startBuild(); // 建造房子
HOUSES.put("house" + ++HOUSE_COUNT, house); // 将建造好的房子加入的集合中存放起来
}
}
}
测试