1.单例模式
单例模式(Singleton Pattern):确保某一个类只有一个实例,而且自己实例化并向整个系统提供这个实例。
通用类图如下:
通用代码如下:
public class Singleton {
private static final Singleton singleton = new Singleton();
// 限制产生多个对象,如果该类是抽象类,该方法可以省略
private Singleton() {
}
// 获取实例对象
public static Singleton getSingleton() {
return singleton;
}
// 其他方法,尽量是static方法
public static void doSomgting() {}
}
2.工厂方法模式
工厂方法模式(Factory Method Pattern):定义一个用于创建对象的接口,让子类决定实例化哪一个类。工厂方法使一个类的实例延迟到其子类。
通用类图如下:
在工厂方法模式中,抽象产品类Product负责定义产品的共性,实现对事物最抽象的定义;Creator为抽象创建类,也不是抽象工厂,具体如何创建产品类是由具体的实现工厂ConcreteProduct完成的。
1.抽象产品类
public abstract class Product {
// 产品类的公式方法
public void method1() {
// 业务逻辑处理
}
// 抽象方法
public abstract void method2();
}
2.具体产品类
/**
* 具体产品类可以有多个
*/
public class ConcreteProduct extends Product {
// 实现抽象方法
public void method2() {
}
}
3.抽象工厂类
public abstract class Creator {
/**
* 创建一个产品对象,其输入参数类型可以自行设置
* 通常为String、Enum、Class等,当然也可以为空
*/
public abstract <T extends Product> T createProduct(Class<T> c);
}
4.具体工厂类
public class ConcreteCreator extends Creator {
public <T extends Product> T createProduct(Class<T> c) {
Product product = null;
try {
product = (Product) Class.forName(c.getName()).newInstance();
} catch (Exception e) {
// 异常处理
}
return (T) product;
}
}
5.场景类
public class Client {
public static void main(String[] args) {
Creator creator = new ConcreteCreator();
Product product = creator.createProduct(ConcreteProduct.class);
// 继续业务处理
}
}
2.1.工厂方法模式的优点
1).良好的封装性,代码结构清晰。
2).工厂方法模式的扩展性非常优秀。在增加产品类的情况下,只要适当地修改具体或扩展工厂类即可。
3).屏蔽产品类。这一点很重要,产品类的实现如何变化,调用者都不需要关心,它只需要关心产品的接口。如果从数据Mysql切换到Oracle,只要切换驱动名称就行。
4).是典型的解耦框架。高层模块值需要知道产品的抽象类,其他的实现类都不用关心,符合迪米特法则,我们不需要的就是不要去交流;也符合依赖倒置原则,只依赖产品类的抽象;也符合里氏替换原则,使用产品子类替换产品父类。
3.抽象工厂抽象
抽象工厂模式(Abstract Factory Pattern): 为创建一组相关或相互依赖的对象提供一个接口,而且无需指定它们的具体类。
抽象工厂模式是工厂方法模式的升级版本,
4.建造者模式
建造者模式(Builder Pattern): 将一个复杂对象的构建与它的表示分离,使得相同的构建过程可以创建不同的表示。
Product产品类
通常是实现了模板方法模式,也就是有模式方法和基本方法。这类一般是定义产品的基本该有的方法。
Builder抽象建造者
规范产品的组建,一般是由子类实现。
ConcreteBuilder具体建造者
实现抽象类定义的所有方法,并且返回一个组建好的对象。
Director导演类
负责安排已有模块的顺序,然后告诉Builder开始建造,具体如何构建一个产品在这里定义
优点
1).封装性:不用关心具体的模型内部是如何实现的
2).多个建造者独立,容易扩展
使用场景
1).相同的方法,不同的执行顺序,产生不同的事件结果时,可以采用
2).多个部件或零件,都可以装配到一个对象中,但是产生的运行结果又不相同时,可以采用
3).产品类非常复杂,或者产品类中的调用顺序不同产生了不同的效能,可以采用
注意事项:建造者模式关注的是零件类型和装配工艺(顺序),这是它与工厂方法模式最大不同的地方,两者注重点不同
5.原型模式
原型模式(Prototype Pattern): 用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象,而不是通过new关键字产生一个对象。
原型模式的核心是一个clone方法,通过该方法进行对象的拷贝,Java提供了一个Cloneable接口来标示这个对象是可拷贝的。
为什么说是”标示”呢?因为Cloneable里面一个方法都没有的,这个接口只是一个标记作用,在JVM中具有这个标记的对象对有可能被拷贝。那怎么才能从”有可能被拷贝”转换为”可以被拷贝”呢?那就是覆盖clone()方法(因为Java类中所有类的老祖宗是Object,所以覆写的其实是Object类中的clone方法,clone()是native的)。
浅复制:clone只是拷贝本对象,其对象内部的数组、引用对象引用类型都不拷贝,还是指向原生对象的内部元素地址。这种拷贝确实是非常浅,两个对象共享一个私有变量,你改我改大家改。需要注意的是clone方法只有内部的数组和引用对象才不拷贝,其他的原始类型比如int,long,char等都会被拷贝。但对于String类型,java希望代把它认为是基本类型,它是没有clone方法,处理机制也比较特殊,通过字符池(stringpool)在需要的时候才在内存中创建新的字符串,可以把String当做基本类使用。
注意:使用原型模式时,引用的成员变量必须满足两个条件才不会被拷贝;一是类的成员变量,而不是方法内变量;二是必须是一个可变的引用对象,而不是一个原始类型或不可变对象。
深复制:将一个对象复制后,不论是基本数据类型还有引用类型,都是重新创建的。简单来说,就是深复制进行了完全彻底的复制,而浅复制不彻底。
通用源码:
public class Prototype implements Cloneable, Serializable {
private static final long serialVersionUID = 1L;
private String string;
private SerializableObject obj;
/* 浅复制 */
public Prototype clone() {
Prototype prototype = null;
try {
prototype = (Prototype) super.clone();
} catch (CloneNotSupportedException e) {
// 异常处理
}
return prototype;
}
/**
* 要实现深复制,需要采用流的形式读入当前对象的二进制输入,再写出二进制数据对应的对象。
*/
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);
return ois.readObject();
}
public String getString() {
return string;
}
public void setString(String string) {
this.string = string;
}
public SerializableObject getObj() {
return obj;
}
public void setObj(SerializableObject obj) {
this.obj = obj;
}
}
class SerializableObject implements Serializable {
private static final long serialVersionUID = 1L;
}
@org.junit.Test
public void test1(){
Prototype prototype = new Prototype();
while (true) {
Prototype clonePrototype = prototype.clone(); // !!!
// 其他的操作
}
}
原型模式的优点:
1).性能优良
原型模式是在内存二进制流的拷贝,要比直接new一个对象性能好很多,特别是要在一个循环体内产生大量的对象时,原型模式可以更好地体现其优点。
2).构造函数不会执行,要注意使用。
对象拷贝时构造函数确实没有被执行,这点从原理来讲也是可以讲得通的,Object类的clone方法的原理是从内存中(具体地说就是堆内存)以二进制流的方式来进行拷贝,重新分配一个内存块,那构造函数没有被执行也是非常正常的了。
使用场景
1).资源优化场景:类初始化需要消化非常多的资源,这个资源包括数据、硬件资源等。
2).通过new产生一个对象需要非常繁琐的数据准备或访问权限,则可以使用原型模式。
3).一个对象多个修改者的场景,而且各个调用者可能都需要修改其值时,可以用原型模式拷贝多个对象供调用者使用。
在实际项目中,原型模式很少单独出现,一般是和工厂方法模式一起出现,通过clone的方法创建一个对象,然后由工厂方法提供给调用者。原型模式已经与Java融为一体,大家可以拿来使用。
clone与final
对象的clone与对象内的final关键字是有冲突的,正常的,final类型是不能重赋值的。所以要使用clone方法,类的成员变量上不要增加final关键字。