目录
一.创建者模式介绍
二.单例设计模式
2.1 单例模式的结构
2.2 单例模式的实现
2.2.1.1 饿汉式-方式1(静态变量方式)
2.2.1.2 饿汉式-方式2(静态代码块方式)
2.2.2.1 懒汉式-方式1(线程不安全)
2.2.2.2 懒汉式-方式2(线程安全)
2.2.2.3 懒汉式-方式3(双重检察锁)
2.2.2.4 懒汉式-方式4(静态内部类方式)
2.2.3 枚举方式
2.3 存在的问题
2.3.1 问题演示
2.3.2 问题的解决
三.工厂模式
3.1 概述
3.1 简单工厂模式
3.1.1 结构
3.1.2 实现
3.1.3 优缺点
3.1.4 扩展
3.2 工厂方法模式
3.2.1 概念
3.2.2 结构
3.2.3 实现
3.2.4 优缺点
3.3 抽象工厂模式
3.3.1 概述
3.3.2 结构
3.3.3 实现
3.3.4 优缺点
3.3.5 使用场景
3.4 JDK源码解析-Collection.iterator方法
四.原型模式
4.1 概述
4.2 结构
4.3 实现
4.4 案例
4.6 扩展(深克隆)
五.建造者模式
5.1 概述
5.2 结构
5.3 实例
5.4 优缺点
5.5 使用场景
5.6 模式扩展
六.创建者模式对比
6.1 工厂方法模式VS建造者模式
6.2 抽象工厂模式VS建造者模式
一.创建者模式介绍
创建者模式的主要关注点时“怎样创建对象?”,它的主要特点时“将对象的创建与使用分离”。
这样可以降低系统的耦合度,使用者不需要关注对象的创建细节。
创建者模式可以分为:
- 单例模式
- 工厂方法模式
- 抽象工程模式
- 原型模式
- 建造者模式
二.单例设计模式
单例模式时Java中最简单的设计模式之一。这中类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。
2.1 单例模式的结构
单例模式的主要有以下角色:
- 单例类:只能创建一个实例的类
- 访问类:使用单例类
2.2 单例模式的实现
单例设计模式分类两种:
- 饿汉式:类加载就会导致该实例对象被创建。
- 懒汉式:类加载不会导致该单实例对象被创建,而是首次使用该对象时才会创建。
2.2.1.1 饿汉式-方式1(静态变量方式)
Singleton类
package part3.singleton.demo1;
// 饿汉式,静态成员变量
public class Singleton {
//1.私有构造方法,外界就无法方位,就无法创建对象
private Singleton(){}
//2.在本类中创建本类对象
private static Singleton instance = new Singleton();
//3.t提供一个公有的访问方式,让外界获取该对象. static:外界无法创建Singleton对象,所以应该无法调用静态方法。
public static Singleton getInstance(){
return instance;
}
}
Client类
package part3.singleton.demo1;
public class Client {
public static void main(String[] args) {
//创建Singleton类的对象
Singleton instance1 = Singleton.getInstance();
Singleton instance2 = Singleton.getInstance();
//判断获取到的两个是否是同个对象
System.out.println(instance1 == instance2);
}
}
说明:该方式在成员变量声明Singleton类型的静态变量,并创建Singleton类的对象instance。instance对象是随着类的加载而创建的。如果该对象足够大的话,而一直没有使用就会造成内存的浪费。
2.2.1.2 饿汉式-方式2(静态代码块方式)
Singleton类
package part3.singleton.demo2;
// 饿汉式-方式2(静态代码块)
public class Singleton {
//1.私有构造方法
private Singleton(){}
//2.声明Singleton类型的变量
private static Singleton instance; // 初始值为null
//3.在静态代码块中进行赋值
static {
instance = new Singleton();
}
//4.对外界提供获取该类对象的方法
public static Singleton getInstance(){
return instance;
}
}
Client类
package part3.singleton.demo2;
public class Client {
public static void main(String[] args) {
//获取Singleton类的对象
Singleton instance1=Singleton.getInstance();
Singleton instance2=Singleton.getInstance();
//判断两次获取的类对象是否是同一个
System.out.println(instance1==instance2);
}
}
说明:该方式在成员变量声明Singleton类型的静态变量,而对象的创建是在静态代码块中,也是随着类的加载而创建。如果该对象足够大的话,而一直没有使用就会造成内存的浪费。
2.2.2.1 懒汉式-方式1(线程不安全)
Singleton类
package part3.singleton.demo3;
//懒汉式 方式1 线程不安全
public class Singleton {
//私有构造方法
private Singleton(){}
//声明Singleton类型变量instance
private static Singleton instance; //只是声明一个变量,并没有创建对象
//对外界提供访问方式
public static Singleton getInstance(){ // 首次使用无非就是在这个访问函数
//判断instance是否为null,如果为null,说明还没有创建Singleton对象
//如果没有,创建一个并返回,如果有,直接返回
if(instance == null){
//线程1等待,线程2获取到cpu的执行全,也会进入到该判断里面
instance = new Singleton();
}
return instance;
}
}
Client类
package part3.singleton.demo3;
import part3.singleton.demo2.Singleton;
public class Client {
public static void main(String[] args) {
//获取Singleton类对象
Singleton instance1 = Singleton.getInstance();
Singleton instance2 = Singleton.getInstance();
//判断获取的两个对象是否相同
System.out.println(instance1 == instance2);
}
}
说明:从上面代码我们可以看出该方式在成员变量位置声明Singleton类型的静态变量,并没有进行对象的赋值操作,那么什么时候 赋值的呢?当调用getinstance()方法获取Singleton类的对象的时候才创建Singleton类的对象,这样就实现了懒加载的效果但是,如果是多线程环境,会出现线程安全问题。
2.2.2.2 懒汉式-方式2(线程安全)
Singleton类
package part3.singleton.demo4;
//懒汉式 方式1 线程不安全
public class Singleton {
//私有构造方法
private Singleton(){}
//声明Singleton类型变量instance
private static Singleton instance; //只是声明一个变量,并没有创建对象
//对外界提供访问方式
public static synchronized Singleton getInstance(){ // 首次使用无非就是在这个访问函数
//判断instance是否为null,如果为null,说明还没有创建Singleton对象
//如果没有,创建一个并返回,如果有,直接返回
if(instance == null){
//线程1等待,线程2获取到cpu的执行全,也会进入到该判断里面
instance = new Singleton();
}
return instance;
}
}
【加锁】在getInstance函数前面加了关键字synchronized(同步) => 这样就不允许同一时间多线程使用这个函数,就不会出现前面的线程不安全问题。
2.2.2.3 懒汉式-方式3(双重检察锁)
再来讨论以下懒汉模式中加锁的问题,对于getInstance方法来说,绝大部分的操作都是读操作,读操作时线程安全的,所以我们没必让每个线程必须持有锁才能调用该方法,我们需要调整加锁的时机。由此也产生一种新的实现模式:双重检查锁模式。
package part3.singleton.demo5;
//双重检查锁方式
public class Singleton {
//私有构造方法
private Singleton(){}
//声明Singleton类型变量
private static Singleton instance;
//对外界提供公共的访问方式
public static Singleton getInstance(){
//第一次判断,如果instance的值不为null,不需要抢占锁,直接返回对象
if(instance == null){
synchronized (Singleton.class){
//第二次判断
if(instance==null){
instance = new Singleton();
}
}
}
return instance;
}
}
双重检查锁是一种非常好的单例实现模式,解决了单例、性能、线程安全问题,上面的双重检查锁模式看上去完美无缺,其实是存在问题的,在多线程的情况下,可能会出现空指针问题,出现问题的原因是JVM在实例化对象的时候会进行优化和指针重排序操作。
要解决双重检查锁模式带来空指针异常问题,只需要使用volatile关键字,volatile关键字可以保证可见性和有序性。
package part3.singleton.demo5;
//双重检查锁方式
public class Singleton {
//私有构造方法
private Singleton(){}
//声明Singleton类型变量
private static volatile Singleton instance;
//对外界提供公共的访问方式
public static Singleton getInstance(){
//第一次判断,如果instance的值不为null,不需要抢占锁,直接返回对象
if(instance == null){
synchronized (Singleton.class){
//第二次判断
if(instance==null){
instance = new Singleton();
}
}
}
return instance;
}
}
添加volatile关键字之后的双重检查锁模式是一种比较好的单例实现模式,能够保证在多线程的情况下线程安全也不会有性能问题。
2.2.2.4 懒汉式-方式4(静态内部类方式)
静态内部类单例模式中实例由内部类创建,由于JVM在加载外部类的过程中,是不会加载静态内部类的,只有内部类的属性/方法被调用时才会被加载,并初始化其静态属性。静态属性由于被static修饰,保证只被实例化一次,并且严格保证实例化顺序。
//静态内部类方式
public class Singleton {
//私有构造方法
private Singleton(){};
//定义一个静态内部类
private static class SingletonHolder{
//在内部类中声明并初始化外部类的对象
private static final Singleton INSTANCE = new Singleton();
}
//提供一个公共的访问方式
public static Singleton getInstance(){
return SingletonHolder.INSTANCE;
}
}
public class Client {
public static void main(String[] args) {
Singleton instance1 = Singleton.getInstance();
Singleton instance2 = Singleton.getInstance();
System.out.println(instance1 == instance2);
}
}
说明:第一次加载Singleton类时不会去初始化INSTANCE,只有第一次调用getInstance,虚拟机加载SingletonHolder并初始化INSTANCE,这样不仅确保线程安全,也能保证Singleton类的唯一性。
小结:静态内部类单例模式是一种优秀的单例模式,是开源项目中比较常用的一种单例模式。在没有任何锁的情况下,保证了多线程下的安全,并且灭有任何性能影响和空间浪费。
2.2.3 枚举方式
枚举类实现单例模式是极力推荐的单例实现模式,因为枚举类型是线程安全的,并且只会被装载一次,设计则会充分利用了枚举的这个特性来实现单例模式,枚举的写法非常简单,而且枚举类型是所用单例实现中唯一一种不会被破坏的单例实现模式。
// 枚举实现方式
public enum Singleton {
INSTANCE;
}
public class Client {
public static void main(String[] args) {
Singleton instance1 = Singleton.INSTANCE;
Singleton instance2 = Singleton.INSTANCE;
System.out.println(instance1 == instance2);
}
}
如果不考虑内存的问题,枚举就是一种很棒的单例实现方式。
2.3 存在的问题
2.3.1 问题演示
破坏单例模式:
使上面定义的单例类(Singleton)可以创建多个对象,枚举方式除外。有两种方式,分别是序列化和反射。
- 序列化反序列化
Singleton类
public class Singleton implements Serializable {
//私有构造方法
private Singleton(){};
//定义一个静态内部类
private static class SingletonHolder{
//在内部类中声明并初始化外部类的对象
private static final Singleton INSTANCE = new Singleton();
}
//提供一个公共的访问方式
public static Singleton getInstance(){
return SingletonHolder.INSTANCE;
}
}
Client测试类
//测试使用反射破坏单例模式
public class Client {
public static void main(String[] args) throws Exception {
//writeObject2File();
readObjectFromFile();
readObjectFromFile();
}
//从文件读取数据(对象)
public static void readObjectFromFile() throws Exception {
//1.创建对象输入流对象
ObjectInputStream ois=new ObjectInputStream(new FileInputStream("E:\\新建文件夹"));
//2.读取对象
Singleton instance=(Singleton)ois.readObject();
System.out.println(instance);
//3.释放方法
ois.close();
}
//向文件中写数据(对象)
public static void writeObject2File() throws Exception{
//1.获取Singleton对象
Singleton instance=Singleton.getInstance();
//2.创建对象输出流对象
ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("E:\\新建文件夹"));
//3.写对象
oos.writeObject(instance);
//4.释放资源
oos.close();
}
}
上面代码运行结果是false,表型序列化和反序列化已经破坏了单例设计模式。
- 反射
Singleton类
public class Singleton {
//私有构造方法
private Singleton(){}
//定义一个·静态内部类
private static class SingletonHolder{
//在内部类中声明并初始化外部类的对象
private static final Singleton INSTANCE=new Singleton();
}
//提供公开的访问方式
public static Singleton getInstance(){
return SingletonHolder.INSTANCE;
}
}
Client测试类
//反射破坏单例模式
public class Client {
public static void main(String[] args) throws Exception{
//1.获取Singleton的字节码对象
Class clazz=Singleton.class;
//2.获取无参构造对象
Constructor cons = clazz.getDeclaredConstructor();
//3.取消访问检查
cons.setAccessible(true);
//4.创建Singleton对象
Singleton s1 =(Singleton) cons.newInstance();
Singleton s2 =(Singleton) cons.newInstance();
System.out.println(s1==s2);
}
}
2.3.2 问题的解决
- 序列化、反序列化破坏单例模式的解决方法
在Singleton类中添加readResolve()方法,在反序列化时被反射调用,如果定义了这个方法,就返回这个方法的值,如果没有定义,则返回新new出来的对象。
Singleton类
public class Singleton implements Serializable {
//私有构造方法
private Singleton(){};
//定义一个静态内部类
private static class SingletonHolder{
//在内部类中声明并初始化外部类的对象
private static final Singleton INSTANCE = new Singleton();
}
//提供一个公共的访问方式
public static Singleton getInstance(){
return SingletonHolder.INSTANCE;
}
//当进行反序列化时,会自动调用该方法,将方法的返回值直接返回
public Object readResolve(){
return SingletonHolder.INSTANCE;
}
}
Client类
//测试使用反射破坏单例模式
public class Client {
public static void main(String[] args) throws Exception {
//writeObject2File();
readObjectFromFile();
readObjectFromFile();
}
//从文件读取数据(对象)
public static void readObjectFromFile() throws Exception {
//1.创建对象输入流对象
ObjectInputStream ois=new ObjectInputStream(new FileInputStream("E:\\新建文件夹"));
//2.读取对象
Singleton instance=(Singleton)ois.readObject();
System.out.println(instance);
//3.释放方法
ois.close();
}
//向文件中写数据(对象)
public static void writeObject2File() throws Exception{
//1.获取Singleton对象
Singleton instance=Singleton.getInstance();
//2.创建对象输出流对象
ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("E:\\新建文件夹"));
//3.写对象
oos.writeObject(instance);
//4.释放资源
oos.close();
}
}
- 反射方式破解单例的解决方法
SIngleton类
public class Singleton {
private static boolean flag=false;
//私有构造方法
private Singleton(){
synchronized (Singleton.class){
//判断flag的信息是否时true,如果时true。说明非第一次访问,直接抛出异常,否则正常构造就好
if(flag){
throw new RuntimeException("不能创建多个对象");
}
//将flag设置为true
flag=true;
}
}
//定义一个·静态内部类
private static class SingletonHolder{
//在内部类中声明并初始化外部类的对象
private static final Singleton INSTANCE=new Singleton();
}
//提供公开的访问方式
public static Singleton getInstance(){
return SingletonHolder.INSTANCE;
}
}
Client类
//反射破坏单例模式
public class Client {
public static void main(String[] args) throws Exception{
//1.获取Singleton的字节码对象
Class clazz=Singleton.class;
//2.获取无参构造对象
Constructor cons = clazz.getDeclaredConstructor();
//3.取消访问检查
cons.setAccessible(true);
//4.创建Singleton对象
Singleton s1 =(Singleton) cons.newInstance();
Singleton s2 =(Singleton) cons.newInstance();
System.out.println(s1==s2);
}
}
三.工厂模式
3.1 概述
需求:设计一个咖啡点点餐系统
设计一个咖啡类(Coffee),并定义其两个子类(美式咖啡【AmericanCoffee】和拿铁咖啡【LatteCoffee】);再设计一个咖啡店类(CoffeeStore),咖啡店具有点咖啡的功能。
具体类的设计如下:
Coffee类
public abstract class Coffee {
public abstract String getName();
//加糖
public void addsugar(){
System.out.println("加糖");
}
//加奶
public void addMilk(){
System.out.println("加奶");
}
}
AmericanCoffee类
//美式咖啡
public class AmericanCoffee extends Coffee {
public String getName(){
return "美食咖啡";
}
}
LatteCoffee类
//拿铁咖啡
public class LatteCoffee extends Coffee{
public String getName(){
return "拿铁咖啡";
}
}
CoffeeStore类
public class CoffeeStore {
public Coffee orderCoffee(String type) {
//声明Coffe类型的变
Coffee coffee = null;
if("american".equals(type)){
coffee=new AmericanCoffee();
}
else if("latte".equals(type)){
coffee=new LatteCoffee();
}
else{
throw new RuntimeException("对不起,你所点的咖啡没有");
}
//加配料
coffee.addMilk();
coffee.addsugar();
return coffee;
}
}
Client测试类
public class Client {
public static void main(String[] args) {
//1.创建咖啡店类
CoffeeStore store=new CoffeeStore();
//2.点咖啡
Coffee coffee = store.orderCoffee("latte");
System.out.println(coffee.getName());
}
}
在JAVA中,万物皆对象,这些对象都需要创建,如果创建的时候直接new该对象,就会对该对象耦合严重,所有new对象的地方都需要修改一遍,这显然违背了软件设计的开闭原则。如果我们使用工厂来生产对象,我们就只和工厂打交道就可以了,彻底和对象解耦,如果要更换对象,直接在工厂里更换对象,直接在工厂里更换该对象即可,达到了与对象解耦的目的;所以说,工厂模式最大的优点就是:解耦。
在本教程中会介绍三种工厂的使用
- 简单工厂模式(不属于GOF的23中经典设计模式)
- 工厂方法模式
- 抽象工厂模式
3.1 简单工厂模式
简单工厂不是一种设计模式,反而比较像是一种编程习惯。
3.1.1 结构
简单工厂包含如下角色:
- 抽象产品:定义了产品的规范,描述了产品的主要特性的功能。
- 具体产品:实现或者继承抽象产品的子类。
- 具体工厂:提供了创建产品的方法,调用者通过该方法来获取产品。
3.1.2 实现
现在使用简单工厂对上面案例进行改进,类图如下:
Coffee类
//咖啡类
public abstract class Coffee {
public abstract String getName();
//加糖
public void addsugar(){
System.out.println("加糖");
}
//加奶
public void addMilk(){
System.out.println("加奶");
}
}
AmericanCoffee类
//美式咖啡
public class AmericanCoffee extends Coffee {
public String getName(){
return "美食咖啡";
}
}
LatteCoffee类
//拿铁咖啡
public class LatteCoffee extends Coffee {
public String getName(){
return "拿铁咖啡";
}
}
SimpleCoffeeFactory类
public class SimpleCoffeeFactory {
public Coffee createCoffee(String type) {
//声明Coffee类型的变量,根据不同累心创建不同的Coffee子类对象
Coffee coffee=null;
if("american".equals(type)){
coffee=new AmericanCoffee();
}
else if("latte".equals(type)){
coffee=new LatteCoffee();
}
else{
throw new RuntimeException("对不起,您所点的咖啡没有");
}
return coffee;
}
}
CoffeeStore类
public class CoffeeStore {
public Coffee orderCoffee(String type) {
SimpleCoffeeFactory factory = new SimpleCoffeeFactory();
//调用生产咖啡的方法
Coffee coffee=factory.createCoffee(type);
//加配料
coffee.addMilk();
coffee.addsugar();
return coffee;
}
}
Client测试类
public class Client {
public static void main(String[] args) {
//创建咖啡店类对象
CoffeeStore store = new CoffeeStore();
Coffee coffee = store.orderCoffee("latte");
System.out.println(coffee.getName());
}
}
工厂处理创建对象的细节,一旦有了SimpleCoffeeFactory,CoffeeStore类中的orderCoffee()就变成此对象的客户,后期如果需要Coffee对象直接从工厂中获取即可。这样也就解除了和Coffee实现类的耦合,同时又产生了新的耦合,CoffeeStore对象和SimpleCoffeeFactory工厂对象的耦合,工厂对象和商品对象的耦合。
后期如果再加新品种的咖啡,我们势必要需求修改SiimpleCoffeeFactory的代码,违反了开闭原则。工厂类的客户端可能有很多,比如创建美团外卖等,这样只能需要修改工厂类的代码,省去其他的修改操作。
3.1.3 优缺点
优点:
封装了创建对象的过程,可以通过参数直接获取对象。把对象的创建和业务逻辑层分开,这样以后就避免了修改客户的代码,如果要实现新产品直接修改工厂类,而不是需要再原代码中修改,这样就降低了客户代码修改的可能性,更加容易扩展。
缺点:
增加新产品是还是需要修改工厂类的代码,违背了“开闭原则”。
3.1.4 扩展
静态工厂
在开发中也有一部分人将工厂类中创建对象的功能定义为静态的;这个就是静态工厂模式,它也不是23中设计模式中的。代码如下:
public class SimpleCoffeeFactory {
public static Coffee createCoffee(String type) {
//声明Coffee类型的变量,根据不同累心创建不同的Coffee子类对象
Coffee coffee=null;
if("american".equals(type)){
coffee=new AmericanCoffee();
}
else if("latte".equals(type)){
coffee=new LatteCoffee();
}
else{
throw new RuntimeException("对不起,您所点的咖啡没有");
}
return coffee;
}
}
3.2 工厂方法模式
针对上例中的缺点,使用工厂方法模式就可以完美的解决,完全遵循开闭原则。
3.2.1 概念
【延迟】定义一个用于创建对象的接口,让子类决定实例化哪个产品类的对象。工厂方法使一个产品类的实例化延迟到其工厂的子类。
3.2.2 结构
工厂方法模式的主要角色:
- 抽象工厂:提供了创建产品的接口,调用者通过它访问具体工厂的工厂方法来创建产品。
- 具体工厂:主要是实现抽象工厂中的抽象方法,完成具体产品的创建。
- 抽象产品:定义了产品的规范,描述了产品的主要特性和功能。
- 具体产品:实现了抽象角色所定义的接口,由具体工厂来创建,它同具体工厂之间一一对应。
3.2.3 实现
使用工厂方法模式对上例进行改进,类图如下:
CoffeeFactory接口
//抽象工厂
public interface CoffeeFactory {
//创建Coffee对象的方法
Coffee createCoffee();
}
AmericanCoffeeFactory类
//美式咖啡类,专门用于生产美式咖啡
public class AmericanCoffeeFactory implements CoffeeFactory {
public Coffee createCoffee() {
return new AmericanCoffee();
}
}
LatteCoffeeFactory类
//拿铁咖啡工厂,专门生产拿铁咖啡
public class LatteCoffeeFactory implements CoffeeFactory {
public Coffee createCoffee() {
return new LatteCoffee();
}
}
Client测试类
public class Client {
public static void main(String[] args) {
//创建咖啡店对象
CoffeeStore coffeeStore = new CoffeeStore();
//创建对象
CoffeeFactory coffeeFactory = new AmericanCoffeeFactory();
coffeeStore.setFactory(coffeeFactory);
//点咖啡
Coffee coffee = coffeeStore.orderCoffee();
System.out.println(coffee.getName());
}
}
从以上的编写代码可以看到,要增加产品类式也要相应地增加工厂类,不需要修改工厂类的代码了,这样就解决了简单工厂模式的缺点。
工厂方法模式是简单工厂模式的进一步抽象。由于使用了多态性,工厂方法模式保持了简单工厂模式的优点,而且克服了它的缺点。
3.2.4 优缺点
优点:
- 用户只需要知道具体工厂的名称就可以得到所要的产品,无须知道产品的具体创建过程。
- 在系统增加新的产品是只需要添加产品类和对象的具体工厂类,无须对原工厂进行任何修改,满足开闭原则。
缺点:
- 每增加一个产品就要增加一个具体品类和一个对应的具体工厂类,这增加了系统的复杂度。
3.3 抽象工厂模式
3.3.1 概述
前面介绍的工厂方法模式中考虑的是一类产品的生产,如畜牧场只养动物、电视机厂只生产电视机、传智播客只培养计算机软件专业的学生等。
这些工厂只生产同种类产品,同种类产品称为同等级产品,也就是说:工厂方法模式只考虑生产同等级的产品,但是在现实生活中许多工厂是综合型的工厂,既生产多等级(种类)的产品,如电器厂既生产电视机又生产洗衣机或空调,大学生既有软件专业又有生物专业等。
本节要介绍的抽象工厂模式将考虑多等级产品的生产,将同一个具体工厂所生产的位于不同等级的一组产品称为一个产品族,下图所示横轴是产品等级,也就是同一类产品;纵轴是产品族,也就是同一品牌的产品,同一品牌的产品产自同一个工厂。
概念:抽象工厂模式是一种为访问类提供一个创建一组相关或相互依赖对象的接口,且访问类无须指定所要产品的具体类就能得到同族的不同等级的产品的模式结构。
抽象工厂模式是工厂方法模式的升级版本,工厂方法模式只生产一个等级的产品,而抽象工厂模式可生产多个等级的产品。
3.3.2 结构
抽象工厂模式的主要角色如下:
- 抽象工厂:提供了创建产品的接口,它包含多个创建产品的方法,可以创建多个不同等级的产品。
- 具体工厂:主要是实现抽象工厂中的多个抽象方法,完成具体产品的创建。
- 抽象产品:定义了产品的规范,描述了产品的主要特性和功能,抽象工厂模式有多个抽象产品。
- 具体产品:实现了抽象产品角色所定义的接口,有具体工厂来创建,它同具体工厂之间是多对一的关系。
3.3.3 实现
现在咖啡店业务发生改变,不仅要生产咖啡还要生产甜点,如提拉米苏、抹茶慕斯等,要是按照工厂方法模式,需要定义提拉米苏类、抹茶慕斯类、提拉米苏工厂、抹茶慕斯工厂、甜点工厂类,很容易发生类爆炸情况。其中拿铁咖啡、美式咖啡是一个产品等级,都是咖啡;提拉米苏、抹茶慕斯也是一个产品等级;拿铁咖啡和提拉米苏是同一个产品族(也就是都属于意大利风味),美式咖啡和抹茶慕斯是同一个产品族(也就是属于美式风味)。所以这个案例可以使用抽象工厂模式实现。类图如下:
Dessert抽象产品
//甜品抽象类
public abstract class Dessert {
public abstract void show();
}
Trimisu具体产品
//提拉米苏类
public class Trimisu extends Dessert{
public void show(){
System.out.println("提拉米苏");
}
}
MatchMousse具体产品
//抹茶慕斯类
public class MatchaMousse extends Dessert{
public void show(){
System.out.println("抹茶慕斯");
}
}
Coffee抽象产品
//咖啡类
public abstract class Coffee {
public abstract String getName();
//加糖
public void addsugar(){
System.out.println("加糖");
}
//加奶
public void addMilk(){
System.out.println("加奶");
}
}
AmericanCoffee具体产品
//美式咖啡
public class AmericanCoffee extends Coffee {
public String getName(){
return "美式咖啡";
}
}
LatteCoffee具体产品
//拿铁咖啡
public class LatteCoffee extends Coffee {
public String getName(){
return "拿铁咖啡";
}
}
DessertFactory抽象工厂
public interface DessertFactory {
//生产咖啡的功能
Coffee createCoffee();
//生产甜品的功能
Dessert createDessert();
}
AmericanDessertFactory具体工厂
//生产美式咖啡和抹茶慕斯
public class AmericanDessertFactory implements DessertFactory {
public Coffee createCoffee() {
return new AmericanCoffee();
}
public Dessert createDessert() {
return new MatchaMousse();
}
}
ItalyDessertFactory具体工厂
//生产拿铁和提拉米苏产品
public class ItalyDessertFactory implements DessertFactory {
public Coffee createCoffee(){
return new LatteCoffee();
}
public Dessert createDessert(){
return new Trimisu();
}
}
Client测试类
public class Client {
public static void main(String[] args) {
//创建的是意大利风味甜品工厂对象
ItalyDessertFactory factory=new ItalyDessertFactory();
AmericanDessertFactory factory1=new AmericanDessertFactory();
//获取拿铁咖啡和提拉米苏甜品
Coffee coffee = factory.createCoffee();
Dessert dessert = factory.createDessert();
Coffee coffee1 = factory1.createCoffee();
Dessert dessert1 = factory1.createDessert();
System.out.println(coffee.getName());
dessert.show();
System.out.println(coffee1.getName());
dessert1.show();
}
}
3.3.4 优缺点
优点:
当一个产品族中的多个对象被设计成一起工作时,它能保证客户端始终只能使用同一个产品族中的对象。
缺点:
当产品族中需要新增加一个新的产品时,所有的工厂类都需要进行修改。
3.3.5 使用场景
- 当需要创建的对象时一系列相互关联或相互依赖的产品族时,如电器工厂的电视机、洗衣机、空调等。
- 系统中有多个产品族,但每次只使用其中的某一族产品。如有人只喜欢穿某个品牌的衣服和鞋子。
- 系统中提供了产品的类库,且所有产品的接口相同,客户端不依赖产品实例的创建细节和内部结构。
如:输入法换皮肤,一整套一起换。生成不同操作系统程序。
3.4 JDK源码解析-Collection.iterator方法
public class Demo {
public static void main(String[] args) {
List<String> list=new ArrayList<>();
list.add("A");
list.add("B");
list.add("C");
//获取迭代器对象
Iterator<String> iterator=list.iterator();
//使用迭代器遍历
while(iterator.hasNext()) {
System.out.println(iterator.next());
}
}
}
对上面的代码,使用迭代器遍历集合,获取集合中的元素。而单列集合获取迭代器的方法就使用到工厂方法模式。我们通过类图看看结构:
Collection接口时抽象工厂,ArrayList时具体工厂,Iterator接口是抽象商品类,ArrayList类中的Iter内部类时具体的商品类。在具体的工厂类中iterator()方法创建具体的商品类的对象。
四.原型模式
4.1 概述
同一个已经创建的实例作为原型,通过复制该原型对象来创建一个和原型对象相同的新对象。
4.2 结构
原型模式包含如下角色:
- 抽象原型类:规定了具体原型对象必须实现的clone()方法
- 具体原型类:实现抽象原型类的clone()方法,它是可以被复制的对象
- 访问类:使用具体原型类中的clone()方法来复制新的对象
接口类图如下:
4.3 实现
原型模式的克隆分为浅克隆和深克隆。
浅克隆:创建一个新对象,新对象的属性和原来对象完全相同,对于非基本类型属性,仍指向原有属性所指向的对象的内存地址。
深克隆:创建一个新对象,属性中引用的其他对象也会被克隆,不再指向原有对象地址。
JAVA中Object类中提供了clone()方法来实现浅克隆。Cloneable接口时上面类图中的抽象原型类,而实现了Cloneable接口的子实现类就是具体的原型类。代码如下:
Realizetype实现类
public class Realizetype implements Cloneable{
public Realizetype() {
System.out.println("Realizetype Constructor");
}
@Override
public Realizetype clone() throws CloneNotSupportedException {
System.out.println("Realizetype clone");
return (Realizetype) super.clone();
}
}
Client测试类
public class Client {
public static void main(String[] args) throws CloneNotSupportedException {
//创建一个原型类对象
Realizetype realizetype=new Realizetype();
Realizetype realizetype1=realizetype.clone();
System.out.println("原型对象和克隆出来的是否时同一个同一个对象?"+(realizetype==realizetype1));
}
}
4.4 案例
用原型模式生成“三好学生”奖状
同一学校的“三好学生”奖状除了获奖人姓名不同,其他都相同,可以使用原型模式复制多个“三好学生”奖状出来,然后再修改奖状上的名字即可。
奖状如下:
Citation实现类
public class Citation implements Cloneable{
//三好学生上的姓名
private String name;
public String getName(){
return name;
}
public void setName(String name){
this.name = name;
}
public Citation clone()throws CloneNotSupportedException{
return (Citation)super.clone();
}
public void show(){
System.out.println(name+"同学,在2020学年第一学期中表现优秀,被评为三好学生。特发此状!");
}
}
CitationTest类
public class CitationTest {
public static void main(String[] args) throws CloneNotSupportedException {
//1.创建原型对象
Citation c1 = new Citation();
//2.克隆奖状对象
Citation c2=c1.clone();
c1.setName("张三");
c2.setName("李四");
//3。调用show方法展示
c1.show();
c2.show();
}
}
4.5 使用场景
- 对象的创建非常复杂,可以使用原型模式快捷的创建对象。
- 性能和安全要求比较高。【如果new,用户可以根据自己想法创建对象】
4.6 扩展(深克隆)
将上面的“三好学生”奖状的案例中Citation类的name属性修改为Student类型的属性。代码如下:
Student类
public class Student {
//学生的姓名
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String toString(){
return "Student{"+
"name='"+name+'\''+
'}';
}
}
Citation类
public class Citation implements Cloneable{
//三好学生上的姓名
// private String name;
//
// public String getName(){
// return name;
// }
//
// public void setName(String name){
// this.name = name;
// }
private Student stu;
public Student getStu(){
return stu;
}
public void setStu(Student stu){
this.stu = stu;
}
public Citation clone()throws CloneNotSupportedException{
return (Citation)super.clone();
}
public void show(){
System.out.println(stu.getName()+"同学,在2020学年第一学期中表现优秀,被评为三好学生。特发此状!");
}
}
CitationTest类
public class CitationTest {
public static void main(String[] args) throws CloneNotSupportedException {
//1.创建原型对象
Citation c1 = new Citation();
//创建张三学生对象
Student stu1=new Student();
stu1.setName("张三");
c1.setStu(stu1);
//2.克隆奖状对象
Citation c2=c1.clone();
c2.getStu().setName("李四");
// c1.setName("张三");
// c2.setName("李四");
//3。调用show方法展示
c1.show();
c2.show();
}
}
说明:
stu1对象和stu2对象是同一个对象,就会产生stu2对象中name属性值改为“李四”,两个Citation(奖状)对象显示的都是李四。这就是浅克隆的效果,对具体原型类中的引用类型的属性进行引用的复制。这种情况需要使用深克隆,而进行深克隆需要使用对象流。代码如下:
Student类
public class Student implements Serializable {
//学生的姓名
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String toString(){
return "Student{"+
"name='"+name+'\''+
'}';
}
}
Citation类
public class Citation implements Cloneable, Serializable {
//三好学生上的姓名
// private String name;
//
// public String getName(){
// return name;
// }
//
// public void setName(String name){
// this.name = name;
// }
private Student stu;
public Student getStu(){
return stu;
}
public void setStu(Student stu){
this.stu = stu;
}
public Citation clone()throws CloneNotSupportedException{
return (Citation)super.clone();
}
public void show(){
System.out.println(stu.getName()+"同学,在2020学年第一学期中表现优秀,被评为三好学生。特发此状!");
}
}
CitationTest测试类
public class CitationTest {
public static void main(String[] args) throws Exception {
//1.创建原型对象
Citation c1 = new Citation();
//创建张三学生对象
Student stu1=new Student();
stu1.setName("张三");
c1.setStu(stu1);
//创建对象输出流对象
ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("E:/a.txt"));
//写对象,序列化
oos.writeObject(c1);
//释放资源
oos.close();
//创建对象输入流对象
ObjectInputStream ois=new ObjectInputStream(new FileInputStream("E:/a.txt"));
//读取对象
Citation c2=(Citation)ois.readObject();
//释放资源
ois.close();
c2.getStu().setName("李四");
c1.show();
c2.show();
}
}
运行结果:
注意:Citation类和Student类必须实现Serializable接口,否则会抛NotSerializableException异常。
五.建造者模式
5.1 概述
将一个复杂对象的构建与表示分离,使得同样的构建过程剋有创建不同的表示。
- 分离了部件的构造(由Builder来负责)和装配(由Direator负责)。从而可以构造出复杂的对象。这个模式适用于:某个对象的构建过程复杂的情况。
- 由于实现了构建和装配的解耦。不同的构建器,相同的装配,也可以做出不同的对象;相同的构建器,不同的装配顺序也可以做出不同的对象。也就是实现了构建算法、装配算法的解耦,实现了更好的复用。
- 建造者模式可以将部件和其组装过程分开,一步一步创建一个复杂的对象。用户只需要指定复杂对象的类型就可以得到该对象,而无须知道其内部的具体构造细节。
5.2 结构
建造者模式包含如下角色:
- 抽象建造者类(Builder):这个接口规定要实现复杂对象的那些部分的创建,并不涉及具体的对象部件的创建。
- 具体建造者类(ConcreteBuilder):实现Builder接口,完成复杂产品的各个部件的具体创建方法。在构造过程完成后,提供产品的实例。
- 产品类(Product):要创建的复杂对象。
- 指挥者类(Director):调用具体建造者来创建复杂对象的各个部分,在指导者中不涉及具体产品的信息,只负责保证对象各部分完整创建或按照某种顺序创建。
类图如下:
5.3 实例
创建共享单车
生产自行车是一个复杂的过程,它包含了车架,车座等组件的生产。而车架又有碳纤维、铝合金等材质的,车座有橡胶、真皮等材质。对于自行车的生产可以使用建造者模式。这里Bike是产品,包含车架,车座等组件;Builder是抽象建造者,MobikeBuilder和OfoBuilder是具体建造者;Director是指挥者。类图如下:
Bike类
//产品对象
public class Bike {
private String frame; //车架
private String seat; //车座
public String getFrame(){
return frame;
}
public String getSeat(){
return seat;
}
public void setFrame(String frame){
this.frame = frame;
}
public void setSeat(String seat){
this.seat = seat;
}
}
Builfer抽象类
//抽象构建者
public abstract class Builder {
//声明Bike类型的变量并进行赋值,提高代码的复用性
protected Bike bike=new Bike(); //一个对象未组装
public abstract void buildFrame();
public abstract void buildSeat();
public abstract Bike createBike();
}
MobileBuilder实现类
public class MobileBuilder extends Builder{
public void buildFrame(){
bike.setFrame("碳纤维车架");
}
public void buildSeat(){
bike.setSeat("真皮车座");
}
public Bike createBike(){
return bike;
}
}
OfoBuilder实现类
public class OfoBuilder extends Builder {
public void buildFrame(){
bike.setFrame("铝合金车架");
}
public void buildSeat(){
bike.setSeat("橡胶车座");
}
@Override
public Bike createBike() {
return bike;
}
}
Director指挥类
public class Director {
//声明Builder类型的变量
private Builder builder;
public Director(Builder builder) {
this.builder = builder;
}
//组装自行车的功能
public Bike construct(){
builder.buildFrame();
builder.buildSeat();
return builder.createBike();
}
}
Client测试类
public class Client {
public static void main(String[] args) {
//创建指挥者对象
Director director=new Director(new MobileBuilder());
//让指挥者只会组装自行车
Bike bike = director.construct();
System.out.println(bike.getFrame());
System.out.println(bike.getSeat());
}
}
注意:
上面示例是Builder模式的常规方法,指挥者类Director在建造者模式中具有很重要的作用,它用于指导具体构建者如何构建产品,控制调用的先后次序,并向调用者但会完整的产品类,但是有些情况下需要简化系统结构,可以把只会者类和抽象建造者类进行结合。
public abstract class Builder {
//声明Bike类型的变量并进行赋值,提高代码的复用性
protected Bike bike=new Bike(); //一个对象未组装
public abstract void buildFrame();
public abstract void buildSeat();
public abstract Bike createBike();
public Bike construct(){
this.buildFrame();
this.buildSeat();
return this.createBike();
}
}
说明:
这样做确实简化了系统结构,但同时也加重了抽象建造者的职责,也不是太符合单一职责原则,如果construct()过于复杂,建议还是封装到Director中。
5.4 优缺点
优点:
- 建造者模式的封装性很好。使用建造者模式可以有效的封装变化,在使用建造者模式的场景中,一般产品类和建造者类是比较稳定的,因此,将主要业务逻辑封装在指挥者类中对整体而言可以取得比较好的稳定性。
- 在建造者模式中,客户端不必知道产品内部的组成细节,将产品本身和产品的创建过程解耦,使得相同的创建过程可以创建不同的产品对象。
- 可以更加精细控制产品的创建过程。将复杂产品的创建步骤分解在不同的方法中,使得创建过程更加清晰,也更方便使用程序来控制创建过程。
- 建造者模式很容易进行扩展。如果有新的需求,通过实现一个新的建造者类就可以完成,基本上不用修改之前已经测试通过的代码,因此也不会对原有的功能引入风险。符合开闭原则。
缺点:
- 建造者模式所创建的产品一般具有较多的共同点,组成部分相似,如果产品之间的差异性很大,则不适合使用建造者模式,因此其使用范围受到一定限制。
5.5 使用场景
建造者模式创建的是复杂对象,其产品的各个部分经常面临剧烈的变化,但将它们组合在一起的算法却相对稳定,所以它通常在以下场合使用:
- 创建的对象较复杂,由多个部件构成,各部件面临复杂的变化,但构件间的建造顺序是稳定的。
- 创建复杂对象的算法独立于该对象的组成部分以及它们的装配方式,即产品的构建过程和最终的表示是独立的。
5.6 模式扩展
建造者模式除了上面的用途外,在开发中还有一个常用的使用方式,就是当一个类构造器需要传入很多参数的时候,如果创建这个类的实例,代码的可读性就会非常差,而且很容易引入错误,此时就可以利用建造者模式进行重构。
Phone类
public class Phone {
private String cpu;
private String screen;
private String memory;
private String mainboard;
public Phone(String cpu, String screen, String memory, String mainboard) {
this.cpu = cpu;
this.screen = screen;
this.memory = memory;
this.mainboard = mainboard;
}
public String getCpu() {
return cpu;
}
public void setCpu(String cpu) {
this.cpu = cpu;
}
public String getScreen() {
return screen;
}
public void setScreen(String screen) {
this.screen = screen;
}
public String getMemory() {
return memory;
}
public void setMemory(String memory) {
this.memory = memory;
}
public String getMainboard() {
return mainboard;
}
public void setMainboard(String mainboard) {
this.mainboard = mainboard;
}
public String toString() {
return "Phone{"+
"cpu='"+cpu+'\''+
". screen='"+screen+'\''+
". memory='"+memory+'\''+
". mainboard='"+mainboard+'\''+
'}';
}
}
Client测试类
public class Client {
public static void main(String[] args) {
//创建Phone对象
Phone phone =new Phone("intel","三星屏幕","金士顿","华硕");
System.out.println(phone);
}
}
上面在客户端代码中构建Phone对象,传递了四个参数,如果参数更多呢?代码的可读性及使用的成本就是比较高。
重构后代码:
Phone类
public class Phone {
private String cpu;
private String screen;
private String memory;
private String mainboard;
public String toString() {
return "Phone{"+
"cpu='"+cpu+'\''+
". screen='"+screen+'\''+
". memory='"+memory+'\''+
". mainboard='"+mainboard+'\''+
'}';
}
//私有构造方法
private Phone(Builder builder) {
this.cpu = builder.cpu;
this.screen = builder.screen;
this.memory = builder.memory;
this.mainboard = builder.mainboard;
}
public static final class Builder {
private String cpu;
private String screen;
private String memory;
private String mainboard;
public Builder cpu(String cpu){
this.cpu = cpu;
return this;
}
public Builder screen(String screen){
this.screen = screen;
return this;
}
public Builder memory(String memory){
this.memory = memory;
return this;
}
public Builder mainboard(String mainboard){
this.mainboard = mainboard;
return this;
}
public Phone build() {
return new Phone(this);
}
}
}
Client测试类
public class Client {
public static void main(String[] args) {
//创建Phone对象
Phone phone = new Phone.Builder()
.cpu("intel")
.screen("三星屏幕")
.memory("金士顿")
.mainboard("华硕")
.build();
System.out.println(phone);
}
}
重构后的代码在使用起来更方便,某种程度上也可以提高开发效率。从软件设计上,对程序员的要求比较高。
六.创建者模式对比
6.1 工厂方法模式VS建造者模式
工厂方法模式主要的是整体对象的创建方式;而建造者模式注重的是部件构建的过程,意在通过一步一步地精确构造创建出一个复杂的对象。
我们举个简单例子来说明两者的差异,如果制造一个超人,如果使用工厂方法模式,直接产出来的就是一个力大无穷、能够飞翔、内裤外穿的超人;而如果使用建造者模式,则需要组装手、头、脚、躯干等部分,然后再把内裤外穿,于是一个超人就诞生了。
6.2 抽象工厂模式VS建造者模式
抽象工厂模式实现对产品家族的创建,一个产品家族是这样的一系列产品:具有不同分类维度的产品组合,采用抽象工厂模式则是不需要关心构建过程,只关心什么产品由什么工厂生产即可。
建造者模式则是按照指定的蓝图建造产品,它的主要目的是通过组装零配件而产生一个新产品。
如果将抽象工厂模式看成汽车配件生产工厂,生产一个产品族产品,那么建造者模式就是一个汽车组装工厂,通过对部件的组装可以返回一辆完整的汽车。