四.创建者模式

创建型模式的主要关注点是"怎样创建对象?“, 它的主要特点是"将对象的创建与使用分离”。
这样可以降低系统的耦合度,使用者不需要关注对象的创建细节。

创建型模式分为:

  • 单例模式
  • 工厂方法模式
  • 抽象工厂模式
  • 原型模式
  • 建造者模式

1.单例设计模式

单例模式(Singleton Pattern)是Java中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。

(1).单例模式的结构

单例模式的主要有以下角色:

  • 单例类:只能创建一个实例的类
  • 访问类: 使用单例类

(2).单例模式的实现

单例设计模式分类两种:

  • 饿汉式: 类加载就会导致该单实例对象就被创建
  • 懒汉式: 类加载不会导致该单实例对象被创建,而是首次使用该对象时才会创建

[1].饿汉式

[a].方式1(静态变量方式)

public class Singleton{
     
     //私有构造方法
     private Singleton(){}

     private static Singleton instance = new Singleton();

     public static Singleton getInstance(){
             return instance;
     } 
}
public class Client{
    public static void main(String[] args){
           Singleton instance = Singleton.getInstance();
           Singleton instance2 = Singleton.getInstance();
           System.out.println(instance == instance2); //true 
    }
}

[b].方式2(静态代码块方式)

public class Singleton{
    
     private Singleton(){}

     private static Singleton instance;

     static {
        instance = new Singleton();
     }

     public static Singleton getInstance(){
          return instance;
     }     
}
public class Client{
    public static void main(String[] args){
          Singleton instance1 = Singleton.getInstance();
          Singleton instance2 = Singleton.getInstance();
          System.out.println(instance1 == instance2);//true 
    }
}

说明:
该方式在成员位置声明Singleton类型的静态变量,而对象的创建是在静态代码块中,也是对着类的加载而创建。所以和饿汉式的方式是1基本上一样,当然该方式也存在内存浪费问题。

[c].方式3(枚举方式)

枚举类实现单例模式是极力推荐的单例实现模式,因为枚举类型是线程安全的,并且只会装载一次,设计者充分的利用了枚举的这个特性来实现单例模式,枚举的写法非常简单,而且枚举类型是所有单例实现中唯一一种不会被破坏的单例实现模式。

public enum Singleton{
      INSTANCE;
}

说明:
枚举方式属于恶汉方式

[2].懒汉式

[a].方式1(线程不安全)

public class Singleton{
     
     private Singleton(){}

     private static Singleton instance;

     public static Singleton getInstance(){
           if(instance == null){
                instance = new Singleton();
           }
           return instance;
     }
}
public class Client{
    public static void main(String[] args){
          Singleton instance1 = Singleton.getInstance();
          Singleton instance2 = Singleton.getInstance();
          System.out.println(instance1 == instance2);//true 
    }
}

[b].方式2(线程安全)

public class Singleton{
     
     private Singleton(){}

     private static Singleton instance;

     public synchronized static Singleton getInstance(){
           if(instance == null){
                instance = new Singleton();
           }
           return instance;
     }
}
public class Client{
    public static void main(String[] args){
          Singleton instance1 = Singleton.getInstance();
          Singleton instance2 = Singleton.getInstance();
          System.out.println(instance1 == instance2);//true 
    }
}

说明:
该方法也实现了懒加载效果,同时解决了线程安全问题。但是在getInstance()方法上添加synchronized关键字,导致该方法的执行效果特别低。从上面代码我们可以看出,其实在初始化instance的时候才会出现线程安全问题,一旦初始化完成就不存在了。

[c].方式3(双重检查锁)

再来讨论一下懒汉模式中加锁问题,对于getInstance()方法来说,绝大部分的操作都是读操作,读操作是线程安全的,所以我们没必让每个线程必须持有锁才能调用该方法,我们需要调整加锁的时机。由此也产生了一种新的实现模式:双重检查锁模式

public class Singleton{
     
   private 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关键字可以保证可见性和有序性。

public class Singleton{

   private Singleton(){}
   
   private static volatile Singleton instance;

   public static Singleton getInstance(){
        if(instance == null){
              synchronized(Singleton.class){
                   if(instance == null){
                         instance = new Singleton();
                   } 
              }
        }
        return instance;
   }     
}

说明:
添加volatile关键字之后的双重检查锁模式是一种比较好的单例实现模式,能够保证在多线程的情况下线程安全也不会有性能问题。

[d].方式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;
   }
}

说明:
1. 第一次加载Singleton类时不会去初始化INSTANCE,只有第一次调用getInstance, 虚拟机加载SingletonHolder并初始化INSTANCE, 这样不仅能确保线程安全,也能保证Singleton类的唯一性。
2. 静态内部类单例模式是一种优秀的单例模式,是开源项目中比较常用的一种单例模式。在没有任何锁的情况下,保证了多线程下的安全,并且没有任何性能影响和空间的浪费。

