Design Pattern - Prototype Pattern && Factory Method Pattern

Prototype Pattern

Intro

使用原型实例指定创建对象的种类,然后通过拷贝这些原型来创建新的对象

原型模式(Prototype Pattern)是创建型模式的一种。通过复制一个已经存在的实例来返回新的实例,而不是新建实例。被复制的实例就是原型

UML图

​ 这种模式是实现了一个原型接口,该接口用于创建当前对象的克隆。Prototype是原型类,ConcretePrototype是子类,Client是调用类。

Example

Prototype类需要具备两个条件

  • 实现Cloneable接口

    在Java中的Cloneable接口的作用只有一个:在运行时通知虚拟机可以安全地在实现了此接口的类上使用clone方法。

    通过该方法进行对象的拷贝,Java提供了一个Cloneable接口来标示这个对象是可拷贝的。Cloneable里面一个方法都没有的,这个接口只是一个标记作用,在JVM中具有这个标记的对象有可能被拷贝。

    在JVM中,只有实现了这个接口的类才可以被拷贝,否则会抛出CloneNotSupportedException异常。

  • 重写Object类中的clone方法

    Java中所有类的父类是Object类,Object有个clone方法,作用是返回对象的一个拷贝,但是其作用于是protected的,一般类无法调用,因此Prototype类需要将clone方法的作用于修改为public

浅拷贝和深拷贝

浅拷贝:clone只是拷贝本对象,其对象内部的数组、引用对象、引用类型都不拷贝,还是指向原型的内部地址。两个对象的地址是一样的

1
2
3
4
5
public class Prototype implements Cloneable{
public Object clone() throw CloneNotSupportedException{
return super.clone();
}
}
1
2
3
4
5
public class ConcretePrototype1 extends Prototype{
public Object clone() throw CloneNotSupportedException{
return (ConcretePrototype1) super.clone();
}
}
1
2
3
4
5
public class ConcretePrototype2 extends Prototype{
public Object clone() throw CloneNotSupportedException{
return (ConcretePrototype2) super.clone();
}
}

深拷贝:将对象复制后,基本数据类型学和引用类型都重新创建。两个对象的地址不一样

1
2
3
public interface Prototype{
public Object clone();
}
1
2
3
4
5
6
7
public class ConcretePrototype1 implements Prototype{
@Override
public Object clone(){
Prototype prototype = new ConcretePrototype1();
return prototype;
}
}
1
2
3
4
5
6
7
public class ConcretePrototype2 implements Prototype{
@Override
public Object clone(){
Prototype prototype = new ConcretePrototype2();
return prototype;
}
}

使用原型的客户端

1
2
3
4
5
6
7
8
9
10
public class Client{
private Prototype type;
public Client(Prototype type){
this.type = type;
}

public void operation(){
Prototype newPrototype = type.clone();
}
}

如果一个对象中只有基本类型属性,那深拷贝和浅拷贝效果都是一样的,基本类型数据不管是用深还是浅都会被拷贝出一份,但如果对象中包含引用对象属性,那浅拷贝其实这是拷贝了一份引用,而深拷贝确实把整个引用对象都拷贝了一份。

Attention

Advantages

  1. 性能优良 原型模式是在内存二进制流的拷贝,比直接new一个对象性能要好很多
  2. 构造函数不会执行 因为对象的复制是通过调用Object类的clone方法来完成的,直接在内存中复制数据 因此不用调用到类的构造方法
  3. 根据客户端要求实现动态创建对象,客户端不需要知道对象的创建细节,便于代码的维护和拓展。

Usage Scenario

  • 资源优化:类初始化需要消化非常多的资源(包括数据、硬件资源等) 通过原型拷贝避免消耗
  • 通过new 产生的一个对象需要非常繁琐的数据准备或者权限
  • 一个对象多个修改者 :一个对象需要提供给其他对象访问,而且各个调用者都需要修改其值 (保护性拷贝)

final关键字

final类型是不能重新赋值的,所以要使用clone() 类的成员变量上不要加final关键字

在实际项目中,原型模式一般是和工厂方法模式同时出现 通过clone方法创建一个对象,然后由工厂方法提供给调用者。

Factory Method Pattern

Intro

定义一个用于创建对象的接口,让子类决定实例化哪一个类

工厂模式有三种:简单工厂模式 工厂方法模式 抽象工厂模式 这里讲的是第二种

工厂方法(Factory method pattern)使一个类的实例延迟到其子类。封装类中不变的部分,提取其中个性化善变的部分为独立类。

创建对象时不会对客户端暴露创建逻辑,并且是通过使用一个共同的接口来指向新创建的对象。

在工厂方法模式中,抽象产品类Product负责定义产品的共性,实现对事物最抽象的定义;Creator为抽象创建类,具体如何创建产品类是由具体的实现工厂ConcreteProduct完成的。

UMI

Example

抽象产品类:具体产品继承的父类或者实现的接口

1
2
3
4
5
6
public abstract class Product {
public void method1(){
}

public abstract void method2();
}

具体产品类:具体工厂角色所创建的对象就是该角色的实例

1
2
3
4
5
public class ConcreteProduct extends Product {
public void method2(){

}
}

抽象工厂类:工厂方法模式的核心,它与应用程序无关。是具体工厂角色必须实现的接口或者必须继承的父类。

1
2
3
public abstract class Creator {
public abstract <T extends Product> T createProduct(Class<T> c);
}

具体工厂类:与具体业务逻辑有关的代码。由应用程序调用以创建对应的具体产品的对象。负责每个产品。

1
2
3
4
5
6
7
8
9
10
11
12
13
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;
}
}

场景类

1
2
3
4
5
6
7
public class Client {
public static void main(String [] args){
Creator creator = new ConcreteCreator();
Product product = creator.createProduct(ConcreteProduct.class);

}
}
  • 一个抽象产品类,可以派生出多个具体产品类。
  • 一个抽象工厂类,可以派生出多个具体工厂类。
  • 每个具体工厂类,只能创建一个具体产品类的实例。

当有新的产品产生时,只要创建并继承抽象产品 而不用修改任何一个类。比如说抽象产品是tp,具体产品类是tpT470、tpE470 当新产品T480出现时,只需要继承tp这个类,然后定义新的工厂,同样是实现工厂接口,生产具体的产品。

符合开闭原则。对拓展开放;对修改封闭。

Attention

使用场景

  • 日志记录器:记录可能记录到本地硬盘、系统事件、远程服务器等,用户可以选择记录日志到什么地方。
  • 数据库访问:当用户不知道最后系统采用哪一类数据库,以及数据库可能有变化时。
  • 设计一个连接服务器的框架,需要三个协议 “POP3” “IMAP” “HTTP” 可以把这三个作为产品类,共同实现一个接口

优点

  • 良好的封装性,代码结构清晰   
  • 良好的拓展性,在增加产品类的情况下,只要适当地修改具体或扩展工厂类即可
  • 屏蔽产品类。调用者只需要关心产品的接口。如果从数据Mysql切换到Oracle,只要切换驱动名称就行。
  • 典型的解耦框架。高层模块值需要知道产品的抽象类,其他的实现类都不用关心,符合迪米特法则,我们不需要的就是不要去交流;也符合依赖倒置原则,只依赖产品类的抽象;也符合里氏替换原则,使用产品子类替换产品父类。

缺点

  • 每增加一个产品都需要增加一个具体类和对象实现工厂,使系统中类的个数成倍增加。当产品种类非常多的时候会出现大量的与之对应的工厂对象。在一定程度上增加了系统的复杂度,也增加了系统具体类的依赖。

参考链接:

拓展阅读: