前言

今天周末,有小雨,正好也不用出门了,那就在家学习吧,经过了两周的面试,拿到了几个offer,但是都不是自己很想去的那种,要么就是几个人的初创小公司,要么就是开发企业内部系统的这种传统开发,感觉这种传统开发已经不能给自己带来多大的提升了,因为工作了这几年这种系统经历了不少了,整天的就是增删改查。创业小公司已经不想再去了,工作了这几年去的都是这种小公司,风险大,压力大,节奏快,没时间沉淀学习。上上家东家还欠我几个月工资呢,就是因为创业公司资金链断了,然后老板忽悠领导,领导再忽悠我们,后来实在发不出工资了,忽悠不住了,就大批大批的走人了。

所以现在很是纠结,大点公司又去不了小的公司还看不上,目前就是这么个高不成低不就的状态,所以还是抓紧时间学习,充实自己吧,哪怕现在进不去稍微大点的公司,那经过努力的学习后说不定还是有机会的,但是不努力是一点机会都没有的。

好了,言归正传,这次要介绍的是创建型设计模式的最后一个,建造者模式,这个模式其实我在平时开发中用的很多,只不过是用了这个模式的更深一种形式吧。后面我会介绍到这一部分内容的。

建造者模式

建造者模式能够将一个复杂的构建与其表示相分离,使得同样的构建过程可以创建不同的表示。这句话理解起来可能有点抽象,简单来说就是调用相同的创建对象的方法(建造过程)可以创建出不同的对象。

还是举例来说明吧,如果说我要创建一部手机,我需要先制造手机的几个核心部件,例如:屏幕、电池、听筒、话筒、机身等。

public class MobilePhone {

    //手机屏幕
private String screen;
//电池
private String battery;
//话筒
private String microphone;
//听筒
private String phoneReceiver;
//机身
private String phoneBody; public String getScreen() {
return screen;
} public void setScreen(String screen) {
this.screen = screen;
} public String getBattery() {
return battery;
} public void setBattery(String battery) {
this.battery = battery;
} public String getMicrophone() {
return microphone;
} public void setMicrophone(String microphone) {
this.microphone = microphone;
} public String getPhoneReceiver() {
return phoneReceiver;
} public void setPhoneReceiver(String phoneReceiver) {
this.phoneReceiver = phoneReceiver;
} public String getPhoneBody() {
return phoneBody;
} public void setPhoneBody(String phoneBody) {
this.phoneBody = phoneBody;
}
}

每一部手机都是这个类的对象,在创建一部手机的时候都要保证这几个核心部件的创建。所以创建手机是需要一个标准规范的,因为这几个核心部件都可以是不同的型号,不同的型号的部件制造出来的手机也是不同的,这样就有了下面建造规范的接口。

public interface IBuildPhone {

    /**
* 建造手机屏幕
*/
void buildScreen(); /**
* 建造手机电池
*/
void buildBattery(); /**
* 建造手机听筒
*/
void buildMicrophone(); /**
* 建造手机话筒
*/
void buildPhoneReceiver(); /**
* 建造手机机身
*/
void buildPhoneBody();
}

有了规范了,就可以创建手机了,先创建一个iphoneX。

public class IPhoneX implements IBuildPhone {

    private MobilePhone mobilePhone;

    public IPhoneX(){
mobilePhone = new MobilePhone();
} /**
* 建造手机屏幕
*/
@Override
public void buildScreen() {
mobilePhone.setScreen("OLED显示屏");
} /**
* 建造手机电池
*/
@Override
public void buildBattery() {
mobilePhone.setBattery("2700mAh电池容量");
} /**
* 建造手机听筒
*/
@Override
public void buildMicrophone() {
mobilePhone.setMicrophone("听筒");
} /**
* 建造手机话筒
*/
@Override
public void buildPhoneReceiver() {
mobilePhone.setPhoneReceiver("话筒");
} /**
* 建造手机机身
*/
@Override
public void buildPhoneBody() {
mobilePhone.setPhoneBody("iphoneX机身");
} /**
* 创建手机
* @return
*/
public MobilePhone build(){
return mobilePhone;
}
}

创建手机的工具写好了,下面就可以使用了。

public class Director {