(3).存在的问题

[1].问题演示

破坏单例模式:
使上面定义的单例类(Singleton)可以创建多个多个对象,枚举方式除外。有两种方式,分别是序列化和反射。

  • 序列化反序列化
import java.io.*;

class Singleton implements Serializable {

    private Singleton(){}

    private static class SingleHolder {
        private static final Singleton INSTANCE = new Singleton();
    }

    public static Singleton getInstance(){
        return SingleHolder.INSTANCE;
    }

    public static void main(String[] args) throws IOException, ClassNotFoundException {
       Singleton instance = Singleton.getInstance();
       ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("F:\\cs.txt"));
       oos.writeObject(instance);
       oos.close();
       ObjectInputStream ios = new ObjectInputStream(new FileInputStream("F:\\cs.txt"));
       Singleton instance2 = (Singleton) ios.readObject();
       ios.close();
       System.out.println(instance == instance2);
    }
}

上面代码运行结果是false, 表明序列化和反序列化已经破坏了单例设计模式。

  • 反射
import java.io.*;
import java.lang.reflect.Constructor;

class Singleton {

    private Singleton(){}

    private static class SingleHolder {
        private static final Singleton INSTANCE = new Singleton();
    }

    public static Singleton getInstance(){
        return SingleHolder.INSTANCE;
    }

    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);

    }
}

上面代码运行结果是false, 表明序列化和反序列化已经破坏了单例设计模式。

[2].解决方法

  • 序列化,反序列方式破坏单例模式的解决方法
    在Singleton类中添加readResolve()方法,在反序列化时被反射调用,如果定义了这个方法,就返回这个方法的值,如果没有定义,则返回新new出来的对象。
import java.io.*;
import java.lang.reflect.Constructor;

class Singleton implements Serializable {

    private Singleton(){}

    private static class SingleHolder{
        private static final Singleton INSTANCE = new Singleton();
    }

    public static Singleton getInstance(){
        return SingleHolder.INSTANCE;
    }

    //当进行反序列化时,会自动调用该方法,将该方法的返回值直接返回+
    public Object readResolve(){
        return SingleHolder.INSTANCE;
    }

    public static void main(String[] args) throws Exception {
       Singleton instance = Singleton.getInstance();
       ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("F:\\cs.txt"));
       oos.writeObject(instance);
       oos.close();
       ObjectInputStream ios = new ObjectInputStream(new FileInputStream("F:\\cs.txt"));
       Singleton instance2 = (Singleton) ios.readObject();
       ios.close();
       System.out.println(instance == instance2);//true
    }
}

  • 反射方式破解单例的解决办法
import java.io.*;
import java.lang.reflect.Constructor;

class Singleton {

    private static boolean flag = false;

    private Singleton(){
        synchronized (Singleton.class){
            if(flag){
                throw new RuntimeException("对象已经创建");
            }
            flag = true;
        }
    }




    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);

    }
}

(4).JDK源码解析-Runtime类

Runtime类就是使用的单例设计模式
[1].通过源代码查看使用的是哪儿种单例模式

package java.lang;
import java.io.*;
import java.util.StringTokenizer;
import sun.reflect.CallerSensitive;
import sun.reflect.Reflection;

public class Runtime {
    private static Runtime currentRuntime = new Runtime();

   
    public static Runtime getRuntime() {
        return currentRuntime;
    }

   
    private Runtime() {}

