设计模式 #3 (原型模式、建造者模式)
设计模式 #3 (原型模式、建造者模式)
文章中所有工程代码和UML
建模文件都在我的这个GitHub
的公开库--->DesignPattern。Star
来一个好吗?秋梨膏!
原型模式
简述:用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。
反例 #1 :
public class negtive {
/*==============服务端=======================*/
static class Resume{
private String name;
private Integer age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
@Override
public String toString() {
return "Resume{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
/*==============客户端=========================*/
public static void main(String[] args) {
Resume resume01= new Resume();
resume01.setName("ling001");
resume01.setAge(20);
System.out.println(resume01);
Resume resume02= new Resume();
resume02.setName("ling002");
resume02.setAge(23);
System.out.println(resume02);
}
}
复制多份简历需要一个个去new
。咱们都是IT
人士了,得专业点,重复无用功怎么能做呢?程序员要说最熟的,难道不是Ctrl+C
+Ctrl+V
吗?(手动滑稽保命)Java就提供了这种复制粘贴的办法,不过他有自己的名字--Clone
。
正例 #1:
public class postvie_01 {
/*==============服务端=======================*/
static class Resume implements Cloneable{
private String name;
private Integer age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
@Override
public String toString() {
return "Resume{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
/*==============客户端=========================*/
public static void main(String[] args) throws CloneNotSupportedException {
Resume resume01= new Resume();
resume01.setName("ling001");
resume01.setAge(20);
Resume resume02= (Resume) resume01.clone();
resume02.setName("li666");
System.out.println(resume01);
System.out.println(resume02);
System.out.println(resume01.equals(resume02));
}
}
不需要new
,只需要服务端先实现一个Cloneable
,并重写clone
方法即可。而且作用堪比new
一个新的对象,因为克隆和被克隆的对象并不是同一个,equals
的时候得到的是false
的。
这时候,新需求来了(这次没有产品经理,别拿刀K自己):为简历增加一个工作经历的内容,这时候:
反例 #2:
public class negtive_02 {
/*==============服务端=======================*/
static class Resume implements Cloneable{
private String name;
private Integer age;
private Date workExperience;
public Date getWorkExperience() {
return workExperience;
}
public void setWorkExperience(Date workExperience) {
this.workExperience = workExperience;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
@Override
public String toString() {
return "Resume{" +
"name='" + name + '\'' +
", age=" + age +
", workExperience=" + workExperience +
'}';
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
/*==============客户端=========================*/
public static void main(String[] args) throws CloneNotSupportedException{
Resume resume01= new Resume();
Date date = new Date();
resume01.setName("ling001");
resume01.setAge(20);
resume01.setWorkExperience(date);
Resume resume02= (Resume) resume01.clone();
resume02.getWorkExperience().setTime(0);
System.out.println(resume02);
System.out.println(resume01);
System.out.println(resume01.equals(resume02));
}
}
这是一个关于深拷贝、浅拷贝的问题。
深拷贝与浅拷贝
浅拷贝(浅复制):clone
得到的对象a
其实只是对被clone
对象b
的引用,即对象a
是指向b
对象上的。
深拷贝(深复制):clone
得到的对象a
是对被clone
对象b
的复制,即对象a
和b
是两个不同的对象,a
只复制了b
的内容。
Java
中,对一对象的clone
中深拷贝对象的基本类型字段,浅拷贝引用类型字段。
这时候要将浅拷贝改为深拷贝。
正例 #2:只需要重写对象的clone
方法即可。
@Override
public Object clone() throws CloneNotSupportedException {
Resume clone = (Resume) super.clone();
Date cloneDate = (Date) clone.getWorkExperience().clone();
clone.setWorkExperience(cloneDate);
return clone;
}
其实就是对浅拷贝的字段再进行深拷贝。
以上面用到的Date
引用类型对象为例:
可以看到Date
是实现了Cloneable
接口的,即表示Date
也是可以进行clone
(克隆)的。只需要将浅拷贝的Date
再使用clone
方法进行一次深拷贝,再赋值给clone
的对象即可。具体参照上面重写的clone
方法。
总结:
这种模式是实现了一个原型接口,该接口用于创建当前对象的克隆。当直接创建对象的代价比较大时,则采用这种模式。例如,一个对象需要在一个高代价的数据库操作之后被创建。我们可以缓存该对象,在下一个请求时返回它的克隆,在需要的时候更新数据库,以此来减少数据库调用。
建造者模式
简述:建造者模式(Builder Pattern)使用多个简单的对象一步一步构建成一个复杂的对象。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
先明确实体类
public class Computer {
private String cpu;
private String gpu;
private String Hd;
private String RAM;
public String getCpu() {
return cpu;
}
public void setCpu(String cpu) {
this.cpu = cpu;
}
public String getGpu() {
return gpu;
}
public void setGpu(String gpu) {
this.gpu = gpu;
}
public String getHd() {
return Hd;
}
public void setHd(String hd) {
Hd = hd;
}
public String getRAM() {
return RAM;
}
public void setRAM(String RAM) {
this.RAM = RAM;
}
@Override
public String toString() {
return "Computer{" +
"cpu='" + cpu + '\'' +
", gpu='" + gpu + '\'' +
", Hd='" + Hd + '\'' +
", RAM='" + RAM + '\'' +
'}';
}
}
反例 #1 :
废话不多说,创建对象。
public class negtive_01 {
public static void main(String[] args) {
Computer computer_01 = new Computer();
computer_01.setCpu("9700k");
computer_01.setGpu("gtx2080ti");
computer_01.setHd("SSD--1T");
computer_01.setRAM("32G");
Computer computer_02 = new Computer();
computer_02.setCpu("9600k");
computer_02.setGpu("gtx1080ti");
computer_02.setHd("SSD--500G");
computer_02.setRAM("16G");
System.out.println(computer_02);
System.out.println(computer_01);
}
}
缺陷:建造复杂对象的时候,客户端程序猿要炸,造成客户端代码臃肿,且违反迪米特法则。
反例 #2:
public class negtive_02 {
/*=============服务端==========================*/
static class HighComputerBuilder {
private Computer computer = new Computer();
public Computer build() {
computer.setCpu("9700k");
computer.setGpu("gtx2080ti");
computer.setHd("SSD--1T");
computer.setRAM("32G");
return computer;
}
}
static class High_02ComputerBuilder {
private Computer computer = new Computer();
public Computer build() {
computer.setCpu("9600k");
computer.setGpu("gtx1080ti");
computer.setHd("SSD--500G");
computer.setRAM("16G");
return computer;
}
}
/*=====================客户端===============================*/
public static void main(String[] args) {
HighComputerBuilder builder_01 = new HighComputerBuilder();
Computer computer_01 =builder_01.build();
High_02ComputerBuilder builder_02 = new High_02ComputerBuilder();
Computer computer_02 =builder_02.build();
System.out.println(computer_01);
System.out.println(computer_02);
}
}
创造了建造者类,用于创建复杂对象。
UML
类图如下:
缺陷:建造者遗漏部分建造步骤编译也会通过,会造成建造出来的对象不符合要求。比如,漏执行某一步骤时,使得部分值为null
,后续对象属性被调用时,可能会抛出空指针NullPointerException
异常,会造成程序崩溃。
反例 #3:
public class negtive_03 {
/*=============服务端==========================*/
interface ComputerBuilder{
Computer build();
void setCpu();
void setGpu();
void setHd();
void setRAM();
}
static class HighComputerBuilder implements ComputerBuilder{
private Computer computer = new Computer();
@Override
public Computer build() {
return computer;
}
@Override
public void setCpu() {
computer.setCpu("9700k");
}
@Override
public void setGpu() {
computer.setGpu("gtx2080ti");
}
@Override
public void setHd() {
computer.setHd("SSD--1T");
}
@Override
public void setRAM() {
computer.setRAM("32G");
}
}
static class High_02ComputerBuilder implements ComputerBuilder{
private Computer computer = new Computer();
@Override
public Computer build() {
return computer;
}
@Override
public void setCpu() {
computer.setCpu("9600k");
}
@Override
public void setGpu() {
computer.setGpu("gtx1080ti");
}
@Override
public void setHd() {
computer.setHd("SSD--500G");
}
@Override
public void setRAM() {
computer.setRAM("16G");
}
}
/*==============客户端=====================================*/
public static void main(String[] args) {
HighComputerBuilder builder_01 = new HighComputerBuilder();
builder_01.setCpu();
builder_01.setGpu();
builder_01.setHd();
builder_01.setRAM();
Computer computer_01 =builder_01.build();
High_02ComputerBuilder builder_02 = new High_02ComputerBuilder();
builder_02.setCpu();
builder_02.setGpu();
builder_02.setHd();
builder_02.setRAM();
Computer computer_02 =builder_02.build();
System.out.println(computer_01);
System.out.println(computer_02);
}
}
创造了建造者接口,创建者不会再遗漏步骤。
UML
类图如下:
缺陷:
-
每一个
builder
都要自己去调用setXXX
方法进行创建,造成代码重复。 -
需要客户端自己执行创建步骤,建造复杂对象的时候,容易造成客户端代码臃肿,且违反迪米特法则。而且客户端会出现遗漏步骤的情况。又回到了原点的感觉???
正例 #1:
public class postive {
/*=============服务端==========================*/
interface ComputerBuilder{
Computer getComputer();
void setCpu();
void setGpu();
void setHd();
void setRAM();
}
static class HighComputerBuilder implements ComputerBuilder {
private Computer computer = new Computer();
@Override
public Computer getComputer() {
return computer;
}
@Override
public void setCpu() {
computer.setCpu("9700k");
}
@Override
public void setGpu() {
computer.setGpu("gtx2080ti");
}
@Override
public void setHd() {
computer.setHd("SSD--1T");
}
@Override
public void setRAM() {
computer.setRAM("32G");
}
}
static class High_02ComputerBuilder implements ComputerBuilder {
private Computer computer = new Computer();
@Override
public Computer getComputer() {
return computer;
}
@Override
public void setCpu() {
computer.setCpu("9600k");
}
@Override
public void setGpu() {
computer.setGpu("gtx1080ti");
}
@Override
public void setHd() {
computer.setHd("SSD--500G");
}
@Override
public void setRAM() {
computer.setRAM("16G");
}
}
//指挥者
static class Director {
public Computer build(ComputerBuilder builder){
builder.setCpu();
builder.setGpu();
builder.setRAM();
builder.setHd();
return builder.getComputer();
}
}
/*==============客户端=====================================*/
public static void main(String[] args) {
Director director = new Director();
Computer computer_01 =director.build(new HighComputerBuilder());
Computer computer_02 =director.build(new High_02ComputerBuilder());
System.out.println(computer_01);
System.out.println(computer_02);
}
}
UML
类图如下:
此时在需要增加一个不同配置的A_Computer
类型计算机,只需要编写一个A_Builder
类实现ComputerBuilder
接口,再传给指挥者Director
进行创建即可得到一个A_Computer
类型的Computer
对象。符合开闭原则。
总结一下建造者模式的优点:
-
创建对象的过程保持稳定。(通过
ComputerBuilder
接口保持稳定) -
创建过程只需要编写一次(通过实现
ComputerBuilder
接口的类保持稳定) -
保持扩展性,需要创建新类型的对象时,只需要创建新的
Builder
,再使用指挥者Director
进行调用进行创建即可。 -
增加指挥者
Director
保证步骤稳定执行,客户端不需要知道创建对象的具体步骤,符合迪米特法则。
建造者模式和工厂模式的区别
- 工厂模式注重
new
一个对象就可以,是否得到了这一对象,更多地关注new
的结果。 - 建造者模式注重保证
new
的对象稳定可用,保证不出现细节缺漏,更多关注new
的细节、过程。
2020.09.18 修改:
改正UML
类图中High_02ComputerBuilder
类的computer
属性类型。