创建型模式分类
创建型模式分为以下几种。
- 单例(Singleton)模式:某个类只能生成一个实例,该类提供了一个全局访问点供外部获取该实例,其拓展是有限多例模式。
- 原型(Prototype)模式:将一个对象作为原型,通过对其进行复制而克隆出多个和原型类似的新实例。
- 工厂方法(FactoryMethod)模式:定义一个用于创建产品的接口,由子类决定生产什么产品。
- 抽象工厂(AbstractFactory)模式:提供一个创建产品系的接口,其每个子类可以生产一系列相关的产品。
- 建造者(Builder)模式:将一个复杂对象分解成多个相对简单的部分,然后根据不同需要分别创建它们,最后构建成该复杂对象。 以上 5 种创建型模式,除了工厂方法模式属于类创建型模式,其他的全部属于对象创建型模式。下面具体介绍下它们的特点、结构与应用实例。
1.单例模式
- 单例模式的定义 单例(Singleton)模式的定义:指一个类只有一个实例,且该类能自行创建这个实例的一种模式。例如,Windows 中只能打开一个任务管理器,这样可以避免因打开多个任务管理器窗口而造成内存资源的浪费,或出现各个窗口显示内容的不一致等错误。又比如J2EE 标准中的 ServletContext 和 ServletContextConfig、Spring 框架应用中的 ApplicationContext、数据库中的连接池等也都是单例模式。
- 单例模式的特点
1.单例类只有一个实例对象
2.该单例对象必须由单例类自行创建
3.单例类对外提供一个访问该单例的全局访问点 - 单例模式的优点
1.单例模式可以保证内存里只有一个实例,减少了内存的开销。
2.可以避免对资源的多重占用。
3.单例模式设置全局访问点,可以优化和共享资源的访问。 - 单例模式的缺点:
1.单例模式一般没有接口,扩展困难。如果要扩展,则除了修改原来的代码,没有第二种途径,违背开闭原则。
2.在并发测试中,单例模式不利于代码调试。在调试过程中,如果单例中的代码没有执行完,也不能模拟生成一个新的对象。
3.单例模式的功能代码通常写在一个类中,如果功能设计不合理,则很容易违背单一职责原则。 - 单例模式的应用场景
1.需要频繁创建的一些类,使用单例可以降低系统的内存压力,减少 GC。
2.某类只要求生成一个对象的时候,如一个班中的班长、每个人的身份证号等。
3.某些类创建实例时占用资源较多,或实例化耗时较长,且经常使用。
4.某类需要频繁实例化,而创建的对象又频繁被销毁的时候,如多线程的线程池、网络连接池等。 5.频繁访问数据库或文件的对象。
6.对于一些控制硬件级别的操作,或者从系统上来讲应当是单一控制逻辑的操作,如果有多个实例,则系统会完全乱套。
7.当对象需要被共享的场合。由于单例模式只允许创建一个对象,共享该对象可以节省内存,并加快对象访问速度。如 Web 中的配置对象、数据库的连接池等。 - 单例模式的实现
单例模式是设计模式中最简单的模式之一。通常,普通类的构造函数是设置为public的,外部类可以通过“new 构造函数()”来生成多个实例。但是,如果将类的构造函数设为私有的,外部类就无法调用该构造函数,也就无法生成多个实例。这时如果该类需要实例化,类自身必须定义一个静态私有实例,并向外提供一个静态的公有函数用于创建或获取该静态私有实例。
单例模式的实例:
1.懒汉模式
该模式的特点是类加载时没有生成单例,只有当第一次调用 getlnstance 方法时才去创建这个单例。
public class Singleton {
//初始化实例为null
private static Singleton instance = null;
//私有构造方法,内部使用,避免类在外部被实例化
private Singleton() {
}
/公有静态方法,外部使用创建或者获取对象实例。
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
ps:这是最基本的单例模式,思想是在第一次使用的时候才加载实例化对象。这是用时间换取空间的一种做法,但是这样使用在多线程下使用会出现并发问题。
2.双重检查加锁模式
这是懒汉模式的优化,能在多线程下使用。
public class LazySingleton {
//定义一个静态变量instance,保证 instance 在所有线程中同步
private static volatile LazySingleton instance = null;
//私有构造方法,内部使用,避免类在外部被实例化
private LazySingleton() {
}
//公有静态方法,外部使用创建或者获取对象实例。getInstance 方法前加同步
public static synchronized LazySingleton getInstance() {
//如果实例不存在,则new一个实例
if (instance == null) {
instance = new LazySingleton();
}
//存在则直接返回。
return instance;
}
}
ps:使用关键字 volatile 和 synchronized的双重检查模式,避免多线程问题,但是每次访问时都要同步,会影响性能,且消耗更多的资源。
3.饿汉模式
该模式的特点是类加载的时候就会创建一个单例,不需要等到调用的时候在创建。
public class HungrySingleton {
//类加载的时候创建一个静态实例
private static final HungrySingleton instance = new HungrySingleton();
private HungrySingleton() {
}
//直接返回该静态实例
public static HungrySingleton getInstance() {
return instance;
}
}
ps:饿汉式单例在类创建的同时就已经创建好一个静态的对象供系统使用,以后不再改变,所以是线程安全的,可以直接用于多线程而不会出现问题。
4.静态内部类模式
使用静态内部类初始化一个静态的单例实例。
public class Singleton {
//静态内部类
private static class SingletonHoler {
//静态初始化器,由JVM来保证线程安全
private static Singleton instance = new Singleton();
}
//私有构造方法,内部使用,避免类在外部被实例化
private Singleton() {
}
/公有获取实例的方法
public static Singleton getInstance() {
return SingletonHoler.instance;
}
}
ps:这种方式和饿汉模式有一点细微的差别,也就是在Singleton被加载的时候不会进行instance实例的初始化,而是在调用获取实例的getInstance()的方法才会显示装载SingletonHolder类,从而实例化instance。和懒加载目的是一样的,但是是线程安全的。 5.枚举模式
public enum Singleton {
INSTANCE;
public void doSomething() {
System.out.println("doSomething");
}
}
ps:最佳的单例实现模式就是枚举模式。利用枚举的特性,让JVM来帮我们保证线程安全和单一实例的问题。
2.原型模式
- 原型模式的定义 用一个已经创建的实例作为原型,通过复制该原型对象来创建一个和原型相同或相似的新对象。在这里,原型实例指定了要创建的对象的种类。用这种方式创建对象非常高效,根本无须知道对象创建的细节。
- 原型模式的优点
1.Java 自带的原型模式基于内存二进制流的复制,在性能上比直接 new 一个对象更加优良。
2.可以使用深克隆方式保存对象的状态,使用原型模式将对象复制一份,并将其状态保存起来,简化了创建对象的过程,以便在需要的时候使用(例如恢复到历史某一状态),可辅助实现撤销操作。 - 原型模式的缺点
1.需要为每一个类都配置一个 clone 方法。
2.clone 方法位于类的内部,当对已有类进行改造的时候,需要修改代码,违背了开闭原则。
3.当实现深克隆时,需要编写较为复杂的代码,而且当对象之间存在多重嵌套引用时,为了实现深克隆,每一层对象对应的类都必须支持深克隆,实现起来会比较麻烦。因此,深克隆、浅克隆需要运用得当。 - 原型模式的实现
原型模式的克隆分为浅克隆和深克隆。
1.浅克隆:创建一个新对象,新对象的属性和原来对象完全相同,对于非基本类型属性,仍指向原有属性所指向的对象的内存地址。
2.深克隆:创建一个新对象,属性中引用的其他对象也会被克隆,不再指向原有对象地址。
//原型类
class WeeklyLog implements Cloneable
public class WeeklyLog implements Cloneable {
private String name;
private String date;
private String content;
public void setName(String name) {
this.name = name;
}
public void setDate(String date) {
this.date = date;
}
public void setContent(String content) {
this.content = content;
}
public String getName() {
return (this.name);
}
public String getDate() {
return (this.date);
}
public String getContent() {
return (this.content);
}
//克隆方法clone(),此处使用Java语言提供的克隆机制
public WeeklyLog clone() {
Object obj = null;
try {
obj = super.clone();
return (WeeklyLog) obj;
} catch (CloneNotSupportedException e) {
System.out.println("不支持复制!");
return null;
}
}
}
//原型模式的测试类
public static void main(String[] args) throws CloneNotSupportedException {
WeeklyLog log_previous = new WeeklyLog(); //创建原型对象
log_previous.setName("张三");
log_previous.setDate("第1周");
log_previous.setContent("这周工作很忙,每天加班!");
System.out.println("****周报****");
System.out.println("周次:" + log_previous.getDate());
System.out.println("姓名:" + log_previous.getName());
System.out.println("内容:" + log_previous.getContent());
System.out.println("--------------------------------");
WeeklyLog log_new = log_previous.clone(); //调用克隆方法创建克隆对象
log_new.setDate("第2周");
System.out.println("****周报****");
System.out.println("周次:" + log_new.getDate());
System.out.println("姓名:" + log_new.getName());
System.out.println("内容:" + log_new.getContent());
}
输出结果如下:
****周报****
周次:第1周
姓名:张三
内容:这周工作很忙,每天加班!
--------------------------------
****周报****
周次:第2周
姓名:张三
内容:这周工作很忙,每天加班!
通过已创建的第一周周报对象,复制第二周周报,只修改需要修改的地方,这样是不是方便了很多。上面的例子因为只有基本类型,所以属于浅克隆。深克隆的话,必须要把不属于基本类型的属性的对象先克隆出来,使用新的引用指向新的对象,这里不在举例了。
3.工厂模式
- 工厂模式的定义
定义一个创建产品对象的工厂接口,将产品对象的实际创建工作推迟到具体子工厂类当中。这满足创建型模式中所要求的“创建与使用相分离”的特点。
按实际业务场景划分,工厂模式有 3 种不同的实现方式,分别是简单工厂模式、工厂方法模式和抽象工厂模式。 - 简单工厂模式
1.简单工厂模式定义
只要一个工厂类就可以完成所有产品的创建就叫简单工厂模式,也称作静态工厂模式。
2.简单工厂模式优点
a.工厂类包含必要的逻辑判断,可以决定在什么时候创建哪一个产品的实例。客户端可以免除直接创建产品对象的职责,很方便的创建出相应的产品。工厂和产品的职责区分明确。
b.客户端无需知道所创建具体产品的类名,只需知道参数即可。
c.也可以引入配置文件,在不修改客户端代码的情况下更换和添加新的具体产品类。
3.简单工厂模式缺点
a.简单工厂模式的工厂类单一,负责所有产品的创建,职责过重,一旦异常,整个系统将受影响。且工厂类代码会非常臃肿,违背高聚合原则。
b.使用简单工厂模式会增加系统中类的个数(引入新的工厂类),增加系统的复杂度和理解难度
c.系统扩展困难,一旦增加新产品不得不修改工厂逻辑,在产品类型较多时,可能造成逻辑过于复杂
d.简单工厂模式使用了 static 工厂方法,造成工厂角色无法形成基于继承的等级结构。
4.简单工厂模式的角色
a.简单工厂(Factory):是简单工厂模式的核心,负责实现创建所有实例的内部逻辑。工厂类的创建产品类的方法可以被外界直接调用,创建所需的产品对象。
b.抽象产品(Ball):是简单工厂创建的所有对象的父类,负责描述所有实例共有的公共接口。
c.具体产品(FootBall):是简单工厂模式的创建目标。
5.简单工厂模式的实现
//产品抽象-球类
public interface Ball{
public void create();
}
//产品-足球
public class FootBall implements Ball{
@Override
public void create(){
System.out.println("生产足球");
}
}
//产品-篮球
public class BasketBall implements Ball{
@Override
public void create(){
System.out.println("生产篮球");
}
}
//创建工厂类:
public class Factory {
//生产产品的静态工厂方法
public static Ball createBall(int type) {
switch (type) {
case 1:
return new FootBall();
case 2:
return new BasketBall();
default:
break;
}
return null;
}
}
//客户端
public static void main(String[] args) {
Ball ball1 = Factory.createBall(1);
Ball ball2 = Factory.createBall(2);
ball1.create();
ball2.create();
}
一个简单的工厂模式,只有一个工厂,生产球类的,客户端只需要知道生产什么球类传什么参数即可。不需要知道怎么生产。因为只有一个工厂,可以写成静态工厂方法。
- 工厂方法模式
1.工厂方法模式定义
工厂方法模式是对简单工厂模式的进一步抽象化,其好处是可以使系统在不修改原来代码的情况下引进新的产品。
2.工厂方法模式优点
a.客户端只需要知道具体工厂的名称就可得到所要的产品,无须知道产品的具体创建过程。
b.灵活性增强,对于新产品的创建,只需多写一个相应的工厂类。
c.典型的解耦框架。高层模块只需要知道产品的抽象类,无须关心其他实现类,满足迪米特法则、依赖倒置原则和里氏替换原则。 3.工厂方法模式缺点 a.类的个数容易过多,增加复杂度
b.增加了系统的抽象性和理解难度
c.抽象产品只能生产一种产品,此弊端可使用抽象工厂模式解决。 4.工厂方法模式的角色 a.抽象工厂(AbstractFactory):提供了创建产品的接口,调用者通过它访问具体工厂的工厂方法 createBall() 来创建产品。
b.具体工厂(Factory1):主要是实现抽象工厂中的抽象方法,完成具体产品的创建。
c.抽象产品(Ball):定义了产品的规范,描述了产品的主要特性和功能。
d.具体产品(FootBall):实现了抽象产品角色所定义的接口,由具体工厂来创建,它同具体工厂之间一一对应。
5.工厂方法模式的实现
//产品抽象-球类
public interface Ball{
public void create();
}
//产品-足球
public class FootBall implements Ball{
@Override
public void create(){
System.out.println("生产足球");
}
}
//产品-篮球
public class BasketBall implements Ball{
@Override
public void create(){
System.out.println("生产篮球");
}
}
//抽象工厂
public interface AbstractFactory {
public Ball createBall();
}
//工厂1
public class Factory1 implements AbstractFactory {
@Override
public Ball createBall() {
return new FootBall();
}
}
//工厂2
public class Factory2 implements AbstractFactory {
@Override
public Ball createBall() {
return new BasketBall();
}
}
//客户端
public static void main(String[] args) {
Ball ball1= new Factory1().createBall();
Ball ball2= new Factory2().createBall();
ball1.create();
ball2.create();
}
工厂方法模式比简单工厂方法的优点是,把工厂抽象出来,每个产品对应不同的工厂,而不是所有产品对应一个工厂,在新增产品的时候只需要新增对象的实现工厂就行了,不需要对原工厂做修改。
- 抽象工厂模式
1.抽象工厂模式定义
是一种为访问类提供一个创建一组相关或相互依赖对象的接口,且访问类无须指定所要产品的具体类就能得到同族的不同等级的产品的模式结构。
通俗的说抽象工厂模式是工厂方法模式的升级版本,工厂方法模式只生产一个等级的产品,而抽象工厂模式可生产多个等级的产品。比如工厂方法模式只能生产球类,抽象工厂模式能生产球类还能生产球员等。
2.抽象工厂模式优点 a.可以在类的内部对产品族中相关联的多等级产品共同管理,而不必专门引入多个新的类来进行管理。
b.当需要产品族时,抽象工厂可以保证客户端始终只使用同一个产品的产品组。
c.抽象工厂增强了程序的可扩展性,当增加一个新的产品族时,不需要修改原代码,满足开闭原则。
3.抽象工厂模式缺点 a.当产品族中需要增加一个新的产品时,所有的工厂类都需要进行修改。增加了系统的抽象性和理解难度。
4.抽象工厂模式角色 a.抽象工厂(AbstractFactory):提供了创建产品的接口,它包含多个创建产品的方法 newProduct(),可以创建多个不同等级的产品。
b.具体工厂(Factory1):主要是实现抽象工厂中的多个抽象方法,完成具体产品的创建。
c.抽象产品(Ball,Person):定义了产品的规范,描述了产品的主要特性和功能,抽象工厂模式有多个抽象产品。
d.具体产品(FootBall,Man):实现了抽象产品角色所定义的接口,由具体工厂来创建,它同具体工厂之间是多对一的关系。 5.抽象工厂模式实现
//产品抽象-球类
public interface Ball{
public void create();
}
//产品-足球
public class FootBall implements Ball{
@Override
public void create(){
System.out.println("生产足球");
}
}
//产品-篮球
public class BasketBall implements Ball{
@Override
public void create(){
System.out.println("生产篮球");
}
}
//产品抽象-人类
public interface Person {
public void create();
}
//产品-男人
public class Man implements Person{
@Override
public void create(){
System.out.println("男人");
}
}
//产品-女人
public class WoMan implements Person{
@Override
public void create(){
System.out.println("女人");
}
}
//抽象工厂
public interface AbstractFactory {
public Ball createBall();
public Person createPerson();
}
//工厂1
public class Factory1 implements AbstractFactory {
@Override
public Ball createBall() {
return new FootBall();
}
@Override
public Person createPerson() {
return new Man();
}
}
//工厂2
public class Factory2 implements AbstractFactory {
@Override
public Ball createBall() {
return new BasketBall();
}
@Override
public Person createPerson() {
return new WoMan();
}
}
//客户端
public static void main(String[] args) {
//工厂1
Factory1 factory1 = new Factory1();
//工厂2
Factory2 factory2 = new Factory2();
//工厂1生产了足球
Ball ball1= factory1.createBall();
ball1.create();
//工厂2生产了篮球
Ball ball2= factory2.createBall();
ball2.create();
//工厂1生产了男人
Person p1 = factory1.createPerson();
p1.create();
//工厂2生产了女人
Person p2 = factory2.createPerson();
p2.create();
}
抽象工厂模式和工厂方法模式的区别就是,工厂方法只能生产一类产品,抽象工厂方法能够生产多类产品。抽象工厂模式的扩展有一定的“开闭原则”倾斜性:
1.当增加一个新的产品族时只需增加一个新的具体工厂,不需要修改原代码,满足开闭原则。
2.当产品族中需要增加一个新种类的产品时,则所有的工厂类都需要进行修改,不满足开闭原则。
抽象工厂模式通常适用于以下场景:
1.当需要创建的对象是一系列相互关联或相互依赖的产品族时,如电器工厂中的电视机、洗衣机、空调等。
2.系统中有多个产品族,但每次只使用其中的某一族产品。如有人只喜欢穿某一个品牌的衣服和鞋。
3.系统中提供了产品的类库,且所有产品的接口相同,客户端不依赖产品实例的创建细节和内部结构。
4.建造者模式
- 建造者模式的定义 将一个复杂对象的构造与它的表示分离,使同样的构建过程可以创建不同的表示,这样的设计模式被称为建造者模式。它是将一个复杂的对象分解为多个简单的对象,然后一步一步构建而成。它将变与不变相分离,即产品的组成部分是不变的,但每一部分是可以灵活选择的。
- 建造者模式的优点
1.封装性好,构建和表示分离。
2.扩展性好,各个具体的建造者相互独立,有利于系统的解耦。
3.客户端不必知道产品内部组成的细节,建造者可以对创建过程逐步细化,而不对其它模块产生任何影响,便于控制细节风险。 - 建造者模式的缺点
1.产品的组成部分必须相同,这限制了其使用范围。
2.如果产品的内部变化复杂,如果产品内部发生变化,则建造者也要同步修改,后期维护成本较大。 - 建造者模式的角色
1.产品角色(Ball):它是包含多个组成部件的复杂对象,由具体建造者来创建其各个零部件。
2.抽象建造者(Builder):它是一个包含创建产品各个子部件的抽象方法的接口,通常还包含一个返回复杂产品的方法 getResult()。
3.具体建造者(ConcreteBuilder):实现 Builder 接口,完成复杂产品的各个部件的具体创建方法。
4.指挥者(Director):它调用建造者对象中的部件构造与装配方法完成复杂对象的创建,在指挥者中不涉及具体产品的信息。 - 建造者模式的实现
//产品角色:包含多个组成部件的复杂对象
public class Ball {
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() {
//显示产品的特性
}
}
//抽象建造者:包含创建产品各个子部件的抽象方法
public abstract class Builder {
//创建产品对象
protected Ball ball = new Ball();
public abstract void buildPartA();
public abstract void buildPartB();
public abstract void buildPartC();
//返回产品对象
public Ball getResult() {
return ball;
}
}
//具体建造者:实现了抽象建造者接口
public class ConcreteBuilder extends Builder {
public void buildPartA() {
product.setPartA("建造 PartA");
}
public void buildPartB() {
product.setPartB("建造 PartB");
}
public void buildPartC() {
product.setPartC("建造 PartC");
}
}
//指挥者:调用建造者中的方法完成复杂对象的创建。
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 static void main(String[] args) {
Builder builder = new ConcreteBuilder();
Director director = new Director(builder);
Ball ball = director.construct();
ball.show();
}
建造者模式唯一区别于工厂模式的是针对复杂对象的创建。也就是说,如果创建简单对象,通常都是使用工厂模式进行创建,而如果创建复杂对象,就可以考虑使用建造者模式
建造者模式主要适用于以下应用场景:
1.相同的方法,不同的执行顺序,产生不同的结果。
2.多个部件或零件,都可以装配到一个对象中,但是产生的结果又不相同。
3.产品类非常复杂,或者产品类中不同的调用顺序产生不同的作用。
4.初始化一个对象特别复杂,参数多,而且很多参数都具有默认值。
总结,建造者模式和工厂模式的区别
- 造者模式更加注重方法的调用顺序,工厂模式注重创建对象。
- 创建对象的力度不同,建造者模式创建复杂的对象,由各种复杂的部件组成,工厂模式创建出来的对象都一样
- 关注重点不一样,工厂模式只需要把对象创建出来就可以了,而建造者模式不仅要创建出对象,还要知道对象由哪些部件组成。
- 建造者模式根据建造过程中的顺序不一样,最终对象部件组成也不一样。