    public void exit(int status) {
        SecurityManager security = System.getSecurityManager();
        if (security != null) {
            security.checkExit(status);
        }
        Shutdown.exit(status);
    }

  
    public void halt(int status) {
        SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            sm.checkExit(status);
        }
        Shutdown.halt(status);
    }


    @Deprecated
    public static void runFinalizersOnExit(boolean value) {
        SecurityManager security = System.getSecurityManager();
        if (security != null) {
            try {
                security.checkExit(0);
            } catch (SecurityException e) {
                throw new SecurityException("runFinalizersOnExit");
            }
        }
        Shutdown.setRunFinalizersOnExit(value);
    }

    public Process exec(String command) throws IOException {
        return exec(command, null, null);
    }

    
    public Process exec(String command, String[] envp) throws IOException {
        return exec(command, envp, null);
    }

    public Process exec(String command, String[] envp, File dir)
        throws IOException {
        if (command.length() == 0)
            throw new IllegalArgumentException("Empty command");

        StringTokenizer st = new StringTokenizer(command);
        String[] cmdarray = new String[st.countTokens()];
        for (int i = 0; st.hasMoreTokens(); i++)
            cmdarray[i] = st.nextToken();
        return exec(cmdarray, envp, dir);
    }

   
    public Process exec(String cmdarray[]) throws IOException {
        return exec(cmdarray, null, null);
    }


    public Process exec(String[] cmdarray, String[] envp, File dir)
        throws IOException {
        return new ProcessBuilder(cmdarray)
            .environment(envp)
            .directory(dir)
            .start();
    }

    public native int availableProcessors();

  
    public native long totalMemory();


    public native void gc();


    private static native void runFinalization0();

  
    public void runFinalization() {
        runFinalization0();
    }


    public native void traceMethodCalls(boolean on);


    @CallerSensitive
    public void load(String filename) {
        load0(Reflection.getCallerClass(), filename);
    }

    synchronized void load0(Class<?> fromClass, String filename) {
        SecurityManager security = System.getSecurityManager();
        if (security != null) {
            security.checkLink(filename);
        }
        if (!(new File(filename).isAbsolute())) {
            throw new UnsatisfiedLinkError(
                "Expecting an absolute path of the library: " + filename);
        }
        ClassLoader.loadLibrary(fromClass, filename, true);
    }

  
    @CallerSensitive
    public void loadLibrary(String libname) {
        loadLibrary0(Reflection.getCallerClass(), libname);
    }

    synchronized void loadLibrary0(Class<?> fromClass, String libname) {
        SecurityManager security = System.getSecurityManager();
        if (security != null) {
            security.checkLink(libname);
        }
        if (libname.indexOf((int)File.separatorChar) != -1) {
            throw new UnsatisfiedLinkError(
    "Directory separator should not appear in library name: " + libname);
        }
        ClassLoader.loadLibrary(fromClass, libname, false);
    }

    @Deprecated
    public InputStream getLocalizedInputStream(InputStream in) {
        return in;
    }

    public OutputStream getLocalizedOutputStream(OutputStream out) {
        return out;
    }
}

从上面源代码中可以看出Runtime类使用的是饿汉式(静态属性)方式实现单例模式
使用Runtime类

package com.xiaotian;

import java.io.IOException;
import java.io.InputStream;

public class RuntimeDemo {
    public static void main(String[] args) throws IOException {
        Runtime runtime = Runtime.getRuntime();
        Process process = runtime.exec("ipconfig");
        InputStream is = process.getInputStream();
        byte[] arr = new byte[1024*1024];
        int len = -1;
        while((len = is.read(arr)) != -1){
            System.out.println(new String(arr, 0, len, "GBK"));
        }

    }
}

2.工厂模式

(1).概述

需求:设计一个咖啡店点餐系统。

设计一个咖啡类(Coffee), 并定义其两个子类(美式咖啡[AmericanCooffee])和拿铁咖啡[LatteCoffee]);再设计一个咖啡店类(CoffeeStore), 咖啡店具有点咖啡的功能。
具体类的设计如下:
四.创建者模式_第1张图片代码如下:

public abstract class Coffee{
    
    public abstract String getName();

    public void addSugar(){
       System.out.println("加糖");
    }

    public void addMilk(){
       System.out.println("添加牛奶");   
    } 
}
public class AmericanCoffee extends Coffee{
     public String getName(){
         return "美式咖啡"; 
     }
}
public class LatteCoffee extends Coffee{

   public String getName(){
      return "拿铁咖啡";
   } 
}
public class CoffeeStore{
    
    public Coffee orderCoffee(String type){
         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; 
    }
}
public class Client{
    public static void main(String[] args){
         CoffeeStore store = new CoffeeStore();
         Coffee coffee = store.orderCoffee("latte");
         System.out.println(coffee.getName()); 
    }
}

在java中,万物皆对象,这些对象都需要创建,如果创建的时候直接new该对象,就会对该对象耦合严重,假如我们要更换对象,所有new对象的地方都需要修改一遍,这显然违背了软件设计的开闭原则。如果我们使用工厂来生产对象,我们就只和工厂打交道就可以了,彻底和对象解耦,如果要更换对象,直接在工厂里更换该对象即可,达到了与对象解耦的目的;所以说,工厂模式最大的优点就是:解耦。

最常用的工厂模式有三种:

  • 简单工厂模式(不属于GOF的23种经典设计模式)
  • 工厂方法模式
  • 抽象工厂模式

(2).简单工厂模式

简单工厂不是一种设计模式,反而比较像是一种编程习惯。

[1].结构

简单工厂包含如下角色:

  • 抽象产品: 定义了产品的规范,描述了产品的主要特性和功能。
  • 具体产品: 实现或者继承抽象产品的子类
  • 具体工厂:提供了创建产品的方法,调用者通过该方法来创建产品。

[2].实现

现在使用简单工厂对上面案例进行改进,类图如下:
四.创建者模式_第2张图片
代码如下:

public abstract class Coffee{
    
    public abstract String getName();

    public void addMilk(){
        System.out.println("添加牛奶"); 
    }

    public void addSugar(){
        System.out.println("添加糖");
    }
}
public class AmericanCoffee extends Coffee{
    public String getName(){
         return "美式咖啡"; 
     }
}
public class LatteCoffee extends Coffee{

