【程序员内功】Java设计模式并不难(四)---建造者模式

Builder Pattern

1、概述

建造者解决的业务场景是:创建复杂对象,对象中可能包含了许多非简单类型属性,并且这样的对象具有重复性,每个对象的属性大致相同,只是其中具体的属性值不一样。如:游戏中人物模型,游戏人物都有脸型、肤色、服装、发型、装饰等属性,这些属性对于人物模型来说其实并非一个String能解决的,而且每种人物角色的属性值都不一样,这就需要使用建造者模式。

2、组成
  • Product(产品角色):需要创建的对象,一般属性都比较复杂。

    class Product  {
           private  String partA; //定义部件,部件可以是任意类型,包括值类型和引用类型
           private  String partB;
           private  String partC;
           //partA的Getter方法和Setter方法省略
           //partB的Getter方法和Setter方法省略
           //partC的Getter方法和Setter方法省略
    }
    
  • Builder(抽象建造者):主要包含对Product中各部分属性的组建,还有一个返回Product的方法。

    abstract class Builder {
         //创建产品对象
           protected  Product product=new Product();
           public  abstract void buildPartA();
           public  abstract void buildPartB();
           public  abstract void buildPartC();
         //返回产品对象
           public  Product getResult() {
                  return  product;
           }
    }
    
  • ConcreteBuilder(具体建造者):抽象建造者的实现,具体某种对象属性的生成。

  • Director(指挥者):调用Builder中属性生成方法,决定对象的生成顺序及方式。

    class Director {
           private  Builder builder;
           public  Director(Builder builder) {
                  this.builder=builder;
           }
           public  void setBuilder(Builder builder) {
                  this.builder=builer;
           }
         //产品构建与组装方法
           public Product construct() {
                  builder.buildPartA();
                  builder.buildPartB();
                  builder.buildPartC();
                  return builder.getResult();
           }
    }
    
3、实战

我们现在使用上述思路将概述中的游戏角色用建造者实现,为了方便阅读,产品角色属性使用String类型代替。

Product

首先定义我们的复杂角色对象

class Actor
{
       private  String type; //角色类型
       private  String sex; //性别
       private  String face; //脸型
       private  String costume; //服装
       private  String hairstyle; //发型
       public  void setType(String type) {
              this.type  = type;
       }
       public  void setSex(String sex) {
              this.sex  = sex;
       }
       ……
}
Builder

提供一个对象属性抽象构造类,其中包含对于每一个复杂属性的build方法和返回产品角色的方法。

注:这里使用抽象类没有使用接口,是因为其中需要包含一个有具体实现的方法:构造器createActor()。

//角色建造器:抽象建造者
abstract class ActorBuilder
{
       protected  Actor actor = new Actor();
       public  abstract void buildType();
       public  abstract void buildSex();
       public  abstract void buildFace();
       public  abstract void buildCostume();
       public  abstract void buildHairstyle();
    //工厂方法,返回一个完整的游戏角色对象
       public Actor createActor()
       {
              return actor;
       }
}
ConcreteBuilder

根据不同的游戏角色类型实现该抽象类

//英雄角色建造器:具体建造者
class HeroBuilder extends ActorBuilder
{
       public  void buildType()
       {
              actor.setType("英雄");
       }
       public  void buildSex()
       {
              actor.setSex("男");
       }
       public  void buildFace()
       {
              actor.setFace("英俊");
       }
       public  void buildCostume()
       {
              actor.setCostume("盔甲");
       }
       public  void buildHairstyle()
       {
              actor.setHairstyle("飘逸");
       }    
}
//恶魔角色建造器:具体建造者
class DevilBuilder extends ActorBuilder
{
       public  void buildType()
       {
              actor.setType("恶魔");
       }
       public  void buildSex()
       {
              actor.setSex("妖");
       }
       public  void buildFace()
       {
              actor.setFace("丑陋");
       }
       public  void buildCostume()
       {
              actor.setCostume("黑衣");
       }
       public  void buildHairstyle()
       {
              actor.setHairstyle("光头");
       }    
}
Director

提供一个控制器,以此控制角色属性生成的顺序

//游戏角色创建控制器:指挥者
class ActorController
{
    //逐步构建复杂产品对象
       public Actor construct(ActorBuilder ab)
       {
              Actor actor;
              ab.buildType();
              ab.buildSex();
              ab.buildFace();
              ab.buildCostume();
              ab.buildHairstyle();
              actor=ab.createActor();
              return actor;
       }
}

考虑到代码的灵活性和可扩展性,我们将具体建造者的类目放在配置文件中,这样通过修改配置文件便可简单变更产品对象。如果需要增加新角色,可以增加一个新的具体角色建造者类作为抽象角色建造者的子类,再修改配置文件即可,原有代码无须修改,完全符合“开闭原则”。

import javax.xml.parsers.*;
import org.w3c.dom.*;
import org.xml.sax.SAXException;
import java.io.*;
class XMLUtil
{
//该方法用于从XML配置文件中提取具体类类名,并返回一个实例对象
       public  static Object getBean()
       {
              try
              {
                     //创建文档对象
                     DocumentBuilderFactory  dFactory = DocumentBuilderFactory.newInstance();
                     DocumentBuilder  builder = dFactory.newDocumentBuilder();
                     Document  doc;                                                
                     doc  = builder.parse(new File("config.xml"));
                     //获取包含类名的文本节点
                     NodeList  nl = doc.getElementsByTagName("className");
            Node  classNode=nl.item(0).getFirstChild();
            String  cName=classNode.getNodeValue();
            //通过类名生成实例对象并将其返回
            Class c=Class.forName(cName);
                 Object obj=c.newInstance();
            return obj;
         }  
         catch(Exception e)
         {
              e.printStackTrace();
              return null;
          }
       }
}

