Java设计模式学习记录-建造者模式
前言
今天周末,有小雨,正好也不用出门了,那就在家学习吧,经过了两周的面试,拿到了几个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插件倡导的那个原则:“写的更少,做的更多”。