   public String getName(){
      return "拿铁咖啡";
   } 
}
public class SimpleCoffeeFactory{
    public Coffee createCoffee(String type){
        Coffee coffee = null;
        if("american".equals(type)){
            coffee = new AmericanCoffee();
        }else if("latte".equals(type)){
            coffee = new LatteCoffee();
        }else{
            throw new RuntimeException("对不起,你所点的咖啡没有");
        }
        return coffee;
    }
}
public class CoffeeStore{
   
   public Coffee orderCoffee(String type){
     SimpleCoffeeFactory factory = new SimpleCoffeeFactory();
     Coffee coffee = factory.createCoffee(type);
     coffee.addMilk();
     coffee.addSugar();
     return coffee;
   }
}
public class Client{
    public static void main(String[] args){
         CoffeeStore store = new CoffeeStore();
         Coffee coffee = store.orderCoffee("latte");
         System.out.println(coffee.getName()); 
    }
}

工厂(factory)处理创建对象的细节,一旦有了SimpleCoffeeFactory, CoffeeStore类中的orderCoffee()就变成此对象的客户,后期如果需要Coffee对象直接从工厂中获取即可。这样也解除了和Coffee实现类的耦合,同时又产生了新的耦合,CoffeeStore对象SimpleCoffeeFactory工厂对象耦合,工厂对象和商品对象的耦合。
后期如果如果再添加新品种的咖啡,我们势必要需求修改SimpleCoffeeFactory的代码,违反了开闭原则。工厂类的客户端可能有很多,比如创建美团外卖等,这样只需要修改工厂类的代码,省去其他的修改操作。

[3].优缺点

[a].优点:

封装了创建对象的过程,可以通过参数直接获取对象。把对象的创建和业务逻辑层分开,这样以后避免了修改客户代码,如果实现新产品直接修改工厂类,而不需要在原代码中修改的可能性,更加容易扩展。

[b].缺点:

增加新产品时还是需要修改工厂类的代码,违背了"开闭原则"。

[4].扩展

静态工厂

在开发中也有一部分人将工厂类中的创建对象的功能定义为静态的,这个就是静态工厂模式,它也不是23种设计模式。代码如下:

public class SimpleCoffeeFactory{

    public static Coffee createCoffee(String type){
        Coffee coffee = null;
        if("americano".equals(type)){
            coffee = new AnericanoCoffee();
        }else if("latte".equals(type)){
            coffee = new LatteCoffee();
        }
        return coffee;
    }
}

(3).工厂方法模式

针对上例中的缺点,使用工厂方法就可以完美的解决,完全遵循开闭原则。

[1].概念

定义一个用于创建对象的接口,让子类决定实例化哪个产品类对象。工厂方法是一个产品类的实例化延迟到其工厂的子类。

[2].结构

工厂方法模式的主要角色:

  • 抽象工厂(Abstract Factory):提供了创建产品的接口,调用者通过它访问具体工厂的工厂方法来创建产品。
  • 具体工厂(ConcreteFactory):主要是实现抽象工厂中的抽象方法,完成具体产品的创建。
  • 抽象产品(Product):定义了产品的规范,描述了产品的主要特性和功能。
  • 具体产品(ConcreteProduct):实现了抽象产品角色所定义的接口,由具体工厂来创建,它同具体工厂之间一一对应。

[3].实现

使用工厂方法模式对上例进行改进,类图如下:
四.创建者模式_第3张图片
代码如下:

public abstract Coffee{
    
   public abstract String getName();

   public void addMilk(){
       System.out.println("添加牛奶");
   } 
   
   public void addSugar(){
      System.out.println("添加糖");
   } 
}
public class AmerianoCoffee extends Coffee{
   
   public String getName(){
        return "美式咖啡";
   }
}
public class LatteCoffee extends Coffee{

   public String getName(){
      return "拿铁咖啡"; 
   }
}
public interface CoffeeFactory{
    Coffee createCoffee();
}
public class AmericanoCoffeeFactory implements CoffeeFactory{
   
   public Coffee createCoffee(){
        return new AmericanoCoffee();
   }
   
}
public class LatteCoffeeFactory implements CoffeeFactory{
   
   public Coffee createCoffee(){
        return new LatteCoffee();
   }
   
}

public class CoffeeStore{

   private CoffeeFactory factory;

   public void setFactory(CoffeeFactory factory){
        this.factory = factory; 
   }

   public Coffee orderCoffee(){
       Coffee coffee = factory.createCoffee();
       coffee.addMilk();
       coffee.addSugar();
       return coffee;
   }  
}
public class Client{
   public static void main(String[] args){
     CoffeeStore store = new CoffeeStore();
     CoffeeFactory factory = new AmericanCoffeeFactory();
     store.setFactory(factory);
     Coffee coffee = store.orderCoffee();
     System.out.println(coffee.getName());
   }
}

