深入设计模式06---建造者模式
前言
这里需要跟大家提一嘴,我们做程序猿最重要的就是手要勤快,多敲代码,最近看书的进度很快,但是发现不配合着代码一块理解很难学透,希望大家借鉴我的方法,先看书,看完之后把案例都敲一下,有助于我们理解。
建造者模式是一种复杂的创建型模式,它将客户端与包含多个部分的复杂对象创的创建过程分离,客户端无需知道复杂对象的内部组成以及装配方式,只需要知道所需的建造者类型即可。建造者模式关注如何一步步的创建一个复杂对象,不同的具体建造者定义了不同的创建过程,而且具体的建造者相互独立,更换建造者或者增加新的建造者非常方便面,系统具有较好的扩展性。
正文
-
概述
将一个复杂对象的构建与他的表示分离,使得同样的构建过程可以创建不同的表示。
建造者模式的设计思想如下:
无论是现实中还是系统中都会存在一些复杂的对象,他们拥有多个组成部分,例如汽车,汽车有方向盘、车身、车轮、发动机等等组成,用户如果想要使用一辆汽车不可能使用单独的部件,而是需要使用一台组装好的汽车,建造者模式的思想就是我们的客户无需关心汽车如何组装,将汽车的组装与使用分开。
建造者模式是一种对象创建型模式,它将客户端与包含多个部件的复杂对象创建过程分离,客户端不需要知道对象的组成部分以及装配方式,只需要知道建造者的类型即可,由建造者负责产生对象提供给客户端使用。 -
结构与实现
-
模式结构
- Builder(抽象建造者): 为创建一个产品的各个部件指定抽象接口,一般其中声明两类方法,一种是buildPartX()(用于创建产品的各个部件),一种是getResult(用于返回产品对象),Builder既可以是抽象类,也可以是接口。
- ConcreteBuild(具体建造者): 实现Builder中声明的各个部件的具体构造与装配方法,定义明确所创建的复杂对象,还可以提供一个方法返回所创建好的复杂对象(也可以由Builder抽象建造者实现)。
- Product(产品): 被构建的复杂对象,由建造者对其进行创建。
- Dircetor(指挥者): 指挥者与建造者之间存在关联关系,客户端一般只需要与指挥者产生关系,指挥者负责调用Builder中的装配方法装配对象,然后将构建好的复杂对象返回给客户端(调用方)。
-
实现案例
前文中提到了复杂对象,复杂对象是指那些包含多个成员变量的对象,这些成员变量也被称为部件或者零件。
案例说明:
某游戏软件公司要开发一款基于角色扮演的网络游戏,玩家可以在游戏中扮演虚拟世界的一个角色,随着游戏版本的升级将会不断地增加新的角色,分析发现游戏角色包性别、脸型等多个组成部分,不同的角色脸型、性别、发型、服饰等都会有所差异,例如:天使拥有美丽的外形和披肩长发,并穿着白裙,而恶魔则是光头,并穿一件刺眼的黑衣。无论哪种角色,他们的创建步骤大致相同,都是先创建其组成部分,然后装配成一个完整的角色,现用创建者模式来实现游戏角色的创建。
案例分析:
角色类Actor==》需要包含类型type、性别sex、脸型face、服装costume、发型hairstytle;
抽象的角色构造器ActorBuilder==》buildType,buildSex、buildFace,buildCostume,buildHairstytle抽象方法;
具体的实现类==》英雄HeroBuilder,天使AngelBuilder,恶魔DevilHuilder
目录结构:
角色类Actor(产品): 其中包含一些复杂的属性(这里使用String代替了复杂的对象)。public class Actor { private String type; private String sex; private String face; private String costume; private String hairstyle; public String getType() { return type; } public void setType(String type) { this.type = type; } public String getSex() { return sex; } public void setSex(String sex) { this.sex = sex; } public String getFace() { return face; } public void setFace(String face) { this.face = face; } public String getCostume() { return costume; } public void setCostume(String costume) { this.costume = costume; } public String getHairstyle() { return hairstyle; } public void setHairstyle(String hairstyle) { this.hairstyle = hairstyle; } @Override public String toString() { return "Actor{" + "type='" + type + '\'' + ", sex='" + sex + '\'' + ", face='" + face + '\'' + ", costume='" + costume + '\'' + ", hairstyle='" + hairstyle + '\'' + '}'; } }
ActorBuilder抽象建造者: 声明了装配角色的一些抽象方法,实现了一个用于返回复杂对象的方法。
public abstract class ActorBuilder { // protected修饰符代表只有子类可见(只有继承了该类的类可以直接使用),将复杂对象保护起来 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; }; }
具体建造者: 继承抽象建造者,实现了其中声明的装配Actor的方法,我们在这里简单给出三个具体建造者:HeroBuilder、AngelBuilder以及DevilBuilder。
// 构建英雄角色 public class HeroBuilder extends ActorBuilder { @Override public void buildType() { actor.setType("英雄"); } @Override public void buildSex() { actor.setSex("男"); } @Override public void buildFace() { actor.setFace("英俊"); } @Override public void buildCostume() { actor.setCostume("铠甲"); } @Override public void buildHairstyle() { actor.setHairstyle("飘逸"); } } // 构建天使角色 public class AngelBuilder extends ActorBuilder { @Override public void buildType() { actor.setType("天使"); } @Override public void buildSex() { actor.setSex("女"); } @Override public void buildFace() { actor.setFace("漂亮"); } @Override public void buildCostume() { actor.setCostume("白裙"); } @Override public void buildHairstyle() { actor.setHairstyle("披肩长发"); } } // 构建恶魔角色 public class DevilBuilder extends ActorBuilder { @Override public void buildType() { actor.setType("恶魔"); } @Override public void buildSex() { actor.setSex("妖"); } @Override public void buildFace() { actor.setFace("恐怖"); } @Override public void buildCostume() { actor.setCostume("黑斗篷"); } @Override public void buildHairstyle() { actor.setHairstyle("光头"); } }
ActorController指挥者: 逐步构建复杂对象并返回对象供客户端使用。
public class ActorController { // 逐步构建复杂的对象 public Actor construct(ActorBuilder builder){ Actor actor; builder.buildType(); builder.buildFace(); builder.buildHairstyle(); builder.buildCostume(); builder.buildSex(); actor = builder.createActor(); return actor; } }
config配置文件: 配置了具体的角色构建Builder(可以通过前端传入),代码中通过解析该配置文件获取具体建造者类的类名,然后通过反射获取具体建造者对象。
<?xml version="1.0" ?> <config> <className>com.xz.example.builder.AngelBuilder</className> </config>
XMLUtil: 用来解析具体的config配置文件。
public class XMLUtil { public static Object getBean(){ try { // 创建DOM文档对象 DocumentBuilderFactory dFactory = DocumentBuilderFactory.newInstance(); DocumentBuilder documentBuilder = dFactory.newDocumentBuilder(); Document doc = documentBuilder.parse(new File("BuilderPattern/src/com/xz/example/config.xml")); // 获取配置bean NodeList nl = doc.getElementsByTagName("className"); Node node = nl.item(0).getFirstChild(); String clazzName = node.getNodeValue(); // 利用反射生成对象并返回 Object obj = Class.forName(clazzName).newInstance(); return obj; } catch (Exception e) { e.printStackTrace(); return null; } } }
客户类: 消费(使用)角色。
public class Client { /** * 需求: * 某游戏软件公司要开发一款基于角色扮演的网络游戏,玩家可以在游戏中扮演虚拟世界的一个角色,随着游戏版本的升级将会不断地增加新的角色, * 分析发现游戏角色包性别、脸型等多个组成部分,不同的角色脸型、性别、发型、服饰等都会有所差异,例如:天使拥有美丽的外形和披肩长发, * 并穿着白裙,而恶魔则是光头,并穿一件刺眼的黑衣。无论哪种角色,他们的创建步骤大致相同,都是先创建其组成部分,然后装配成一个完整的角色, * 现试用创建者模式来实现游戏角色的创建。 * 分析: * 角色类Actor==》需要包含类型type、性别sex、脸型face、服装costume、发型hairstytle; * 抽象的角色构造器ActorBuilder==》buildType,buildSex、buildFace,buildCostume,buildHairstytle抽象方法 * 具体的实现类==》英雄HeroBuilder,天使AngelBuilder,恶魔DevilHuilder * @param args */ public static void main(String[] args) { Object bean = XMLUtil.getBean(); if (null != bean && bean instanceof ActorBuilder){ ActorBuilder builder = (ActorBuilder) bean; ActorController controller = new ActorController(); Actor actor = controller.construct(builder); System.out.println(actor.getType()+"的外观:"); System.out.println("属性:"+actor.getSex()); System.out.println("面容:"+actor.getFace()); System.out.println("发型:"+actor.getHairstyle()); System.out.println("服饰:"+actor.getCostume()); } } }
运行结果:
如果我们需要增加新的角色时只需要增加新的具体建造者实现并继承抽象建造者,然后修改配置文件,无需修改原有的代码,系统更易于扩展。
-
-
深入探讨指挥者类
省略指挥者: 有些情况下为了简化系统结构,我们可以将指挥者与抽象构建这进行合并,由Builder提供逐步构建复杂对象的方法construct(Builder builder),由于Builder类是抽象类,可以将construct(Builder builder)方法声明为static的方法,以供客户端直接调用,我们还可以直接省略construct方法中的参数,直接在Builder使用this调用,但注意这个时候方法不要使用static,因为我们需要实例来提供复杂对象的装配。
修改完成的抽象构建者代码如下:public 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.buildFace(); this.buildHairstyle(); this.buildCostume(); this.buildSex(); return actor; } }
对应的客户端:
public class Client { /** * 优化: * 忽略指挥者(ActorController),引入钩子方法 * @param args */ public static void main(String[] args) { Object bean = XMLUtil.getBean(); if (null != bean && bean instanceof ActorBuilder){ ActorBuilder builder = (ActorBuilder) bean; Actor actor = builder.construct(); System.out.println(actor.getType()+"的外观:"); System.out.println("属性:"+actor.getSex()); System.out.println("面容:"+actor.getFace()); System.out.println("发型:"+actor.getHairstyle()); System.out.println("服饰:"+actor.getCostume()); } } }
引入钩子方法: 建造者模式还可以通过construt()方法来更加精细地控制产品的创建过程,例如增加一类称为钩子的特殊方法来控制是否对某个builderPartX()进行调用。
(钩子方法:是对于抽象方法或者接口中定义的方法的一个空实现,在实际中的应用,比如说有一个接口,这个接口里有7个方法,而你只想用其中一个方法,那么这时,你可以写一个抽象类实现这个接口,在这个抽象类里将你要用的那个方法设置为abstract,其它方法进行空实现,然后你再继承这个抽象类,就不需要实现其它不用的方法,这就是钩子方法的作用。)
钩子方法的返回值通常是boolean类型,方法名一般为isXXX(),钩子方法定义在抽象建造者中,在上面的案例中,我们可以通过引入钩子方法isBarehead()方法来判断角色是否为光头,抽象建造者提供一个默认的返回值false,恶魔建造者重写该方法返回true,construt()方法在调用buildHair时现判断是否为光头,不是光头再构建发型,否则不构建发型。
修改后的建造者:public 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 isBarehead(){ return true; } // 逐步构建复杂的对象 public Actor construct(){ this.buildType(); this.buildFace(); if (!this.isBarehead()){ this.buildHairstyle(); } this.buildCostume(); this.buildSex(); return actor; } }
恶魔建造者:
public class DevilBuilder extends ActorBuilder { @Override public void buildType() { actor.setType("恶魔"); } @Override public void buildSex() { actor.setSex("妖"); } @Override public void buildFace() { actor.setFace("恐怖"); } @Override public void buildCostume() { actor.setCostume("黑斗篷"); } @Override public void buildHairstyle() { actor.setHairstyle("光头"); } @Override public boolean isBarehead() { return true; } }
==修改配置文件后==运行结果:
-
优缺点分析
-
优点
- 客户端不必知道产品的内部组成细节,将产品本身与产品的创建过程实现了解耦,使相同的创建过程可以创建出不同的产品。
- 每一个具体建造者相对独立,与其它的建造者无关,这让增加或删除具体建造者变的很便捷,基本无需修改其它代码,符合开闭原则。
- 可以更加精细的控制产品的创建过程,将复杂产品的创建步骤分解在不同的方法中,使创建的过程更加的清晰,也更方便使用程序来控制创建过程。
-
缺点
- 所创建的产品一般需要有较多的共同点,如果产品之间差异较大则无法使用,因此使用的范围受到一定的限制。
- 如果产品的内部变化很复杂,可能导致需要定义很多具体建造者类来实现这种变化,导致系统变得很庞大,增加系统的理解难度和运行成本。
-
适用环境
在以下情况下可以考虑使用建造者模式:
1. 需要生成的对象有复杂的内部结构;
2. 需要生成的产品对象属性相互依赖,需要指定其生成的顺序;
3. 对象的创建过程独立于创建该对象的类(建造者模式引入指挥者将类的创建过程封装在指挥者中);
4. 隔离对象的创建和使用,使得相同的创建过程可以创建出不同的产品;
-
-
自练习习题
- 电脑组装工厂可以将CPU\内存\硬盘\主机\显示屏等硬件组装在一起构成一台电脑,这台电脑可以是台式机\可以是笔记本\也可以是没有显示器的服务器,具体创建什么电脑由我们的客户决定,使用建造者模式模拟实现。
自律即自由,认真编写每一行代码!!!
半原创博客,用以记录学习,希望可以帮到您,不喜可喷。
- 点赞
- 收藏
- 关注作者
评论(0)