    /**
* 建造一部手机
* @param buildPhone
* @return
*/
public MobilePhone createMobilePhone(IBuildPhone buildPhone){ buildPhone.buildBattery();
buildPhone.buildMicrophone();
buildPhone.buildScreen();
buildPhone.buildPhoneReceiver();
buildPhone.buildPhoneBody(); return buildPhone.createMobilePhone();
} @Test
public void thatTest(){
System.out.println(JSON.toJSONString(createMobilePhone(new IPhoneX())));
}
}

关键的方法在createMobilePhone()方法,这个方法接收一个IBuildPhone接口的对象,所以只要符合这个创建手机规范的对象都可以创建一部手机。createMobilePhone()方法可以接收new IPhoneX()这样一个对象,也可以接收new IPhone8()、new FindX()等等。

具体使用方法在thatTest()方法中。这个方法的运行结果是:

{"battery":"2700mAh电池容量","microphone":"听筒","phoneBody":"iphoneX机身","phoneReceiver":"话筒","screen":"OLED显示屏"}

上面这个例子的实现过程就使用了我们今天要说的建造者模式,我们来分析一下建造者模式的结构。

如下图:

在建造者模式的结构图中包含如下4个角色。

Builder(抽象建造者):它(IBuildPhone)为创建一个产品的各个部件指定了标准,规定了要创建复杂对象需要创建哪些部分,并不直接创建对象的具体部分。

ConcreteBuilder(具体建造者):它实现了Builder接口(IPhoneX),实现各个部分的具体构造和装配方法,定义并明确它所创建的复杂对象,也可以提供一个方法返回创建好的复杂产品对象。

Product(产品角色):它(MobilePhone)是被建造的复杂对象,包含多个组成部分,具体建造者创建该产品的内部表示并定义它的装配过程。

Director(指挥者):指挥者(Director),它复杂安排复杂对象的建造次序,指挥者与抽象建造者之间存在关联关系,可以在Director的方法中调用建造者对象的部件构造与装配方法,完成建造复杂对象的任务。客户端一般只需与Director进行交互。

建造者模式灵活使用

好了,建造者模式到这里就算是介绍完了,然后说一说我们平时在项目中是怎么使用建造者模式的。先说一下场景,我们一般在开发的过程中都是需要分层的,MVC这个不一般人都不陌生吧,Model-View-Controller。(我这里只是举例子不一定真的项目中就这样用)那我们的数据在每一层的传输过程中如果需要增加或删除些额外的功能怎么实现呢?

还是举例子吧,如下面一个实体类:

public class Person {

    private Long id;

    private String name;

    private int age;

    private String address;

    public Long getId() {
return id;
} public void setId(Long id) {
this.id = id;
} public String getName() {
return name;
} public void setName(String name) {
this.name = name;
} public int getAge() {
return age;
} public void setAge(int age) {
this.age = age;
} public String getAddress() {
return address;
} public void setAddress(String address) {
this.address = address;
}
}

如果说这个类是一个orm框架需要的实体类,它最好的场景是只被后端的数据操作使用,但是controller中有一个add方法,这个方法是新增一个人员,add方法接收的参数是一个人员对象,但是这个对象和上面这个Person得属性有些差别,例如这个对象里面有请求ip,以及这个对象中没有id这个字段(id在数据库中自增,所以前端不允许传过来id )。这个时候就不能使用Person类的对象作为add的方法了,需要再创建一个类专门来给Controller使用。

如下代码:

/**
* Controller使用的参数类
*/
public class PersonVO { private String name; private int age; private String address; //ip地址
private String requestIP; public String getName() {
return name;
} public void setName(String name) {
this.name = name;
} public int getAge() {
return age;
} public void setAge(int age) {
this.age = age;
} public String getAddress() {
return address;
} public void setAddress(String address) {
this.address = address;
} public String getRequestIP() {
return requestIP;
} public void setRequestIP(String requestIP) {
this.requestIP = requestIP;
}
}

参数对象可以创建了, 但是PersonVO的对象是需要转成Person的对象的,这样才能插入到数据库中(数据库的insert方法的参数是Person对象)。这种转换操作其实也简单如下代码:

public Person convert2Person(PersonVO personVO){

        Person person = new Person();

        person.setName(personVO.getName());
person.setAge(personVO.getAge());
person.setAddress(personVO.getAddress()); return person;
}