从以上的编写的代码可以看到,要增加产品类时也要相应地增加工厂类,不需要修改工厂类的代码,这样就解决了简单工厂模式的缺点。
工厂方法模式是简单工厂模式的进一步抽象。由于使用了多态性,工厂方法模式保持了简单工厂模式的有点,而且克服了它的缺点。

[4].优缺点

优点

  • 用户只需知道具体工厂的名称就可得到所要的产品,无需知道产品的具体创建过程;
  • 在系统增加新的产品时只需要添加具体产品类和对应的具体工厂类,无须对原工厂进行任何修改,满足开闭原则;

缺点

  • 每增加一个产品就要增加一个具体产品类和一个对应的具体工厂类,这增加了系统的复杂度。

(4).抽象工厂模式

前面介绍的工厂方法模式中考虑的是一类产品的生产,如畜牧场只养动物,电视机厂只生产电视等。
这些工厂只生产同种类产品,同种类产品称为同等级产品,也就是说:工厂方法模式只考虑生产同等级的产品,但是在现实生活中许多工厂是综合型工厂,能生产多等级(种类)的产品,如电器厂既生产电视机又生产洗衣机或空调,大学既有
抽象工厂模式将考虑多等级产品的生产,将同一个具体工厂所生产的位于不同等级的一组产品称为一个产品族,下图所示横轴是产品等级,也就是同一类产品;纵轴是产品,也就是同一品牌的产品,同一品牌的产品产自同一个工厂。
四.创建者模式_第4张图片

[1].概念

是一种为访问类提供一个创建一组相关或相互依赖对象的接口,且访问类无须指定所要产品的具体类就能得到同族的不同等级的产品的模式结构。
抽象工厂模式是工程方法模式的升级版本,工厂方法模式只生产一个等级的产品,而抽象工厂模式可身材多个等级的产品。

[2].结构

抽象工厂模式的主要角色如下:

  • 抽象工厂(Abstract Factory): 提供了创建产品的接口,它包含多个创建产品的方法,可以创建多个不同等级的产品。
  • 具体工厂(Concrete Factory): 主要实现抽象工厂中的多个方法,完成具体产品的创建。
  • 抽象产品(Product): 定义了产品规范,描述了产品的主要特性和功能,抽象工厂模式有多个抽象产品。
  • 具体产品(ConcreteProduct): 实现了抽象产品角色所定义的接口,由具体工厂来创建,它同具体工厂之间是多对一的关系。

[3].实现

现在咖啡业务发生改变,不仅要生产咖啡还要生产甜点,如提拉米苏,抹茶慕斯等,要是按照工厂模式,需要创建提拉米苏,抹茶慕斯类,提拉米苏工厂,抹茶慕斯工厂,甜点工厂类,很容易发生类爆炸。其中拿铁咖啡,美式咖啡是一个产品等级都是咖啡,提拉米苏,抹茶慕斯又是一个产品等级;拿铁咖啡和提拉米苏是同一产品族(也就是都属于意大利风味),美式咖啡和抹茶慕斯是同一产品族(也就是美式风味)。所以这个案例可以使用抽象工厂模式实现,类图如下:
四.创建者模式_第5张图片
代码如下:

public abstract class Coffee{
    
    public abstract String getName();

    public void addMilk(){
      System.out.println("添加牛奶"); 
    }

    public void addSugar(){
       System.out.println("添加糖"); 
    }
}
public class AmericanoCoffee extends Coffee{
    public String getName(){
        return "美式咖啡";
    }
}
public class LatteCoffee extends Coffee{
    public String getName(){
        return "拿铁咖啡";
    } 
}
public abstract class Dessert{
    public abstract void show();
}
public class Tiramisu extends Dessert{
     public void show(){
        System.out.println("提拉米苏");
     }
}
public class MatchaMouse extends Dessert{
    public void show(){
        System.out.println("抹茶慕斯");
    }
}
public interface DessertFactory{
   public Coffee createCoffee();
   public Dessert createDessert(); 
}
public class AmericanoDessertFactory implements DessertFactory{
    
    @Override 
    public Coffee createCoffee(){
       return new AmericanCoffee();
    }
     
     @Override 
     public Dessert createDessert(){
         return new MatchaMouse();
     }
}
public class ItalyDessertFactory implements DessertFactory{
    @Override 
    public Coffee createCoffee(){
       return new LatteCoffee();
    }
     
     @Override 
     public Dessert createDessert(){
         return new Tiramisu();
     }
}
public class Client{
   public static void main(String[] args){
      AmericanDessertFactory factory = new AmericanDessertFactory();
      Coffee coffee = factory.createCoffee();
      Dessert dessert = factory.createDessert();

      System.out.println(coffee.getName());
      dessert.show();    
   }
}