配置文件如下

<?xml version="1.0"?>
<config>
       <className>AngelBuilder</className>
</config>   

这样我们的游戏角色建造者模式就开发好了,我们可以书写测试类测试

class Client
{
       public  static void main(String args[])
       {
              ActorBuilder ab; //针对抽象建造者编程
              ab =  (ActorBuilder)XMLUtil.getBean(); //反射生成具体建造者对象
         ActorController ac = new  ActorController();
              Actor actor;
              actor = ac.construct(ab); //通过指挥者创建完整的建造者对象
              String  type = actor.getType();
              System.out.println(type  + "的外观:");
              System.out.println("性别:" + actor.getSex());
              System.out.println("面容:" + actor.getFace());
              System.out.println("服装:" + actor.getCostume());
              System.out.println("发型:" + actor.getHairstyle());
       }
}

结果为:

天使的外观:
性别:女
面容:漂亮
服装:白裙
发型:披肩长发
4、优化

有的同学可能会问,Director是不是必须的,答案当然是否定的,引入Director符合"单一职责原则",Product只负责对象是什么样子,Builder专注于对象各组成部分细节上如何构建,而Director管理的是几个组成部分之间如何组合。如果业务需求不十分复杂并且以后新增的情况也较少,我们完全可以把Director抽离出来,将此部分功能放到Builder里去做。

abstract class ActorBuilder
{
       protected static Actor actor = new  Actor();
       public  abstract void buildType();
       public  abstract void buildSex();
       public  abstract void buildFace();
       public  abstract void buildCostume();
       public  abstract void buildHairstyle();
       public static Actor  construct(ActorBuilder ab)
       {
              ab.buildType();
              ab.buildSex();
              ab.buildFace();
              ab.buildCostume();
              ab.buildHairstyle();
              return actor;
       }
}

甚至说我们不用指定construct的入参,直接使用this指代更加简单

abstract class ActorBuilder
{
       protected  Actor actor = new Actor();
       public  abstract void buildType();
       public  abstract void buildSex();
       public  abstract void buildFace();
       public  abstract void buildCostume();
       public  abstract void buildHairstyle();
       public Actor construct()
       {
              this.buildType();
              this.buildSex();
              this.buildFace();
              this.buildCostume();
              this.buildHairstyle();
              return actor;
       }
}
5、精确控制

对于上述方法我们已经实现了不同角色之间没有耦合,新增、修改角色对原系统也无影响,已经符合建造者模式,但如果我们还想进一步精确控制对象各部分是否创建,可以引入HookMethod(钩子方法)确定某个属性是否创建。

直接在抽象建造者中添加该方法,如果子类产品对象中有特殊需求覆盖即可。

isBareheaded()方法用于判断该角色是否是"光头",默认为false;

abstract class ActorBuilder
{
       protected  Actor actor = new Actor();
       public  abstract void buildType();
       public  abstract void buildSex();
       public  abstract void buildFace();
       public  abstract void buildCostume();
       public  abstract void buildHairstyle();
       //钩子方法
public boolean isBareheaded()
       {
              return false;
       }
       public  Actor createActor()
       {
              return  actor;
       }
}

我们的恶魔角色很明显是光头,所以需要覆盖该方法。

class DevilBuilder extends ActorBuilder
{
       public  void buildType()
       {
              actor.setType("恶魔");
       }
       public  void buildSex()
       {
              actor.setSex("妖");
       }
       public  void buildFace()
       {
              actor.setFace("丑陋");
       }
       public  void buildCostume()
       {
              actor.setCostume("黑衣");
       }
       public  void buildHairstyle()
       {
              actor.setHairstyle("光头");
       }
     //覆盖钩子方法
       public boolean isBareheaded()
       {
              return true;
       }     
}

这样我们的指挥着也需要进行些微改动

class ActorController
{
       public  Actor construct(ActorBuilder ab)
       {
              Actor  actor;
              ab.buildType();
              ab.buildSex();
              ab.buildFace();
              ab.buildCostume();
         //通过钩子方法来控制产品的构建
              if(!ab.isBareheaded())
              {
                     ab. buildHairstyle();
              }
              actor=ab.createActor();
              return  actor;
       }
}
6、总结

其实建造者模式和抽象工厂模式有些类似,但是建造者模式关注的是复杂对象的创建,而抽象工厂则是返回一系列产品。抽象工厂模式只能生产特定的运动器材,如Adidas只能生产Adidas的球衣、球鞋,Nike只能生产Nike的球衣、球鞋,而建造者模式就好比服装厂商,我可以让服装产商定制一套队服,包括球衣(大小)、球鞋(尺码)、球袜等。参考

建造者模式所有函数加到一起才能生成一个对象,抽象工厂一个函数就能生成一个对象

①优点
  • 客户端与产品的创建过程解耦,使用相同的创建过程即可创建不同的产品对象。
  • 每个对象相互独立、互不影响,变更对象只需新增实现、修改配置文件即可,符合"开闭原则"。
  • 更加精细化的控制产品创建,控制粒度更细。
②缺点
  • 建造者模式的产品属性基本一致,只是属性间差异较大,如果某个对象大部分属性都不相同,则不适合建造者模式。
  • 如果业务需求复杂庞大,可能需要定义N多个具体建造者类,将增加系统的难度及运维成本。
③适用场景
  • 产品对象内部结构复杂包含多个成员属性。
  • 产品对象属性互相依赖,需要指定其生成顺序。
  • 需要使用相同创建过程创建不同产品。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值