文章目录
本系列文章共分为六篇:
设计模式(一)设计模式的分类与区别
设计模式(二)创建型模式介绍及实例
设计模式(三)结构型模式介绍及实例
设计模式(四)行为型模式介绍及实例(上)
设计模式(五)行为型模式介绍及实例(下)
设计模式(六)设计模式的常见应用
本篇将介绍创建者模式,创建型模式的主要关注点是“怎样创建对象,使对象的创建与使用分离开来
”。
一、单例模式*
单例模式:指一个类只有一个实例,且该类能自行创建这个实例的一种模式。即:某个类能自行生成全局唯一实例。
- 单例模式特点
1)单例类只有一个实例对象
。
2)该单例对象必须由单例类自行创建
。
3)单例类对外提供一个访问该单例的全局访问点
。 - 单例模式主要角色
1)单例类
:包含一个实例且能自行创建这个实例的类。
2)访问类
:使用单例的类。
1.1 单例模式实现方式
单例模式的实现方式较多,一一介绍。
- 1、懒汉式单例
该模式的特点是类加载时没有生成单例,只有当第一次调用getlnstance方法时才去创建这个单例
。代码示例:
public class Singleton{
private static Singleton instance=null;
//private避免类在外部被实例化
private Singleton(){}
public static synchronized Singleton getInstance(){
//getInstance方法前加同步
if(instance==null){
instance=new Singleton();
}
return instance;
}
}
如果编写的是多线程程序,则不能删除上例代码中的关键字synchronized,否则将存在线程非安全的问题。使用synchronized关键字就可以保证线程安全,但是每次访问时都要同步,会影响性能,且消耗更多的资源,这是懒汉式单例的缺点。
- 2、饿汉式单例
饿汉式单例在类创建的同时就已经创建好一个静态的对象供系统使用
,以后不再改变,所以是线程安全的,可以直接用于多线程而不会出现问题。代码示例:
public class Singleton {
//在调用getInstance之前就创建一个static对象
private static final Singleton instance = new Singleton();
private Singleton (){}
public static Singleton getInstance() {
return instance;
}
}
- 3、双重校验锁单例
该方式既不会在类加载时就初始化单例对象,又合理缩小了synchronized锁的影响范围,安全且在多线程情况下能保持高性能。代码示例:
public class Singleton {
private volatile static Singleton singleton;
private Singleton (){}
public static Singleton getSingleton() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
由于volatile关键字可能会屏蔽掉虚拟机中的一些必要的代码优化,所以运行效率并不是很高。因此一般建议,没有特别的需要,不要使用。
- 4、静态内部类单例
该方式通过创建一个静态内部类,在静态内部类中实例化单例,进而达到了延迟实例化的目的
。因为外部类加载时并不需要立即加载内部类,内部类不被加载则不去初始化单例,故而不占内存。同时,静态内部类中final修饰单例,也达到了安全的目的
。代码示例:
public class Singleton {
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
private Singleton (){}
public static final Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
- 5、枚举式单例
除了以上四种方式的单例模式,还有用枚举实现的单例模式。上面的双重校验锁单例和静态内部类单例,在不考虑反射场景下,是没有问题的,但是利用反射可以调用private构造方法,所以此时可以使用一种更安全的单例模式:枚举式单例。这种方式可以反序列化
。
之所以可以使用枚举来实现单例,是因为:枚举中的构造方法默认为private
,其被设计成是单例模式,JVM为了保证每一个枚举类元素的唯一实例,不允许外部通过new来创建的,所以会把构造函数设计成private,防止用户生成实例。示例代码:
public class Singleton {
}
public enum SingletonEnum {
SINGLETON;
private Singleton instance;
private SingletonEnum(){
instance = new Singleton();
}
public Singleton getInstance(){
return instance;
}
}
//生成单例
Singleton singleton1 = SingletonEnum.SINGLETON.getInstance();
- 几种单例创建方式的比较
类别 | 特点 |
---|---|
饿汉式 | 类初始化时创建单例,线程安全,适用于单例占内存小的场景,否则推荐使用懒汉式延迟加载 |
懒汉式 | 需要创建单例实例的时候再创建,需要考虑线程安全(性能不太好) |
双重检验锁 | 效率高,线程安全 |
静态内部类方式 | 可以同时保证延迟加载和线程安全 |
枚举方式 | 使用枚举除了线程安全和防止反射调用构造器之外,还提供了自动序列化机制,防止反序列化的时候创建新的对象 |
1.2 单例模式应用场景
1)某类只要求生成一个对象(比如唯一序列号)
的时候。
2)当对象需要被共享(比如例如一个Web页面上的计数器
)的场合,可以不用把每次刷新都记录到数据库中,使用单例模式保持计数器的值。由于单例模式只允许创建一个对象,共享该对象可以节省内存,并加快对象访问速度。
3)当某类需要频繁实例化,而创建的对象又频繁被销毁
的时候,如多线程的线程池、网络连接池
等。
4)需要定义大量的静态常量和静态方法(如工具类)的环境,可以采用单例模式(当然,也可以直接声明为static的方式)。
二、工厂方法模式*
被创建的对象称为产品
,把创建产品的对象称为工厂
。如果要创建的产品不多,只要一个工厂类就可以完成,这种模式叫“简单工厂模式"。本节介绍的“工厂方法模式”是对简单工厂模式的进一步抽象化,其好处是可以使系统在不修改原来代码的情况下引进新的产品。
工厂方法模式:定义一个创建产品对象的工厂接口,将产品对象的实际创建工作推迟到具体子类工厂当中
。即:由实现工厂接口的具体子类工厂决定生成什么产品(产品是一类的)。
- 工厂方法模式的优点
1)只需要知道具体工厂的名称就可得到所要的产品,无须知道产品的具体创建过程。
2)在系统增加新的产品时只需要添加具体产品类和对应的具体工厂类,无须对原工厂进行任何修改,满足开闭原则。 - 工厂方法模式的缺点
1)每次增加一个产品时,都需要增加一个具体类和对象实现工厂,使得系统中类的个数成倍增加,在一定程度上增加了系统的复杂度,同时也增加了系统具体类的依赖。这个缺点可以通过和反射结合来改进
。
2.1 工厂方法模式实现方式
工厂方法包括4部分,分别为:抽象工厂、具体工厂、抽象产品和具体产品。
抽象工厂:最顶级的接口类,对外提供入口,其他的工厂都需要实现其接口,创建不同的具体工厂类。
具体工厂:该类需实现抽象工厂,并且实现抽象工厂中的方法,在方法中导入产品依赖,就可以根据产品的不同创造出不同的产品。
抽象产品:最顶级的接口类,对工厂提供入口,在接口中提供方法,统一定义该产品的功能。
具体产品:该类实现与抽象产品,需要重写抽象产品接口,实现具体产品的功能。
以不同公司可以制作不同类型电影为例。抽象产品(Movie)有两种实现类:具体产品1(ActionMovie)和具体产品2(ComedyMovie)。抽象工厂(Factory)也有两种实现类:具体工厂1(FoxFactory)和具体工厂2(WBPFactory)。代码示例:
//抽象产品:电影
public interface Movie {
void play();
}
//具体产品1:动作电影
public class ActionMovie implements Movie {
private String factory;
public ActionMovie(String factory){
this.factory = factory;
}
public void play() {
System.out.println(factory+" ActionMovie is playing");
}
}
//具体产品2:喜剧电影
public class ComedyMovie implements Movie {
private String factory;
public ComedyMovie(String factory){
this.factory = factory;
}
public void play() {
System.out.println(factory+" ComedyMovie is playing");
}
}
//抽象工厂
public interface Factory {
Movie getActionMovie();
Movie getComedyMovie();
}
//具体工厂1:Fox公司
public class FoxFactory implements Factory{
public Movie getActionMovie(){ return new ActionMovie("Fox"); }
public Movie getComedyMovie(){ return new ComedyMovie("Fox"); }
}
//具体工厂2:WBP公司
public class WBPFactory implements Factory{
public Movie getActionMovie(){ return new ActionMovie("WBP"); }
public Movie getComedyMovie(){ return new ComedyMovie("WBP"); }
}
//测试类
public class FactoryTest {
public static void main(String[] args) {
Factory foxFactory = new FoxFactory();
Movie actionMovie = foxFactory.getActionMovie();
actionMovie.play(); //Fox ActionMovie is playing
Movie comedyMovie = foxFactory.getComedyMovie();
comedyMovie.play(); //Fox ComedyMovie is playing
Factory WBPFactory = new WBPFactory();
actionMovie = WBPFactory.getActionMovie();
actionMovie.play(); //WBP ActionMovie is playing
comedyMovie = WBPFactory.getComedyMovie();
comedyMovie.play(); //WBP ComedyMovie is playing
}
}
2.2 工厂方法模式应用场景
1)客户只知道创建产品的工厂名,而不知道具体的产品名
。
2)创建对象的任务由多个具体子工厂中的某一个完成
,而抽象工厂只提供创建产品的接口
。
3)客户不关心创建产品的细节,只关心产品的品牌
。
4)jdbc 连接数据库,降低对象的产生和销毁。
三、抽象工厂模式
上一节的工厂方法模式只能产生一类产品(一种顶级抽象接口),如果需要产生多类产品/产品族(将同一个具体工厂所生产的位于不同等级的一组产品称为一个产品族),就要使用抽象工厂模式。也就是说,抽象工厂相对于工厂模式,差异之处在于能生产多等级产品。
要达到生产一个产品族的目的话,其实也能想到:在一个抽象工厂的层级上再抽象出一层定义生产一个产品组的顶级抽象工厂
。可以参考这张表来理解:
抽象工厂模式:一种为访问类提供一个创建一组相关或相互依赖对象的接口,且访问类无须指定所要产品的具体类就能得到同族的不同等级的产品的模式结构。即:由实现工厂接口的具体子类工厂决定生成什么产品(产品是多类的)。
- 抽象工厂模式优点
1)可以在类的内部对产品族中相关联的多等级产品共同管理,而不必专门引入多个新的类来进行管理。
2)当增加一个新的产品族时不需要修改原代码,满足开闭原则。 - 抽象工厂模式缺点
当产品族中需要增加一个新的产品时,所有的工厂类都需要进行修改。
3.1 抽象工厂模式实现方式
抽象工厂模式通常包含四个必要角色和一个可选角色:
1)抽象工厂:定义一个接口或抽象类,它声明了一组用于创建相关产品对象的方法,这些方法的返回值通常是一个抽象产品。
2)具体工厂:它实现了抽象工厂中声明的创建产品对象的方法,同一产品等级下根据不同的产品参数,创建一个具体产品对象。
3)抽象产品:定义一个接口或抽象类,表示同类产品,在抽象产品中声明了产品所具有的方法。
4)具体产品:它定义了抽象产品下所拥有的具体产品,实现了抽象产品接口中定义的业务方法。
5)工厂创造器(可选角色):提供一个静态方法,根据传入不同的工厂类型参数,创造一个具体的工厂类,返回值用抽象工厂接收。
可以看粗,在抽象工厂模式中,也可以是和工厂模式一样有4个角色,也可以有5个角色。两者最大的不同的是:工厂模式产生的产品是一类的,抽象工厂模式产生的产品是多类的。
代表“形状”的抽象产品层和具体产品层:
public interface Shape {
void draw();
}
class Square implements Shape{
@Override
public void draw() {
System.out.println("我是正方形");
}
}
class Circle implements Shape{
@Override
public void draw() {
System.out.println("我是圆形");
}
}
代表“颜色”的抽象产品层和具体产品层:
public interface Color {
void fill();
}
class Red implements Color{
@Override
public void fill() {
System.out.println("我是红色");
}
}
class Blue implements Color{
@Override
public void fill() {
System.out.println("我是蓝色");
}
}
抽象工厂和具体工厂层:
//抽象工厂类
public interface AbstractFactory {
Shape getShape(String shape);
Color getColor(String color);
}
//形状工厂类
class ShapeFactory implements AbstractFactory {
@Override
public Shape getShape(String shape){
if("square".equalsIgnoreCase(shape)){
return new Square();
} else if("circle".equalsIgnoreCase(shape)){
return new Circle();
}else{
return null;
}
}
@Override
public Color getColor(String color) {
return null;
}
}
//颜色工厂类
class ColorFactory implements AbstractFactory {
@Override
public Shape getShape(String shape){
return null;
}
@Override
public Color getColor(String color) {
if("red".equalsIgnoreCase(color)){
return new Red();
} else if("blue".equalsIgnoreCase(color)){
return new Blue();
}else{
return null;
}
}
}
工厂创造器:
public class FactoryProducer {
//工厂创造器
public static AbstractFactory createFactory(String choice){
if("shape".equalsIgnoreCase(choice)){
return new ShapeFactory();
} else if("color".equalsIgnoreCase(choice)){
return new ColorFactory();
}else{
return null;
}
}
}
测试类:
public class Test {
public static void main(String[] args) {
//测试形状工厂
AbstractFactory absFactory01=FactoryProducer.createFactory("shape");
absFactory01.getShape("circle").draw(); //我是圆形
//测试颜色工厂
AbstractFactory absFactory02=FactoryProducer.createFactory("color");
absFactory02.getColor("red").fill(); //我是红色
}
}
3.2 抽象工厂模式应用场景
1)当需要创建的对象是一系列相互关联或相互依赖的产品族
时。
2)系统中有多个产品族,但每次只使用其中的某一族产品。
3)系统中提供了产品的类库,且所有产品的接口相同,客户端不依赖产品实例的创建细节和内部结构。
四、建造者模式
建造者模式常常用来创建复杂的产品,此模式中,会有一个专业的建造者(Build)类负责复杂产品(可以进行分部构建)的具体创建过程
,从而达到对象创建和使用分离的目的。
建造者模式:指将一个复杂对象的构造与它的表示分离
,使同样的构建过程可以创建不同的表示
。即:将复杂的对象分解为多个简单的对象,然后分步骤构建。
-
建造者模式的优点
1)各个具体的建造者相互独立,有利于系统的扩展。
2)客户端不必知道产品内部组成的细节,便于控制细节风险。
3)可以对构造过程进行更精细化的控制。 -
建造者模式的缺点
1)产品的组成部分必须相同,这限制了其使用范围。
2)如果产品的内部变化复杂,该模式会增加很多的建造者类。 -
建造者模式主要角色
1、产品(Product):需要生成的类对象。
2、抽象建造者(Builder):它是一个包含创建产品各个子部件的抽象方法的接口,通常还包含一个返回复杂产品的方法。
3、具体建造者(ConcreteBuilder):实现 Builder 接口,完成复杂产品的各个部件的具体创建方法。
4、指挥者(Director):它调用建造者对象中的部件构造与装配方法完成复杂对象的创建,在指挥者中不涉及具体产品的信息。
建造者模式结构图:
4.1 建造者模式实现方式
此处以一个较复杂的产品创建过程为例,该产品包括partA、partB、partC三个模块,这三个模块需要分开创建,代码示例:
//包含复杂组件的产品类
class Product{
private String partA;
private String partB;
private String partC;
public void setPartA(String partA){
this.partA=partA;
}
public void setPartB(String partB){
this.partB=partB;
}
public void setPartC(String partC){
this.partC=partC;
}
public void show(){
System.out.println("partA:"+partA+"\npartB:"+partB+"\npartC:"+partC);
}
}
//抽象建造者,一般定义各个组件的建造过程和返回对象接口
public abstract class Builder{
//创建产品对象
protected Product product=new Product();
public abstract void buildPartA();
public abstract void buildPartB();
public abstract void buildPartC();
//返回产品对象
public Product getResult(){
return product;
}
}
//具体建造者
public class ConcreteBuilder extends Builder{
public void buildPartA(){
product.setPartA("AAA");
}
public void buildPartB(){
product.setPartB("BBB");
}
public void buildPartC(){
product.setPartC("CCC");
}
}
//指挥者
public class Director{
private Builder builder;
public Director(Builder builder){
this.builder=builder;
}
//产品构建与组装方法
public Product construct(){
builder.buildPartA();
builder.buildPartB();
builder.buildPartC();
return builder.getResult();
}
}
//测试类
public class BuildTest{
public static void main(String[] args){
Builder builder=new ConcreteBuilder();
Director director=new Director(builder);
//此处的例子还有更常见的写法,如:
//builder.buildPartA().buildPartB().buildPartC();
Product product=director.construct();
product.show();
}
}
结果示例:
partA:AAA
partB:BBB
partC:CCC
4.2 建造者模式应用场景
1)创建的对象较复杂,由多个部件构成,各部件面临着复杂的变化,但构件间的建造顺序是稳定的。
2)创建复杂对象的算法独立于该对象的组成部分以及它们的装配方式,即产品的构建过程和最终的表示是独立的。
3)相同的方法,不同的执行顺序,产生不同的事件结果时,可以采用建造者模式。
4)产品类非常复杂,或者产品类中的调用顺序不同产生了不同的效能,这个时候使用建造者模式非常合适。
五、原型模式*
在有些系统中,存在大量相同或相似对象的创建问题
,如果用传统的构造函数来创建对象,会比较复杂且耗时耗资源,此时就需要用原型模式来创建对象。
Java是自带原型模式的。要实现原型模式需要实现Cloneable接口,重写clone接口。
原型模式:用一个已经创建的实例作为原型,通过复制该原型对象来创建一个和原型相同或相似的新对象。即:利用现有对象创建相同或相似对象。
- 原型模式主要角色
1、抽象原型类(Cloneable):声明克隆方法的接口,是所有具体原型类的公共父类,它可以是抽象类也可以是接口。
2、具体原型类(ConcretePrototype):它实现在抽象原型类中声明的克隆方法,在克隆方法中返回自己的一个克隆对象。
3、Client(客户类):在客户类中,让一个原型对象克隆自身从而创建一个新的对象,只需要直接实例化或通过工厂方法等方式创建一个原型对象,再通过调用该对象的克隆方法即可得到多个相同的对象。 - 原型模式的优点
1)性能提高
。原型模式是在内存二进制流的拷贝,要比直接new一个对象性能好很多,特别是要在一个循环体内产生大量的对象时,原型模式可以更好地体现其优点。
2)逃避构造函数的约束
。这既是它的优点也是缺点,直接在内存中拷贝,构造函数是不会执行的。优点就是减少了约束,缺点也是减少了约束,需要在实际应用时考虑。 - 缺点
对于现有类,进行克隆可能实现起来较为复杂。
5.1 原型模式实现方式
原型模式的实现,有两种方式:深克隆与浅克隆。浅克隆是创建一个新对象,新对象的属性和原来对象完全相同,对于非基本类型属性,仍指向原有属性所指向的对象的内存地址;深克隆是创建一个新对象,属性中引用的其他对象也会被克隆,不再指向原有对象地址。
注意: 使用原型模式时,引用的成员变量必须满足两个条件才不会被拷贝:一 是类的成员变量,而不是方法内变量;二是必须是一个可变的引用对象,而不是 一个原始类型或不可变对象。
- 1、浅克隆实现
//抽象原型,原生的Cloneable接口
public interface Cloneable {
}
//具体原型
public class ConcretePrototype implements Cloneable {
private String name;
private int id;
public Object clone(){
ConcretePrototype prototype = null;
try {
prototype = (ConcretePrototype)super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return prototype;
}
public String getName() { return name; }
public void setName(String name) {
this.name = name;
}
public int getId() { return id; }
public void setId(int id) {
this.id = id;
}
}
//客户类
public class PrototypeTest {
public static void main(String[] args) {
ConcretePrototype prototype = new ConcretePrototype();
prototype.setName("exp");
ConcretePrototype prototype2 = (ConcretePrototype) prototype.clone();
//prototype is equals prototype2?false
System.out.println("prototype is equals prototype2?"+prototype.equals(prototype2));
}
}
- 2、深克隆实现
深克隆可以用序列化的方式来实现(深克隆还可以通过在clone方法里再clone一遍引用对象实现,当然,此时所引用的对象也要实现Cloneable接口
),即实现Serializable接口。示例代码:
/*具体原型*/
public class ConcretePrototype implements Serializable {
private String name;
private int id;
public ConcretePrototype clone(){
//将对象写入流中
ByteArrayOutputStream bao = new ByteArrayOutputStream();
ObjectOutputStream oos;
ObjectInputStream ois;
try {
oos = new ObjectOutputStream(bao);
oos.writeObject(this);
//将对象取出来
ByteArrayInputStream bi = new ByteArrayInputStream(bao.toByteArray());
ois = new ObjectInputStream(bi);
try {
return (ConcretePrototype)ois.readObject();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
public String getName() { return name; }
public void setName(String name) {
this.name = name;
}
public int getId() { return id; }
public void setId(int id) {
this.id = id;
}
}
/*测试类*/
public class PrototypeTest {
public static void main(String[] args) {
ConcretePrototype prototype = new ConcretePrototype();
prototype.setName("exp");
prototype.setId(1);
ConcretePrototype prototype2 = (ConcretePrototype) prototype.clone();
// prototype is equals prototype2?false
System.out.println("prototype is equals prototype2?"+prototype.equals(prototype2));
//prototype2.getName():exp,prototype2.getId():1
System.out.println("prototype2.getName():"+prototype2.getName()+",prototype2.getId():"+prototype2.getId());
}
}
同时,当对象之间存在多重嵌套引用时,为了实现深克隆,每一层对象对应的类都必须支持深克隆。
5.2 原型模式应用场景
1)对象之间相同或相似
,常见的情况是:只有几个属性不同。
2)对象的创建过程比较麻烦,但复制比较简单的时候。
3)当要实例化的类是在运行时刻指定时,例如,通过动态装载。
4)资源优化场景
。类初始化需要消化非常多的资源,这个资源包括数据、硬件资源等。
5)性能和安全要求的场景
。通过new产生一个对象需要非常繁琐的数据准备或访问权限,则可以使用原型模式。
6)一个对象多个修改者的场景
。一个对象需要提供给其他对象访问,而且各个调用者可能都需要修改其值时,可以考虑使用原型模式拷贝多个对象供调用者使用。