如果要加同一个产品族的话,只需要在加一个对应的工厂类即可,不需要修改其他的类。

[4].优缺点

[a].优点:

当一个产品族中的多个对象被设计成一起工作时,它能保证客户端始终只能使用同一个产品族中的对象。

[b].缺点

当产品族中需要增加一个新的产品时,所有的工厂类都需要进行修改。

[5].使用场景

  • 当需要创建的对象是一系列相互关联或相互依赖的产品族时,如电器工厂中的电视机,洗衣机,空调等。
  • 系统中有多个产品族,但每次只使用其中的某一族产品。如有人只喜欢穿某一个品牌的衣服和鞋。
  • 系统中提供了产品的类库,且所有产品的接口相同,客户端不依赖产品实例的创建细节和内部结构。
    如:输入法换皮肤,一整台一起换。生成不同操作系统的程序。

(5).模式扩展

简单工厂+配置文件解除耦合

可以通过工厂模式+配置文件的方式解除工厂对象和产品对象的耦合。在工厂类中加载配置文件中的全类名,并创建对象进行存储,客户端如果需要对象,直接获取即可。
示例:
[1].创建配置文件bean.properties

american = com.xiaotian.AmericanCoffee
latte = com.xiaotian.LatteCoffee

[2].创建Coffee类,AmericanCoffee类和LatterCoffee类

package com.xiaotian;

public abstract class Coffee {

        public abstract String getName();

        public void addMilk(){
            System.out.println("添加牛奶");
        }
        
        public void addSugar(){
            System.out.println("添加糖");
        }
}
package com.xiaotian;

public class AmericanCoffee extends Coffee{
    @Override
    public String getName() {
        return "美式咖啡";
    }
}
package com.xiaotian;

public class LatteCoffee extends Coffee{

    @Override
    public String getName() {
        return "拿铁咖啡";
    }
}

[3].改进工厂类

package com.xiaotian;
import java.io.IOException;
import java.io.InputStream;
import java.util.*;
public class CoffeeFactory {

    private static Map<String, Coffee> map = new HashMap<>();

