文章目录
一、原型模式
引入:将一个羊对象一毛一样的复制十份, 最朴素的想法:使用同样的构造参数new十个对象。缺点很明显,写起来很麻烦。。。
另一种思路:
Object类提供了一个clone()方法,可以将当前对象完整的复制一份。
但是要使用这个方法要求类必须实现Cloneable接口【able型接口是表示这个类具有某项能力。
这个当然来自英语词根-able】
这种想法实现起来就是原型模式的初始模型
原型模式是指,提供一个对象,无需知晓如何复制的细节就能得到和这个对象一毛一样的复制。
孙大圣拔一根猴毛,再造一群孙大圣。
原型模式类图:
Client是指想克隆自己的类,而ConcretePrototype是指帮助其创建复制的类。
好处:
- 模板对象改变时,克隆对象跟着改变,而不必去一个一个去动手修改。
- 动态获取运行状态,指的就是自动修改这一点。
原型模式需要对新加入的属性修改提供方源码,因此违反了开闭原则。
1.1 实现Cloneable方式
羊的实体类【需要重写clone()方法】
class Sheep implements Cloneable {
private String name;
private int age;
public Sheep(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Sheep{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();//无需任何操作,默认就好
}
}
//测试对羊类的复制
@Test
public void test() throws CloneNotSupportedException {
Sheep sheep1 = new Sheep("小红", 2);
Sheep sheep2 = (Sheep)sheep1.clone();
Sheep sheep3 = (Sheep)sheep1.clone();
Sheep sheep4 = (Sheep)sheep1.clone();
Sheep sheep5 = (Sheep)sheep1.clone();
Sheep sheep6 = (Sheep)sheep1.clone();
System.out.println(sheep1);
System.out.println(sheep2);
System.out.println(sheep3);
System.out.println(sheep4);
System.out.println(sheep5);
System.out.println(sheep6);
}
1.2 原型模式在Spring中的使用
使用过spring框架的我们,在配置bean标签的时候,会发现有一个scope的标签,他的默认值是singleton(单例),若我们设置为prototype,就会变成原型模式创建对象【即每次getBean()都会得到一个完整的拷贝】。此时,若我们使用==
运算符去比较,就会得到false的结果。因为得到的并不是同一个对象了。
在getBean()的方法栈调用中,会进入一个BeanFactory的实现类,最终会通过方法isPrototype()
判断对象是否设置为原型模式,若是,按照原型模式创建对象。
1.3 深拷贝与浅拷贝【重点】
通过(一)中的方式只能实现对象的浅拷贝:
即对象的基本数据类型以及String类型的变量可以完整的赋值一个新的值,但是其引用类型变量不能复制,而是会将该对象的引用复制一遍放到这个克隆体中来敷衍一下。
但是有时候又确实有复制完整对象的需求。
如,对上面的羊类,多了一个属性Master(主人),现在需要将其主人也完整复制一份。
实现完整带引用的复制有两种写法: (1)深度重写clone()方法实现深拷贝 (一)的实现中,我们让clone()保持默认实现,在这里,我们需要将其稍微改进一点,不但要 使用clone()方法super.clone的到对其普通变量的克隆,还需要对其引用类型进行克隆(直接让引用类型调用自己的克隆方法即可)。【PS:如果该引用类型类也有引用对象是不是也要对他进行同样处理呢?太套娃了】 (2)通过虚拟化/反序列化实现深拷贝 让引用类和本类都实现Serializable接口,使其具有序列化能力。 定义一个普通方法,先将原对象进行序列化写入硬盘,再通过反序列化得到新的对象。
由于对象序列化流/反序列化流都是包装类,因此这个操作共需要四个类型的流对象。
这里有个技巧,可以使用一个特殊的流类:ByteArrayOutputStream/...iutpu...
这个流可以将读取的直接暂存到缓存,而不是真的写到硬盘。
这样做,既可以节省硬盘空间,又可以让只在内存操作,增快效率。
以下是代码实现:
public class Demo2 implements Serializable{
class Master implements Cloneable, Serializable {
private String name;
private int age;
public Master(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Master{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
class Sheep implements Cloneable,Serializable {
private String name;
private int age;
private Master master;
public Sheep(String name, int age, Master master) {
this.name = name;
this.age = age;
this.master = master;
}
@Override
public String toString() {
return "Sheep{" +
"name='" + name + '\'' +
", age=" + age +
", master=" + master +
'}';
}
@Override
// 会返回不同master.hashCode()的深拷贝方式1:重写clone()
protected Object clone() throws CloneNotSupportedException {
//复制普通类型和String类型
Sheep sheep = (Sheep)super.clone();
master = (Master)this.master.clone();
sheep.master = master;
return sheep;
}
//使用序列化实现深拷贝
public Object deepClone() throws IOException, ClassNotFoundException {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(this);
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
Sheep sheep = (Sheep) ois.readObject();
bos.close();
oos.close();
bis.close();
ois.close();
return sheep;
}
/* @Override
// 会返回同样master.hashCode()的浅拷贝
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}*/
}
需要注意的是,我为了方便定义了好几个内部类,序列化时必须要将外部类也给实现序列化接口,否则序列化会抛出异常。
二、建造者模式
2.1 引入案例
需求:设计一个盖房子的程序。
现实中盖房子的流程是:
- 打地基;
- 砌墙;
- 封顶
由于不同房子的墙、屋顶都不相同,因此需要设计为抽象方法交由子类来实现
将这三个方法封装到一起,形成一个盖房子的方法build();
下面是设计类图:
代码实现:
abstract class AbsractHouse{
public abstract void buildBasic();
public abstract void buildWall();
public abstract void roofed();
public void build() {
buildBasic();
buildWall();
roofed();
}
}
class LittleHouse extends AbsractHouse{
@Override
public void buildBasic() {
System.out.println("小房子地基");
}
@Override
public void buildWall() {
System.out.println("小房子墙");
}
@Override
public void roofed() {
System.out.println("小房子屋顶");
}
}
@Test
public void test() {
AbsractHouse littleHouse = new LittleHouse();
littleHouse.build();
}
这样写,可以满足一般的需求。
但是,房子是一个静态的物体,而建造这件事是一个动态的行为,将动态的行为和静态实体放在一个类中非常不利用拓展,而且在实际使用中也会遇到耦合度高的问题。
我们要做的是将行为与具体的产品进行解耦,这就需要建造者模式。
2.2 建造者模式
建造者模式将产品的部件实现与建造过程隔离开来,通过一些复杂对象的构成部件就可以很轻松的创建这个复杂对象。
在现在开发的应用中,建造者模式更大的作用是帮助用户非常快的初始化一个复杂对象:
试想一个场景:
一个类有十几个属性,其中可能只有一两个属性是必需的,而大部分属性都是不必须的。
以前我们的做法是设计重载的构造器去创建对象,或者利用JavaBean的setter方法去设置这几个必须变量。
但如果这样的场景呢?
这几个必须的变量通常值是固定的,而其他才是真正需要设置的变量,这时候就很麻烦了。
遇到这种情况,正是建造者模式应用的最佳场合。
如,若用户想建造一台汽车,用户不需要知道轮胎,方向盘,发动机这些配件是怎么设计出来的,而只需要关心怎么把它们组装起来即可。
建造者模式具有四个角色:
- Production,产品,就好像上面的汽车;
- Builder,抽象的建造接口或者抽象类,用来设计创建每个部件的轮廓,就好像汽车每个部件的图纸一样;
- ConcreteBuilder:具体的建造类,实现抽象的部件创建方法,就好像根据图纸实际创建出来的部件;
- Director:具体协调上面各角色工作的中间层类,解耦的关键所在。
Director类中聚合了一个Builder实现类实例,Builder生成产品并通过Director这个中介交于客户端。
可能这样还是太抽象,转化为开头那个例子中的角色:
与最开始的设计相比,多了一层中间层HouseDirector,看看有什么好处:
再看类图,会发现Client不仅依赖Director类,还依赖Builder实现类。
不是说应该面向接口吗,为什么还要依赖实现类?
这正是创建者模式最方便的地方,具体实现类就好像是一套完整的套餐,而客户端只需要将这个套餐告诉Director,他就能用他聚合的Builder来无缝连接“这个套餐”,生成用户订购的那个产品。
比如说:我们在购买电脑时,不知道具体买哪些部件组装,这个时候可能有的地方有现成的组装好的主机,我们可以按照自己想要的配置挑选套餐,最后商家会为我们组装好我们就能获取到了。
或许这个例子比之前的盖房子更容易理解?
那就把这个例子实现了吧。。。
在这里,为了突出建造者模式的好处(配置默认的属性),我就将CPU默认为Intel的了,A厨别见怪。
/**
* 电脑类:大部分配置都是用户设置的
* 这个类作为真正的产品
* 其中,CPU使用默认配置,减少使用端的属性配置量
*/
class Computer{
private String CPU;
private String memory;
private String operatingSystem;
private String hardDisk;
public Computer() {
this.CPU = "Intel";
}
@Override
public String toString() {
return "Computer{" +
"CPU='" + CPU + '\'' +
", memory='" + memory + '\'' +
", operatingSystem='" + operatingSystem + '\'' +
", hardDisk='" + hardDisk + '\'' +
'}';
}
}
/**
* Builder抽象类,只负责创建部件,不负责创建产品本身
*/
abstract class ComputerBuilder{
abstract void builderMemory();
abstract void builderOperatingSystem();
abstract void builderHardDisk();
abstract Computer createComputer();
}
/**
* 建造者实现类
* 相当于一套完整的配置套餐
* 可以有很多套这样的套餐,我就不一一写了
*/
class WindowsComputerBuilder extends ComputerBuilder{
private Computer computer = new Computer();
@Override
void builderMemory() {
this.computer.memory = "kingston";
}
@Override
void builderOperatingSystem() {
this.computer.operatingSystem = "windows10";
}
@Override
void builderHardDisk() {
this.computer.hardDisk = "ADATA";
}
@Override
Computer createComputer() {
return this.computer;
}
}
/**
* 负责接收用户的订单请求:即ComputerBuilder实现类
* 调用ComputerBuilder的建造部件的方法返还一个Computer对象给用户
*/
class ComputerDirector{
private ComputerBuilder computerBuilder;
public ComputerDirector(ComputerBuilder computerBuilder) {
this.computerBuilder = computerBuilder;
}
public Computer buildComputer() {
computerBuilder.builderMemory();
computerBuilder.builderOperatingSystem();
computerBuilder.builderHardDisk();
return computerBuilder.createComputer();
}
}
@Test
public void test() {
System.out.println(new ComputerDirector(new WindowsComputerBuilder()).buildComputer());
}
可以看到,时至今日,建造者模式的设计已经变得非常的麻烦和不适宜,其实可以有其他的简化版本,以后有机会再补充.
2.3 JDK中对建造者模式的使用
典型应用:StringBuilder类
查看一下StringBuilder类的类图:
查看Appendable接口,他已经设计了建造的构想,即Appendable是一个抽象Builder类。
而AbstractStringBuilder类,虽然叫做抽象类,但是已经实现了大量的构建字符串方法,因此可以说他是一个Builder实现类(抽象的为什么还是实现类,听起来很怪,其实是因为这是建造者模式的一种魔改,或者说不完全遵守建造者模式规范)
最后的StringBuilder类,他解决了AbstractStringBuilder不能实例化的问题,并且提供对父类AbstractStringBuilder的调用,即他充当了Director的作用。