原型模式
定义
用原型实例指定创建对象的种类,并且通过拷贝这些原型,创建新的对象。是一种创建型设计模式,允许一个对象再创建另外一个可定制的对象,无需知道如何创建的细节。
例子
有一只羊,现在需要我们再克隆属性完全相同的四只羊。
传统方法
使用四次new方法
//传统的方法
Sheep sheep = new Sheep("tom", 1, "白色");
Sheep sheep2 = new Sheep(sheep.getName(), sheep.getAge(), sheep.getColor());
Sheep sheep3 = new Sheep(sheep.getName(), sheep.getAge(), sheep.getColor());
Sheep sheep4 = new Sheep(sheep.getName(), sheep.getAge(), sheep.getColor());
Sheep sheep5 = new Sheep(sheep.getName(), sheep.getAge(), sheep.getColor());
在创建新的对象时,总是需要重新获取原始对象的属性,如果创建的对象比较复杂,效率就会很低;总是需要重新初始化对象,而不是动态地获得对象运行时的状态,不够灵活。
原型模式
Sheep类中继承cloneable,并实现clone()方法。
@Override
protected Object clone() {
Sheep sheep = null;
try {
sheep = (Sheep) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return sheep;
}
浅拷贝与深拷贝
浅拷贝
原型模式中clone默认是浅拷贝,如果Sheep中有一个属性是对象,如下:
此时使用clone()
我们发现,这个对象属性并没有被复制一份,而是和之前的一样,如下图,hashCode一样
且修改sheep2中friend的某个属性值,sheep中的也会同样改变
那么也就是说,浅拷贝只是将该变量的内存地址(引用值)复制给一份新的对象,但实际上这两对象都指向同一个实例。所以修改值会互相影响。
深拷贝
有两种实现方法:
方法一(不推荐):
/**
* 深拷贝 方式一,重写clone方法
* @return
* @throws CloneNotSupportedException
*/
@Override
protected Object clone() throws CloneNotSupportedException {
Object deep = null;
deep = super.clone();
//将对象属性也拷贝一份
DeepProtoType deepProtoType = (DeepProtoType) deep;
deepProtoType.deepCloneableTarget = (DeepCloneableTarget) deepCloneableTarget.clone();
return deepProtoType;
}
方法二(推荐)
/**
* 深拷贝 方式二 通过对象的序列化实现
* @return
*/
public Object deepClone() {
ByteArrayOutputStream bos = null;
ObjectOutputStream oos = null;
ByteArrayInputStream bis = null;
ObjectInputStream ois = null;
try {
//序列化
bos = new ByteArrayOutputStream();
oos = new ObjectOutputStream(bos);
//当前这个对象以对象流的方式输出
oos.writeObject(this);
//反序列化
bis = new ByteArrayInputStream(bos.toByteArray());
ois = new ObjectInputStream(bis);
DeepProtoType copy = (DeepProtoType) ois.readObject();
return copy;
} catch (IOException | ClassNotFoundException ioException) {
ioException.printStackTrace();
return null;
} finally {
try {
bos.close();
oos.close();
bis.close();
ois.close();
} catch (IOException ioException) {
ioException.printStackTrace();
}
}
}
测试:
说明确实是被复制了一份。深拷贝为所有引用数据类型的成员变量申请存储空间,并复制每个引用数据类型成员变量所引用的对象,直到该对象可达的所有对象。
应用例子
Spring中原型Bean的创建中,如果xml配置的scope=“prototype”。那么在doGetBean方法中有一个if判断是否是原型模式,是就按原型模式创建,这样每次创建出来的Bean不是同一个,即使属性一样。
优点
1)创建新的对象比较复杂时,可以利用原型模式简化对象的创建过程,同时提高效率。
2)不用重新初始化对象,而是动态地获得对象运行的状态。
3)如果原始对象发生变化,比如增加属性,其他克隆对象也会发生相应的变化,不需要修改多余的代码。
4)实现深拷贝需要重写clone方法或者使用序列化和反序列化。
缺点
需要为每一个类配备一个克隆方法,对于已又有类进行改造时需要修改源代码,违反开闭原则。
建造者模式
定义
1)又叫生成器模式,是一种对象构建模式。可以将复杂对象的建造过程抽象出来,使这个抽象过程的不同实现方法可以够造出不同表现的对象。
2)建造者是一步步创建一个复杂的对象,它允许用户只通过指定复杂对象的类型和内容就可以构建它们,用户不需要知道内部实现细节。
四个角色
1)Product:产品角色,比如房子
2)Builder:抽象建造者,创建一个Product对象的各个部分指定的接口/抽象类
3)ConcreteBuilder:具体建造者,实现接口,构建和装配各个部件。
4)Director:指挥者,构建一个使用Builder接口的对象(聚合了Builder)。它主要用于创建一个复杂的对象。主要有两个作用,①隔离了客户与对象的生产过程。②负责控制产品对象的生产过程。
结构类图
例子
加入需要建一个房子,房子有buildBasic、buildWalls,roofed基本操作。房子也有高楼和普通房等类型。
传统方式
代码设计
定义抽象类AbstractHouse
public abstract class AbstractHouse {
public abstract void buildBasic();
public abstract void buildWalls();
public abstract void roofed();
public void build() {
buildBasic();
buildWalls();
roofed();
}
}
定义两个类ComonHouse和HighBuilding,均继承AbstractHouse
public class CommonHouse extends AbstractHouse{
@Override
public void buildBasic() {
System.out.println("给普通房子打地基");
}
@Override
public void buildWalls() {
System.out.println("给普通房子砌墙");
}
@Override
public void roofed() {
System.out.println("给普通房子封墙");
}
}
public class HighBuilding extends AbstractHouse{
@Override
public void buildBasic() {
System.out.println("给高楼建筑打地基");
}
@Override
public void buildWalls() {
System.out.println("给高楼建筑砌墙");
}
@Override
public void roofed() {
System.out.println("给高楼建筑封墙");
}
}
客户端调用测试
public class Client {
public static void main(String[] args) {
CommonHouse commonHouse = new CommonHouse();
commonHouse.build();
}
}
结构类图
传统方式的问题:
没有涉及缓冲层,不易扩展和维护,把房子和创建房子的过程封装在一起,耦合性增强了。
建造者模式
代码
定义个House类,平平无奇(角色Product)
定义一个抽象类HouseBuilder(也可以用接口)(角色Builder)
public abstract class HouseBuilder {
protected House house = new House();
//定义各个流程的抽象方法
public abstract void buildBasic();
public abstract void buildWalls();
public abstract void roofed();
//建造房子
public House buildHouse() {
return house;
}
}
定义两个类ComonHouse和HighBuilding,均继承HouseBuilder(ConcreteBuilder)
public class CommonHouse extends HouseBuilder{
@Override
public void buildBasic() {
System.out.println("给普通房子打地基");
}
@Override
public void buildWalls() {
System.out.println("给普通房子砌墙");
}
@Override
public void roofed() {
System.out.println("给普通房子封墙");
}
}
public class HighBuilding extends HouseBuilder{
@Override
public void buildBasic() {
System.out.println("给高楼建筑打地基");
}
@Override
public void buildWalls() {
System.out.println("给高楼建筑砌墙");
}
@Override
public void roofed() {
System.out.println("给高楼建筑封墙");
}
}
定义一个类HouseDirector(角色Director)
public class HouseDirector {
HouseBuilder houseBuilder = null;
public HouseDirector(HouseBuilder houseBuilder) {
this.houseBuilder = houseBuilder;
}
public void setHouseBuilder(HouseBuilder houseBuilder) {
this.houseBuilder = houseBuilder;
}
public House constructHouse() {
houseBuilder.buildBasic();
houseBuilder.buildWalls();
houseBuilder.roofed();
return houseBuilder.buildHouse();
}
}
主要是由指挥者决定步骤,步骤的具体细节由不同建造者实现。把创建者(HouseBuilder)与创建产品的过程(constructHouse函数)分离来实现解耦。
结构类图
应用例子
jdk中的StringBuilder源码中涉及到建造者模式的思想,虽然不是完全一样,但是思想有相同的地方。
Appendable接口
定义了多个append方法,充当抽象建造者的角色(Builder),定义了抽象方法
AbstractStringBuilder实现了Appendable接口方法,它其实已经是建造者了,完成了建造方法的实现,只是不能实例化。
StringBuilder类,继承AbstractStringBuilder,即充当指挥者(Director),又充当具体建造者(ConcreteBuilder)。
注意事项
1)建造者模式所创建的产品一般具有较多的共同点,其组成部分相似,如果产品之间差异性很大,则不适合用建造者模式。
2)如果产品内部变化复杂,也要慎重考虑建造者模式,因为可能会导致需要定义很多具体建造者类来实现这种变化。
建造者模式vs抽象工厂模式
抽象工厂模式实现对产品家族的创建,一个产品家族是这样的一系列产品:具有不同分类维度的产品组合(比如披萨根据主要原料、地域风味),采用抽象工厂模式不需要关心构建过程,只关心什么产品由什么工厂生产即可。而建造者模式则是要求按照指定的蓝图建造产品,它的主要目的是通过组装零配件而产生一个新产品。
适配器模式
定义
属于结构型模式,将某个类的接口转换成客户端期望的另一个接口表示,主要目的是兼容性,让原本因接口不匹配不能一起工作的两个类可以协同工作,别名为包装器(Wrapper)。
主要有两类:类适配器、对象适配器、接口适配器模式。
比如一些插座只支持两孔,可以买多个多功能转换插头(也就是类比于适配器),这样可以供各种电器使用。
工作原理
1)将一个类的接口转换成另一种接口,让原本接口不兼容的类可以兼容。
2)从用户的角度看不到被适配者,是解耦的。
3)用户调用适配器转化出来的目标接口方法,适配器再调用被适配者的相关接口方法。
4)用户收到反馈效果,感觉只是和目标接口交互。
类适配器模式
原理
Adapter类通过继承src类,实现dst类接口,完成src->dst的适配。
例子
220V交流电相当于src,充电器相当于适配器,而手机支持的5v直流电是dst。
结构类图
代码
定义被适配者(src)
public class Voltage220V {
public int output220V() {
int src = 220;
System.out.println("电压=" + src + "伏");
return src;
}
}
定义接口Voltage5V(dst)
public interface Voltage5V {
public int output5V();
}
定义适配器(Adapter)
public class VoltageAdapter extends Voltage220V implements Voltage5V{
@Override
public int output5V() {
int srcV = output220V();
int dstV = srcV / 44;
return dstV;
}
}
注意事项
1)Java是单继承机制,所以类适配器需要继承src类这一点算是一个缺点,因为这要求dst必须是接口,有一定的局限性。
2)src类的方法在Adapter中都会暴露出来,也增加使用的成本。
3)由于其继承了src类,所以它可以根据需求重写src类的方法,使得Adapter灵活性增强了。
对象适配器模式
原理
基本思路与类适配器相同,只是修改Adapter类,不继承src类,而是持有src类的实例(聚合),以解决兼容性问题。(合成复用原则指出:在系统中尽量使用关联关系来替代继承关系,详情见设计原则)
例子
结构类图
代码
其他一样,就是修改了Adapter
public class VoltageAdapter implements Voltage5V {
private Voltage220V voltage220V;
public VoltageAdapter(Voltage220V voltage220V) {
this.voltage220V = voltage220V;
}
@Override
public int output5V() {
int dstV = 0;
if (voltage220V != null) {
int srcV = voltage220V.output220V();
System.out.println("使用对象适配器");
dstV = srcV / 44;
}
return dstV;
}
}
注意事项
1)和类适配器思想相同,不过遵守合成复用原则,使用组合/聚合替代继承,解决了类适配器必须继承src从而导致dst只能是接口的局限性问题。
2)更灵活,耦合性更低。
接口适配器模式
原理
也称为缺省适配器模式
1)当不需要全部实现接口提供的方法时,可先设计一个抽象类实现接口,并为该接口中每一个方法提供一个默认实现(空方法),那么该抽象类的子类可有选择地覆盖父类得到某些方法来实现需求。(安卓开发中一些监听器相关源码函数经常使用到该模式,用什么监听函数就自己覆盖相应方法去实现)比如我们自己具体使用覆盖时如下格式:
new AmimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator amimator) {
//具体实现
}
}
2)适用于一个接口不想使用其所有方法的情况。
例子
结构类图
代码
定义一个接口,有好多方法,但不会使用其全部
public interface InterfaceExam {
public void m1();
public void m2();
public void m3();
public void m4();
}
定义一个抽象类AbsAdapter,实现接口,为该接口中每一个方法提供一个默认实现(空方法)
public abstract class AbsAdapter implements InterfaceExam{
@Override
public void m1() {
}
@Override
public void m2() {
}
@Override
public void m3() {
}
@Override
public void m4() {
}
}
使用
public class Client {
public static void main(String[] args) {
System.out.println();
AbsAdapter absAdapter = new AbsAdapter() {
//只需要覆盖我们需要使用的接口方法
@Override
public void m1() {
System.out.println("使用了m1方法");
}
};
absAdapter.m1();
}
}
应用例子
SpringMvc中的HandlerAdapter,使用了适配器模式。
SpringMVC相关的源代码分析在这里讲感觉篇幅不够,可能以后会另开一篇,暂时省略,先挖个坑
TODO
注意事项
1)三种命名方式,是根据src是以怎样的形式给到Adapter(在Adapter)来命名的。
2)类适配器:以类给到,在Adapter里,就是将src当做父类继承。
对象适配器:以对象给到,在Adapter里,将src作为一个对象持有(聚合)。
接口适配器:以接口给到,在Adapter里,将src作为一个接口实现。
3)Adapter模式最大的作用还是将原本不兼容的接口融合在一起工作。
4)实际开发中,实现起来不拘泥于我们讲解的三种经典形式。