    static{

        try {
            Properties p = new Properties();
            InputStream is = CoffeeFactory.class.getClassLoader().getResourceAsStream("bean.properties");
            p.load(is);
            Set<Object> keys = p.keySet();
            for (Object key : keys) {
                String className = p.getProperty((String) key);
                Class clzz = Class.forName(className);
                Coffee coffee = (Coffee) clzz.newInstance();
                map.put((String) key, coffee);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static Coffee createCoffee(String name){
        return map.get(name);
    }

    public static void main(String[] args) throws IOException {
        System.out.println(CoffeeFactory.createCoffee("latte"));

    }
}

[4].测试类

public class Client{
  public static void main(String[] args){
        System.out.println(CoffeeFactory.createCoffee("latte"));
  } 
}

(6).JDK源码解析 - Collection.iterator方法

public class Demo{
   public static void main(String[] args){
      List<String> list = new ArrayList();
      list.add("令狐冲");
      list.add("风清扬");
      list.add("任我行");
      
      //获取迭代器对象
      Iterator<String> it = list.iterator();
      //使用迭代器遍历
      while(it.hasNext()){
          String ele = it.next();
          System.out.println(ele); 
      }
   }
}

使用迭代器遍历集合,获取集合中的元素。而单列集合集合获取迭代器就使用到了工厂方法模式。如下类图所示
四.创建者模式_第6张图片
Collection接口是抽象工程类,ArrayList是具体的工厂类;Iterator接口是抽象商品类,ArrayList类中的Iter内部类是具体的商品类。在具体的工厂类中iterator()方法创建具体的商品类的对象
另外:
1. DateFormat类中的getInstance()方法使用的是工厂模式
2. Calendar类中的getInstance()方法使用的是工厂模式

3.原型模式

(1).概述

用一个已经创建的实例作为原型,通过复制该原型对象来创建一个和原型对象相同的新对象。

(2).结构

原型模式包含如下角色:

  • 抽象原型类: 规定了具体原型对象必须实现的clone()方法。
  • 具体原型类: 实现抽象原型类的clone()方法,它是可被复制的对象。
  • 访问类:使用具体原型类中的clone()方法来复制新的对象
    接口类图如下:
    四.创建者模式_第7张图片

(3).实现

原型模式的克隆可分为浅克隆和深克隆

浅克隆: 创建一个新对象,新对象的属性和原来对象完全相同,对于非基本类型属性,仍指向原来属性所指向的对象的内存地址。
深克隆: 创建一个新对象,属性中引用的其他对象也会被克隆,不再指向原有地址对象

Java中的Object类中提供了clone()方法来实现浅克隆。Cloneablej接口是上面的类图中的抽象原型类,而实现了Cloneable接口的子实现类就是具体的原型类。
示例

public class RealizeType implements Cloneable{

    public RealizeType() {

    }

    @Override
    protected RealizeType clone() throws CloneNotSupportedException {
        return (RealizeType) super.clone();
    }

    public static void main(String[] args) throws CloneNotSupportedException {
        RealizeType r1 = new RealizeType();
        RealizeType r2 = r1.clone();
        System.out.println(r1 == r2);//false
    }
}

(4).案例

用原型模式生成"三好学生"奖状
同一学校的"三好学生"奖状除了获奖人姓名不同,其他都相同,可以使用原型模式复制多个"三好学生"奖状出来,然后再修改奖状上的名字即可。
类图如下:
四.创建者模式_第8张图片
代码如下:

public class Citation implements Cloneable{
    
    private String name;

    public void setName(String name){
       this.name = name;
    }
    
    public String getName(){
        return name; 
    }

    public void show(){
      System.out.println(name+"被评为三好学生");
    }

    @Override
    protected Citation clone() throws CloneNotSupportedException {
        return (Citation) super.clone();
    }
}
public class CitationTest{
   public static void main(String[] args){
        Citation c1 = new Citation();
        c1.setName("张三");
        Citation c2 = c1.clone();
        c2.setName("李四");
        c1.show();
        c2.show();
   }
}

(5).使用场景

  • 对象的创建非常复杂,可以使用原型模式快捷的创建对象
  • 性能和安全要求比较高

(6).扩展(深克隆)

示例
将上面的"三号学生"奖状的案例中Citation类的name属性修改为Student类型的属性。代码如下:

package com.xiaotian;

public class Student {

    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                '}';
    }
}

package com.xiaotian;

public class Citation implements Cloneable{

    private Student student;

    public Student getStudent() {
        return student;
    }

    public void setStudent(Student student) {
        this.student = student;
    }

    @Override
    public String toString() {
        return "Citation{" +
                "student=" + student +
                '}';
    }

    @Override
    protected Citation clone() throws CloneNotSupportedException {
        return (Citation) super.clone();
    }

    public static void main(String[] args) throws CloneNotSupportedException {
        Student student = new Student();
        student.setName("张三");
        Citation c1 = new Citation();
        c1.setStudent(student);
        Citation c2 = c1.clone();
        System.out.println(c1);
        System.out.println(c2);
        System.out.println(c1 == c2);
    }
}

4.建造者模式

(1).概述

将一个复杂对象的构建与表示分离,使得同样的构建过程可以创建不同的表示。
四.创建者模式_第9张图片

  • 分离了部件的构造(由Builder来负责)和装配(由Director负责)。从而可以构造出复杂的对象。这个模式适用于:某个对象的构建过程复杂的情况。
  • 由于实现了构建和装配的解耦。不同的构建器,相同的装配,也可以做出不同的对象;相同的构建器,不同的装配顺序也可以做出不同的对象。也就是实现了构建算法,装配算法的解耦,实现更好的复用。
  • 建造者模式可以将部件和其组装过程分开,一步一步构建一个复杂的对象。用户只需要指定复杂对象的类型就可以得到该该对象,而无须知道知道其内部的具体构造细节。

(2).结构

建造者(Builder)模式包含如下角色:

  • 抽象建造者类(Builder): 这个接口规定要实现复杂对象的那些部分的构建,并不涉及具体的对象部件的构建。
  • 具体建造者类(ConcreteBuilder): 实现Builder接口,完成复杂产品的各个部件的具体创建方法。在构造过程完成后,提供产品的实例。
  • 产品类(Product): 要创建的复杂对象
  • 指挥者类(Director): 调用具体构造者来创建复杂对象的各个部分,在指导者不涉及具体产品的信息,只负责保证对象各部分完整创建或按某种顺序创建。
    类图如下:
    四.创建者模式_第10张图片

(3).实例

创建共享单车
生产自行车是一个复杂的过程,它包含了车架,车座等组件的生产。而车架又有炭纤维,铝合金等材质的,车座有橡胶,真皮等材质。对于自行车的生产就可以使用建造者模式。
这里Bike是产品,包含车架,车座等组件;Builder是抽象建造者,MobikeBuilder和OfoBuilder是具体的建造者;Director是指挥者。类图如下:
四.创建者模式_第11张图片
具体的代码如下:

public class Bike{
   
   private String frame; //车架

   private String seat; //车座

   public String getFrame(){
       return frame;
   }

   public void setFrame(String frame){
       this.frame = frame; 
   }

   public String getSeat(){
       return seat;
   }

    public void setSeat(String seat){
        this.seat = seat;
    }    
}
public abstract class Builder{

   //声明Bike类型的变量,并进行赋值
   protected Bike bike = new Bike();

   public abstract void buildFrame();

   public abstract void buildSeat();
   
   //构建自行车的方法
   public abstract Bike createBike();
   
}
public class MobileBuilder extends Builder{
    
    @Override
    public void buildFrame(){
        bike.setFrame("碳纤维车架");
    }
    
    @Override
    public void buildSeat(){
         bike.setSeat("真皮座椅");
    }
    
    @Override 
    public Bike createBike(){
       return bike;
    }
}
public class OfoBuilder extends Builder{

    @Override
    public void buildFrame(){
        bike.setFrame("铝合金车架");
    }
    
    @Override
    public void buildSeat(){
         bike.setSeat("橡皮车座");
    }
    
    @Override 
    public Bike createBike(){
       return bike;
    }
}
public class Director{
   
   //声明builder类型变量
   private Builder builder;

    public Director(Builder builder){
        this.builder = builder;
    }
    
    //组装自行车功能
    public Bike construct(){
        builder.buildFrame();
        builder.buildSeat();
        return builder.createBike();
    }
}
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在建造者模式中具有很重要的作用,它用于指导具体构建这如何构建产品,控制调用先后次序,并向调用者返回完整的产品类,但是有些情况下需要简化系统结构,可以把指挥者类和抽象建造者进行结合

//抽象builder类
public abstract class Builder{

    protected Bike mBike = 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中。

(4).优缺点

优点:

  • 建造者模式的封装性很好。使用建造者模式可以有效的封装变化,在使用建造者模式的场景中,一般产品类和建造者类是比较稳定的,因此,将主要的业务逻辑封装在指挥者类中对整体而言可以取得比较好的稳定性。
  • 在建造者模式中,客户端不必知道产品内部组成的细节,将产品本身与产品的创建过程解耦,使得相同的构建过程可以创建不同的产品对象。
  • 可以更加精细地控制产品的创建过程,将复杂产品的创建步骤分解在不同的方法中,使得创建过程更加清晰,也更方便使用程序来控制创建过程。
  • 建造者模式很容易进行扩展。如果有新的需求,通过实现一个新地建造者类就可以完成,基本上不用修改之前已经测试通过的代码,因此也就不会对原有功能引入风险。符合开闭原则。

缺点

建造者模式所创建的产品一般具有较多的共同点,其组成部分相似,如果产品之间的差异性很大,则不适合使用建造者模式,因此其使用范围受到一定的限制。

(5).使用场景

建造者(Builder)模式创建的是复杂对象,其产品的各个部分经常面临剧烈的变化,但将它们组合在一起的算法却相对稳定,所以它通常在以下场合使用。

  • 创建的对象较复杂,由多个部件构成,各部件面临着复杂的变化,但构件间的建造顺序是稳定的。
  • 创建复杂对象的算法独立于该对象的组成部分以及它们的装配方式,即产品的构建过程和最终的表示是独立的。

(6).模式扩展

建造者模式除了上面的用途外,在开发中还有一个常用的使用方式,就是当一个类构造器需要传入多个参数时,如果创建这个类的实例,代码可读性非常差,而且很容易引入错误,此时就可以利用建造者模式进行重构。
重构前代码如下:

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 class Client{
  public static void main(String[] args){
       //构建Phone对象
       Phone phone = new Phone("intel", "王星屏幕",  "金士顿", "华硕");
       System.out.println(phone);
  }
}

上面在客户端代码中构建Phone对象,传递了四个参数,如果参数更多呢?代码的可读性及使用的成本就是比较高。
重构后代码:

public class Phone{

    private String cpu;
    private String screen;
    private String memory;
    private String mianboard;

    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);
        }
    }
}
public class Client{
   public static void main(String[] args){
       Phone phone = new Phone.Builder()
                      .cpu("intel")
                      .screen("三星屏幕")
                      .memory("金士顿内存条")
                      .mainboard("华硕主板")
                      .build();
        System.out.println(phone);               
   }
}

6.创建者模式对比

(1).工厂方法模式vs建造者模式

工厂方法模式注重的是整体对象的创建方式;而建造者模式注重的是部件构建的过程,意在通过一步一步地精确构造创建出一个复杂的对象。
例如:要制造一个超人,如果使用工厂方法模式,直接产生出的就是一个力大无穷,能够飞翔,内裤外穿的超人;而如果使用建造者模式,则需要组装手,头,脚,躯干等部分,然后再把内裤外穿,于是一个超人就诞生了。

(2).抽象工厂模式vs建造者模式

抽象工厂模式实现对产品家族的创建,一个产品家族是这样的一系列产品: 具有不同分类纬度的产品组合,采用抽象工厂模式则是不需要关心构建过程,只关心什么产品由什么工厂生产即可。
建造者模式则是要求按照指定的蓝图建造产品,它的主要目的是通过组装零配件而产生一个新产品。
如果将抽象工厂模式看成汽车配件生产厂,生成一个产品族的产品,那么建造者模式就是一个汽车组装工厂,通过对部件的组装可以返回一辆完整的汽车。

你可能感兴趣的:(java)