但是我们通常是不这么做的,因为如果要转换的这个对象的字段很多那需要写很多次对象调setter方法来进行赋值。一种方式是直接写一个将所有属性当做参数的构造方法,直接一个一个把属性值传入就可以了,这种方式最简单暴力。还有一种方式就是需要包装一下这种方式,把Person改造一下。

如下代码:

public class Person {

    private Long id;

    private String name;

    private int age;

    private String address;

    public Long getId() {
return id;
} public void setId(Long id) {
this.id = id;
} public String getName() {
return name;
} public void setName(String name) {
this.name = name;
} public int getAge() {
return age;
} public void setAge(int age) {
this.age = age;
} public String getAddress() {
return address;
} public void setAddress(String address) {
this.address = address;
} Person(){} Person(String name,int age,String address){
this.name = name;
this.age = age;
this.address = address;
} public static Person builder(){
return new Person();
} public Person name(String name){
this.name = name;
return this;
} public Person age(int age) {
this.age = age;
return this;
} public Person address(String address) {
this.address = address;
return this;
} public Person build(){
return new Person(name,age,address);
} }

后面新增了两个构造函数,以及一个builder()方法和一个build()方法,还有几个赋值方法,需要注意的是赋值方法和setter方法的区别,这样的赋值方法是在赋值后将当前对象返回,用来实现链式调用。

这样在对象转换的时候就可以这样用了:

public Person convert2Person(PersonVO personVO){

        return Person.builder()
.name(personVO.getName())
.age(personVO.getAge())
          .address(personVO.getAddress())
.build();
}

这种方式其实也是一种建造者模式的应用,这种方式在构建对象的过程实现起来更灵活,例如如果这个对象就只有前两个参数有值,address是没有内容的,那可以直接这样写:

public Person convert2Person(PersonVO personVO){

        return Person.builder()
.name(personVO.getName())
.age(personVO.getAge())
.build();
}

在填充了两个属性后就直接调用build()方法区创建对象。

其实为了实现这种创建对象的方式,每次除了写getter/setter方法后还需要写这么多其他的代码,这样是有点麻烦的,所以在日常的开发过程中,我们是没必要写额外的代码来实现这种方式,可以用工具来实现。推荐一个工具包Lombok,我们的开发工具是使用IDEA,IDEA在使用Lombok时是需要下载一个lombok的插件,然后在项目中依赖lombok的工具包,就可以使用了。使用了lombok后的代码变的非常简洁,连getter/setter方法都不用写了。

@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class Person { private Long id; private String name; private int age; private String address; }

@Data 这个注解代表实现了所有非final属性的getter/setter方法,以及重写了toString方法和hashCode方法。

@AllArgsConstructor 这个注解代表实现了一个包含全部属性为参数的构造方法(Person(Long id,String name,int age, String address))。

@NoArgsConstructor 这个注解代表实现了一个没有任何参数的构造方法(Person())。

@Builder 这个注解代表实现了上面介绍的那种灵活的创建对象的建造者模式(使用这个注解时需要依赖上面3个注解,原因看这种方式的实现过程就能明白了)。

在创建对象时,使用方式没有变化也是链式调用方法赋值,这里就不再写创建对象的过程了。

其实lombok还有一些其他的注解也很强大,使用这个工具包的好处是,不但使代码变得简洁,也提高了开发效率。

在这里想到了jQuery插件倡导的那个原则:“写的更少,做的更多”

Java设计模式学习记录-建造者模式的更多相关文章

  1. Java设计模式学习记录-模板方法模式

    前言 模板方法模式,定义一个操作中算法的骨架,而将一些步骤延迟到子类中.使得子类可以不改变一个算法的结构即可重新定义该算法的某些特定步骤. 模板方法模式 概念介绍 模板方法模式,其实是很好理解的,具体 ...

  2. Java设计模式学习记录-状态模式

    前言 状态模式是一种行为模式,用于解决系统中复杂的对象状态转换以及各个状态下的封装等问题.状态模式是将一个对象的状态从该对象中分离出来,封装到专门的状态类中,使得对象的状态可以灵活多变.这样在客户端使 ...

  3. Java设计模式学习记录-备忘录模式

    前言 这次要介绍的是备忘录模式,也是行为模式的一种 .现在人们的智能手机上都会有备忘录这样一个功能,大家也都会用,就是为了记住某件事情,防止以后自己忘记了.那么备忘录模式又是什么样子的呢?是不是和手机 ...

  4. Java设计模式学习记录-迭代器模式

    前言 这次要介绍的是迭代器模式,也是一种行为模式.我现在觉得写博客有点应付了,前阵子一天一篇,感觉这样其实有点没理解透彻就写下来了,而且写完后自己也没有多看几遍,上次在面试的时候被问到java中的I/ ...

  5. Java设计模式学习记录-解释器模式

    前言 这次介绍另一个行为模式,解释器模式,都说解释器模式用的少,其实只是我们在日常的开发中用的少,但是一些开源框架中还是能见到它的影子,例如:spring的spEL表达式在解析时就用到了解释器模式,以 ...

  6. Java设计模式学习记录-命令模式

    前言 这次要介绍的是命令模式,这也是一种行为型模式.最近反正没有面试机会我就写博客呗,该投的简历都投了.然后就继续看书,其实看书也会给自己带来成就感,原来以前不明白的东西,书上已经给彻底的介绍清楚了, ...

  7. Java设计模式学习记录-外观模式

    前言 这次要介绍的是外观模式(也称为门面模式),外观模式也属于结构型模式,其实外观模式还是非常好理解的,简单的来讲就是将多个复杂的业务封装成一个方法,在调用此方法时可以不必关系具体执行了哪些业务,而只 ...

  8. Java设计模式学习记录-桥接模式

    前言 这次介绍结构型设计模式中的第二种模式,桥接模式. 使用桥接模式的目的就是为了解耦,松散的耦合更利于扩展,但是会增加相应的代码量和设计难度. 桥接模式 桥接模式是为了将抽象化与实现化解耦,让二者可 ...

  9. Java设计模式学习记录-代理模式

    代理模式 代理模式是常见设计模式的一种,代理模式的定义是:为其他对象提供一种代理以控制对这个对象的访问. 在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起 ...

随机推荐

  1. Amoeba变形虫

    我们通过路由选择来决定操作时访问那个数据库,而路由的选择方式不外乎以下几种: 1) SpringAOP方式:spring底层配置多个数据源,配置路由(面向切面编程)手工写很多代码(废除) 2) MyS ...

  2. Win7的“以管理员身份运行”

    如果HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System\EnableLUA被设置为0,则"以管理员身份运行" ...

  3. AEAI Portal 权限体系说明

    1.概述 在数通畅联的产品体系中,AEAI Portal毫无疑问的占据了很重要的地位,在这里我们将通过参考Portal样例,讲述一下AEAI Portal权限体系的控制方法.在Portal使用过程中, ...

  4. javaweb项目中的过滤器的使用

    翻阅博客园的的时候,看到两篇关于javaweb过滤器的帖子写的很好,这里备忘一下: 过滤器基础:http://www.cnblogs.com/xdp-gacl/p/3948353.html 获取器案例 ...

  5. Dapper实现一对多对象关系聚合导航属性

    领域对象:Game(游戏), Room(游戏群),两者一对多的关系,SQL语句中会用到JOIN public class Game : AggregateRoot { public string Ta ...

  6. 【ElasticSearch】:索引Index、文档Document、字段Field

    因为从ElasticSearch6.X开始,官方准备废弃Type了.对应数据库,对ElasticSearch的理解如下: ElasticSearch 索引Index 文档Document 字段Fiel ...

  7. centos7搭建mysql5.7主从同步

    主从基本概念 mysql主从同步定义 主从同步使得数据可以从一个数据库服务器复制到其他服务器上,在复制数据时,一个服务器充当主服务器(master),其余的服务器充当从服务器(slave).因为复制是 ...

  8. POJ 2498

    #include<iostream> using namespace std; #include<string> #include<stdio.h> int mai ...

  9. 图片训练:使用卷积神经网络(CNN)识别手写数字

    这篇文章中,我们将使用CNN构建一个Tensorflow.js模型来分辨手写的数字.首先,我们通过使之“查看”数以千计的数字图片以及他们对应的标识来训练分辨器.然后我们再通过此模型从未“见到”过的测试 ...

  10. Quarz.net 设置任务并行和任务串行

    如何设置Quarz.net某个任务完成后再继续执行该任务?  Quarz.net 的任务有并行和串行两种: 并行:一个定时任务,当执行时间到了的时候,立刻执行此任务,不管当前这个任务是否在执行中: 串 ...