Java设计模式(二)


uml类图的六种关系

分类

  1. 创建型模式:单例模式,抽象工厂模式,原型模式,建造者模式,工厂模式
  2. 结构型模式:适配器模式,桥接模式,装饰模式,组合模式,外观模式,享元模式,代理模式
  3. 行为型模式:模板方法模式,命令模式,访问者模式,迭代器模式,观察者模式,中介者模式,备忘录模式,解释器模式,状态模式,策略模式,职责链模式。

创建型模式

单例模式

介绍:
所谓的单例设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法(静态方法)。

比如:Hibernate的SessionFactory,它充当数据存储源的代理,并负责创建Session对象,SessionFactory并不是轻量级的,一般情况下,一个项目通常只需要一个SessionFactory就够,这就会使用到单例模式。

单例模式的八种创建方式:

  1. 饿汉式(静态常量)
  2. 饿汉式(静态代码块)
  3. 懒汉式(线程不安全)
  4. 懒汉式(线程安全,同步方法)
  5. 懒汉式(线程安全,同步代码块)
  6. 双重检查
  7. 静态内部类
  8. 枚举

饿汉式-静态常量应用实例:
步骤如下:
1)构造器私有化
2)类的内部创建对象
3)向外暴露一个静态的公共方法
4)代码实现

package com.ming.creation.singleton.type1;

/**
 * @Author: mei_ming
 * @DateTime: 2022/11/14 22:31
 * @Description: 单例模式--饿汉式(静态变量)
 */
public class SingletonTest01 {
    public static void main(String[] args) {
        Singleton instance = Singleton.getInstance();
        Singleton instance2 = Singleton.getInstance();
        System.out.println(instance==instance2);  // true
        System.out.println(instance.hashCode());  // 1163157884
        System.out.println(instance2.hashCode());  // 1163157884
    }

}
//饿汉式(静态变量)
class Singleton{
    //1. 构造器私有化,外部不能 new
    private Singleton(){}

    //2. 本类内部创建对象实例
    private final static Singleton instance = new Singleton();

    //3. 提供一个公有的静态方法,返回实例对象
    public static Singleton getInstance(){
        return instance;
    }
}

优缺点
1)优点:这种写法简单,在类加载的时候就完成实例化。避免了线程同步问题。
2)缺点:在类加载的时候就完成实例化,没有达到懒加载的效果,如果从始至终从未使用过这个实例,就会造成内存浪费。
3)这种方式基于classloader机制避免了多线程的同步问题,不过,instance在类加载时就实例化,在单例模式中大多数都是调用getInstance方法,但时导致类装载的原因有很多种,因此不能确定有其他的方式(或者其他的静态方法)导致类加载,这时候初始化instance就没有达到懒加载的效果。
4)结论:这种单例模式可用,可能会造成内存浪费

饿汉式-静态代码块应用实例:

package com.ming.creation.singleton.type2;

/**
 * @Author: mei_ming
 * @DateTime: 2022/11/14 22:39
 * @Description: 单例模式--饿汉式(静态代码块)
 */
public class SingletonTest02 {
    public static void main(String[] args) {
        Singleton instance = Singleton.getInstance();
        Singleton instance2 = Singleton.getInstance();
        System.out.println(instance==instance2);  // true
        System.out.println(instance.hashCode());  // 1163157884
        System.out.println(instance2.hashCode());  // 1163157884
    }
}

//饿汉式(静态代码块)
class Singleton{
    //1. 构造器私有化,外部不能 new
    private Singleton(){}

    //2. 本类内部创建对象实例
    private static Singleton instance;

    static {  // 3. 在静态代码块中,创建单例对象
        instance = new Singleton();
    }

    //4. 提供一个公有的静态方法,返回实例对象
    public static Singleton getInstance(){
        return instance;
    }
}

优缺点
1)和上面的方式类似,只不过将实例化的过程放在静态代码块中,也是在类加载的时候,就执行静态代码块中的代码,初始化类的实例。
2)结论:这种单例模式可用,可能会造成内存浪费

懒汉式-线程不安全应用实例:

package com.ming.creation.singleton.type3;

/**
 * @Author: mei_ming
 * @DateTime: 2022/11/15 22:06
 * @Description: 单例模式--懒汉式(线程不安全)
 */
public class SingletonTest03 {
    public static void main(String[] args) {
        System.out.println("懒汉式--线程不安全");
        Singleton instance = Singleton.getInstance();
        Singleton instance2 = Singleton.getInstance();
        System.out.println(instance==instance2);  // true
        System.out.println(instance.hashCode());  // 1163157884
        System.out.println(instance2.hashCode());  // 1163157884
    }
}

//懒汉式(线程不安全)
class Singleton{
    //1. 构造器私有化,外部不能 new
    private Singleton(){}

    //2. 本类内部创建对象实例
    private static Singleton instance;

    //3. 提供一个公有的静态方法,当使用到该方法是,才会创建instance,返回实例对象
    public static Singleton getInstance(){
        if(instance == null){
            instance = new Singleton();
        }
        return instance;
    }
}

优缺点
1)起到了懒加载的效果,但是只能在单线程下使用。
2)如果在多线程下,一个线程进入了if(singleton==null)判断语句块,还未来得及往下执行,另一个线程也通过了这个判断,这时便会产生多个实例。所以多线程下不可用。
3)结论:在实际开发中,不要使用

懒汉式-线程安全,同步方法应用实例:

package com.ming.creation.singleton.type4;

/**
 * @Author: mei_ming
 * @DateTime: 2022/11/15 22:21
 * @Description: 懒汉式--线程安全,同步方法
 */
public class SingletonTest04 {
    public static void main(String[] args) {
        System.out.println("懒汉式--线程安全,同步方法");
        Singleton instance = Singleton.getInstance();
        Singleton instance2 = Singleton.getInstance();
        System.out.println(instance==instance2);  // true
        System.out.println(instance.hashCode());  // 1163157884
        System.out.println(instance2.hashCode());  // 1163157884
    }
}

//懒汉式(线程安全,同步方法)
class Singleton{
    //1. 构造器私有化,外部不能 new
    private Singleton(){}

    //2. 本类内部创建对象实例
    private static Singleton instance;

    //3. 提供一个公有的静态方法,加入同步处理的代码,解决了线程安全问题
    public static synchronized Singleton getInstance(){
        if(instance == null){
            instance = new Singleton();
        }
        return instance;
    }
}

优缺点
1)解决了线程不安全的问题
2)效率太低,每个线程在想获得类的实例时,执行getInstance()方法都要进行同步,而其实这个方法执行一次实例化代码就行了,后面的想获得该类实例,直接return就行了。
3)结论:在实际开发中,不推荐使用。

懒汉式-线程安全,同步代码块应用实例:

//懒汉式(线程安全,同步代码块)
class Singleton{
    private Singleton(){}
    
    private static Singleton instance;

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

优缺点
1)这种写法,本意时想对第四种实现方式的改进,因为前面同步方法效率太低,改为同步代码块
2)但是这种同步斌不能起到线程同步的作用,和第3种实现一样,会产生多个实例。
3)结论:在实际开发中,不要使用

双重检查应用实例:

package com.ming.creation.singleton.type6;

/**
 * @Author: mei_ming
 * @DateTime: 2022/11/15 22:51
 * @Description: 单例模式--双重检查
 */
public class SingletonTest06 {
    public static void main(String[] args) {
        System.out.println("单例模式--双重检查");
        Singleton instance = Singleton.getInstance();
        Singleton instance2 = Singleton.getInstance();
        System.out.println(instance==instance2);  // true
        System.out.println(instance.hashCode());  // 1163157884
        System.out.println(instance2.hashCode());  // 1163157884
    }
}
//双重检查
class Singleton{
    private Singleton(){}

    private static volatile Singleton instance;

    //提供一个静态的公有方法,加入双重检查代码,解决线程安全问题,同时解决懒加载问题,同时保证效率
    //a,b线程同时进入第一个判断里,假设a线程进入同步块,创建完实例,就走出同步块,释放锁
    //b线程进入,发现已经有实例,则跳出同步块
    //c,d,e线程调用getInstance方法,则不用进行同步处理,直接返回a线程创建的实例
    public static Singleton getInstance(){
        if(instance == null){
            synchronized (Singleton.class){
                if(instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

优缺点
1)Double-Check概念是多线程开发中常使用到的,如代码中所示,我们进行了两次 if(singleton==null) 检查,这样就可以保证线程安全了。
2)这样,实例化代码只用执行一次,后面再次访问时,判断 if(singleton==null),直接return 实例化对象,也避免了反复进行方法同步。
3)线程安全,延迟加载,效率较高。
4)结论:在实际开发中,推荐使用

静态内部类应用实例:

package com.ming.creation.singleton.type7;

/**
 * @Author: mei_ming
 * @DateTime: 2022/11/15 22:51
 * @Description:  静态内部类
 */
public class SingletonTest07 {
    public static void main(String[] args) {
        System.out.println("静态内部类");
        Singleton instance = Singleton.getInstance();
        Singleton instance2 = Singleton.getInstance();
        System.out.println(instance==instance2);  // true
        System.out.println(instance.hashCode());  // 1163157884
        System.out.println(instance2.hashCode());  // 1163157884
    }
}
//静态内部类完成
class Singleton{
    private Singleton(){}

    //写一个静态内部类,该类中有一个静态属性 Singleton
    private static class SingletonInstance{
        private static final Singleton SINGLETON = new Singleton();
    }

    //提供静态的公有方法,直接返回SingletonInstance.SINGLETON
    public static Singleton getInstance(){
        return SingletonInstance.SINGLETON;
    }
}

优缺点
1)这种方式采用了类加载的机制来保证初始化实例只有一个线程。
2)静态内部类方式在Singleton类被装载时并不会立即实例化,而是在需要实例化时,调用getInstance方法,才会加载SingletonInstance类,从而完成Singleton的实例化。
3)类的静态属性只会在第一次加载类的时候初始化,所以在这里,JVM帮助我们保证了线程的安全性,在类进行初始化时,别的线程时无法进入的。
4)线程安全,延迟加载,效率较高。
5)结论:在实际开发中,推荐使用

枚举应用实例:

package com.ming.creation.singleton.type8;

/**
 * @Author: mei_ming
 * @DateTime: 2022/11/16 22:36
 * @Description: 单例模式--枚举
 */
public class SingletonTest08 {
    public static void main(String[] args) {
        System.out.println("枚举");
        Singleton instance = Singleton.SINGLETON;
        Singleton instance2 = Singleton.SINGLETON;
        System.out.println(instance==instance2);  // true
        System.out.println(instance.hashCode());  // 1163157884
        System.out.println(instance2.hashCode());  // 1163157884
        instance.sayHello();
    }
}

//枚举
enum Singleton{
    SINGLETON;
    public  void sayHello(){
        System.out.println("hello~");
    }
}

优缺点
1)借助JDK1.5中添加的枚举来实现单例模式,不仅能避免多线程同步的问题,而且还能防止反序列化重新创建新的对象。
2)结论:推荐使用

单例模式在JDK应用的源码分析
1)在JDK中,java.lang.Runtime就是经典的单例模式
2)代码分析

//饿汉式
public class Runtime {
    private static Runtime currentRuntime = new Runtime();
    
    public static Runtime getRuntime() {
        return currentRuntime;
    }

    private Runtime() {}
}

单例模式注意事项和细节
1)单例模式保证了系统内存中该类只存在一个对象,节省了系统资源,对于一些需要频繁创建销毁的对象,使用单例模式可以提供性能。
2)当想实例化一个单例类的时候,必须要记住使用相应的获取对象的方法,而不是使用 new
3)单例模式使用的场景:需要频繁的进行创建和销毁的对象,创建对象时耗时过多或耗费资源过多,但又经常用到的对象,工具类对象,频繁访问数据库或文件的对象(比如数据源,session工厂等)

工厂模式

简单工厂模式

基本介绍

  1. 简单工厂模式属于创建型模式,是工厂模式的一种。简单工厂模式是由一个工厂对象决定创建出哪一种产品类的实例。简单工厂模式是工厂模式家族种最简单的模式。
  2. 简单工厂模式:定义了一个创建对象的类,由这个类来封装实例化对象的行为(代码)
  3. 在软件开发中,当我们会用到大量的创建某种,某类或某批对象时,就会使用到工厂模式。

组成

  • 抽象产品 :定义了产品的规范,描述了产品的主要特性和功能。(Keyboard)
  • 具体产品 :实现或者继承抽象产品的子类 ,(DellKeyboard,HPKeyboard,LenovoKeyboard)
  • 具体工厂 :提供了创建产品的方法,调用者通过该方法来获取产品。(KeyboardFactory)
public interface Keyboard {
    void print();
    void input(String context);
}

public class DellKeyboard implements Keyboard{
    @Override
    public void print() {
        System.out.println("DELL键盘 print");
    }

    @Override
    public void input(String context) {
        System.out.println("DELL键盘 input "  + context);
    }
}

public class HPKeyboard implements Keyboard{
    @Override
    public void print() {
        System.out.println("HP键盘 print");
    }

    @Override
    public void input(String context) {
        System.out.println("HP键盘 input "  + context);
    }
}

public class LenovoKeyboard implements Keyboard{
    @Override
    public void print() {
        System.out.println("Lenovo键盘 print");
    }

    @Override
    public void input(String context) {
        System.out.println("Lenovo键盘 input " + context);
    }
}

//具体工厂
public class KeyboardFactory {
    public Keyboard getInstance(String brand){
        if("hp".equals(brand)){
            return new HPKeyboard();
        }else if("dell".equals(brand)){
            return new DellKeyboard();
        }else if("lenovo".equals(brand)){  //新增Lenovo类型判断
            return new LenovoKeyboard();
        }
        return null;
    }
}

测试:

public class SimpleFactoryTest {
    public static void main(String[] args) {
        Scanner scan = new Scanner(System.in);
        KeyboardFactory keyboardFactory = new KeyboardFactory();
        System.out.println("请输入你想要的品牌的键盘");  //hp,dell,lenovo
        String brand=scan.nextLine();
        Keyboard instance = keyboardFactory.getInstance(brand);
        instance.input("test");
        instance.print();
    }
}

简单工厂模式的优缺点
优点:封装了创建对象的过程,可以通过参数直接获取对象。把对象的创建和业务逻辑层分开,这样以后就避免了修改客户代码,如果要实现新产品直接修改工厂类,而不需要在原代码中修改,这样就降低了客户代码修改的可能性,更加容易扩展。
缺点:getInstance()方法利用if-else创建并返回具体的键盘实例,如果增加新的键盘子类,键盘工厂的创建方法中就要增加新的if-else。这种做法扩展性差,违背了开闭原则,也影响了可读性。

工厂方法模式

基本介绍:
工厂方法模式设计方案:定义了一个创建对象的抽象方法,由子类决定要实例化的类。

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

代码实现:

//具体产品及抽象产品同上

//抽象工厂
public interface IKeyboardFactory {
    Keyboard getInstance();
}
//具体工厂
public class DellKeyboardFactory implements IKeyboardFactory {
    @Override
    public Keyboard getInstance() {
        return new DellKeyboard();
    }
}

public class HPKeyboardFactory implements IKeyboardFactory {
    @Override
    public Keyboard getInstance() {
        return new HPKeyboard();
    }
}

public class LenovoKeyboardFactory implements IKeyboardFactory {
    @Override
    public Keyboard getInstance() {
        return new LenovoKeyboard();
    }
}

测试类:

public class FactoryMethodTest {
    public static void main(String[] args) {
        Scanner scan = new Scanner(System.in);
        System.out.println("请输入你想要的品牌的键盘");  //hp,dell,lenovo
        String brand = scan.nextLine();
        Keyboard instance;
        if ("hp".equals(brand)) {
            instance = new HPKeyboardFactory().getInstance();
        } else if ("dell".equals(brand)) {
            instance = new DellKeyboardFactory().getInstance();
        } else if ("lenovo".equals(brand)) {  //新增Lenovo类型判断
            instance = new LenovoKeyboardFactory().getInstance();
        } else {
            throw new RuntimeException("没有该brand");
        }
        instance.input("test");
        instance.print();
    }
}

工厂方法模式的优缺点
优点:

  • 用户只需要知道具体工厂的名称就可得到所要的产品,无须知道产品的具体创建过程;
  • 在系统增加新的产品时只需要添加具体产品类和对应的具体工厂类,无须对原工厂进行任何修改,
  • 满足开闭原则;
    缺点:
  • 每一种品牌对应一个工厂子类,在创建具体键盘对象时,实例化不同的工厂子类。但是,如果业务涉及的子类越来越多,难道每一个子类都要对应一个工厂类吗?这样会使得系统中类的个数成倍增加,增加了代码的复杂度

抽象工厂模式:

基本介绍
抽象工厂模式是一种为访问类提供一个创建一组相关或相互依赖对象的接口,且访问类无须指定所要产品的具体类就能得到同族的不同等级的产品的模式结构。

抽象工厂模式是工厂方法模式的升级版本,工厂方法模式只生产一个等级的产品,而抽象工厂模式可生产多个等级的产品。

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

代码实现

public interface Keyboard {
    void print();
}
public class DellKeyboard implements Keyboard{
    @Override
    public void print() {
        System.out.println("DELL键盘 print");
    }
}
public class HPKeyboard implements Keyboard{
    @Override
    public void print() {
        System.out.println("HP键盘 print");
    }
}
public interface MainFrame {
    void run();
}
public class DellMainFrame implements MainFrame{
    @Override
    public void run() {
        System.out.println("Dell 主机 run~");
    }
}
public class HPMainFrame implements MainFrame{
    @Override
    public void run() {
        System.out.println("HP 主机 run~");
    }
}
public class DellMonitor implements Monitor{
    @Override
    public void play() {
        System.out.println("Dell显示器 play~");
    }
}
public class HPMonitor implements Monitor{
    @Override
    public void play() {
        System.out.println("HP显示器 play~");
    }
}

//抽象工厂
public interface IFactory {
    MainFrame createMainFrame();
    Monitor createMonitor();
    Keyboard createKeyboard();
}
public class DellFactory implements IFactory{
    @Override
    public MainFrame createMainFrame() {
        return new DellMainFrame();
    }

    @Override
    public Monitor createMonitor() {
        return new DellMonitor();
    }

    @Override
    public Keyboard createKeyboard() {
        return new DellKeyboard();
    }
}
public class HPFactory implements IFactory{
    @Override
    public MainFrame createMainFrame() {
        return new HPMainFrame();
    }

    @Override
    public Monitor createMonitor() {
        return new HPMonitor();
    }

    @Override
    public Keyboard createKeyboard() {
        return new HPKeyboard();
    }
}

测试类:

public class AbsFactoryTest {
    public static void main(String[] args) {
        Scanner scan = new Scanner(System.in);
        System.out.println("请输入你想要的品牌");  //hp,dell,lenovo
        String brand = scan.nextLine();
        IFactory factory;
        if ("hp".equals(brand)) {
            factory = new HPFactory();
        } else if ("dell".equals(brand)) {
            factory = new DellFactory();
        } else {
            throw new RuntimeException("找不到该brand");
        }

        factory.createKeyboard().print();
        factory.createMainFrame().run();
        factory.createMonitor().play();
    }
}

抽象工厂模式的优缺点

  • 增加分组非常简单,例如要增加Lenovo分组,只需创建Lenovo工厂和具体的产品实现类。
  • 分组中的产品扩展非常困难,要增加一个鼠标Mouse,既要创建抽象的Mouse接口, 又要增加具体的实现:DellMouse、HPMouse, 还要再每个Factory中定义创建鼠标的方法实现。

工厂模式在JDK中的运用:
Calendar类

package com.ming.creation.factory.jdksrc;

import java.util.Calendar;

/**
 * @Author: mei_ming
 * @DateTime: 2022/11/21 15:33
 * @Description: Calender中工厂模式的使用
 */
public class Factory {
    public static void main(String[] args) {
        Calendar instance = Calendar.getInstance();
        // 注意月份下标从0开始,所以取月份要+1
        System.out.println("年:"+instance.get(Calendar.YEAR));
        System.out.println("月:"+(instance.get(Calendar.MONTH)+1));
        System.out.println("日:"+instance.get(Calendar.DAY_OF_MONTH));
        System.out.println("时:"+instance.get(Calendar.HOUR_OF_DAY));
        System.out.println("分:"+instance.get(Calendar.MINUTE));
        System.out.println("秒:"+instance.get(Calendar.SECOND));
    }
    /**
     * 1. 调用Calendar的getInstance()方法:
     * public static Calendar getInstance()
     *     {
     *         return createCalendar(TimeZone.getDefault(), Locale.getDefault(Locale.Category.FORMAT));
     *     }
     *
     * 2. 调用createCalendar()方法:
     *
     * private static Calendar createCalendar(TimeZone zone,
     *                                            Locale aLocale)
     *     {
     *         CalendarProvider provider =
     *             LocaleProviderAdapter.getAdapter(CalendarProvider.class, aLocale)
     *                                  .getCalendarProvider();
     *         if (provider != null) {
     *             try {
     *                 return provider.getInstance(zone, aLocale);
     *             } catch (IllegalArgumentException iae) {
     *                 // fall back to the default instantiation
     *             }
     *         }
     *
     *         Calendar cal = null;
     *
     *          // 3. 在这里使用到简单工厂模式
     *         if (aLocale.hasExtensions()) {
     *             String caltype = aLocale.getUnicodeLocaleType("ca");
     *             if (caltype != null) {
     *                 switch (caltype) {
     *                 case "buddhist":
     *                 cal = new BuddhistCalendar(zone, aLocale);
     *                     break;
     *                 case "japanese":
     *                     cal = new JapaneseImperialCalendar(zone, aLocale);
     *                     break;
     *                 case "gregory":
     *                     cal = new GregorianCalendar(zone, aLocale);
     *                     break;
     *                 }
     *             }
     *         }
     *         ....
     *         return cal;
     *     }
     */
}

工厂模式小结:

  1. 工厂模式的意义:将实例化对象的代码提取出来,放到一个类中统一管理和维护,达到和主项目的依赖关系的解耦。从而提高项目的扩展和维护性。
  2. 三种工厂模式:简单工厂模式,工厂方法模式,抽象工厂模式。
  3. 设计模式的依赖抽象原则:
    • 创建对象实例时,不要直接new 类,而是把这个new类的动作放在一个工厂的方法中,并返回。
    • 不要让类继承具体类,而是继承抽象类或者实现interface(接口)
    • 不要覆盖基类中已经实现的方法。

简单工厂:唯一工厂类,一个产品抽象类,工厂类的创建方法依据入参判断并创建具体产品对象。
工厂方法:多个工厂类,一个产品抽象类,利用多态创建不同的产品对象,避免了大量的if-else判断。
抽象工厂:多个工厂类,多个产品抽象类,产品子类分组,同一个工厂实现类创建同组中的不同产品,减少了工厂子类的数量。

原型模式

问题:如何实现克隆羊,要求创建5个一样的羊,名字为tom,1岁,白色。
传统方法:

public class Sheep {
    private String name;
    private int age;
    private String color;
}

public class Client {
    public static void main(String[] args) {
        Sheep sheep = new Sheep("tom",1,"白色");

        Sheep sheep1 = new Sheep(sheep.getName(), sheep.getAge(), sheep.getColor());
        Sheep sheep2 = new Sheep(sheep.getName(), sheep.getAge(), sheep.getColor());
        Sheep sheep3 = new Sheep(sheep.getName(), sheep.getAge(), sheep.getColor());
        Sheep sheep4 = new Sheep(sheep.getName(), sheep.getAge(), sheep.getColor());

        System.out.println(sheep);  // Sheep{name='tom', age=1, color='白色'}
        System.out.println(sheep1);  // Sheep{name='tom', age=1, color='白色'}
        System.out.println(sheep2);  // Sheep{name='tom', age=1, color='白色'}
        System.out.println(sheep3);  // Sheep{name='tom', age=1, color='白色'}
        System.out.println(sheep4);  // Sheep{name='tom', age=1, color='白色'}
    }
}

缺点:

  • 在创建新的对象时,总是需要重新获取原始对象的属性,如果创建的对象比较复杂时,效率较低,
  • 不能动态创建,不够灵活

改进思路:
Java中Object类时所有类的根类,Object类提供了一个clone()方法,该方法可以将一个Java对象复制一份,但需要实现clone的Java类必须实现一个接口Cloneable,该接口表示该类能够复制且具有复制的能力 --> 原型模式

原型模式基本介绍

  1. 原型模式(prototype)是指:用原型实例指定创建对象的种类,并且通过拷贝这些原型,创建新的对象。
  2. 原型模式是一种创建型设计模式,允许一个对象再创建另外一个可定制的对象,无需知道如何创建的细节。
  3. 工作原理:通过将一个原型对象传给哪个要发动创建的对象,这个要发动创建的对象通过请求原型对象拷贝他们自己来实现创建,即 对象.clone()

组成

  • 抽象原型类:规定了具体原型对象必须实现的的 clone() 方法。
  • 具体原型类:实现抽象原型类的 clone() 方法,它是可被复制的对象。
  • 访问类:使用具体原型类中的 clone() 方法来复制新的对象。

代码演示

public class Sheep implements Cloneable{
    private String name;
    private int age;
    private String color;
	//.....
    //克隆该实例,使用默认的clone()方法
    @Override
    protected Object clone() {
        Sheep sheep = null;
        try {
            sheep = (Sheep) super.clone();
        } catch (CloneNotSupportedException e) {
            System.out.println(e.getMessage());
            e.printStackTrace();
        }
        return sheep;
    }
}

测试:

public class PhotoTypeTest {
    public static void main(String[] args) {
        Sheep sheep = new Sheep("tom",1,"白色");
        Sheep sheep1 =(Sheep) sheep.clone();
        Sheep sheep2 =(Sheep) sheep.clone();
        Sheep sheep3 =(Sheep) sheep.clone();
        System.out.println(sheep);  // Sheep{name='tom', age=1, color='白色'}
        System.out.println(sheep1);  // Sheep{name='tom', age=1, color='白色'}
        System.out.println(sheep2);  // Sheep{name='tom', age=1, color='白色'}
        System.out.println(sheep3);  // Sheep{name='tom', age=1, color='白色'}
    }
}

原型模式在Spring框架的运用:

<!-- beans.xml -->
<bean id= "id01" class="com.ming.bean.Master" scope="phototype">
public class Master{
	private Integer id=10;
	private String name="孙悟空";
}
public class PhotoTypeTest {
    public static void main(String[] args) {
        ApplicationContext app = new ClassPathXmlApplicationContext("beans.xml");
        //获取master[通过id获取master]
        Object bean = app.getBean("id01");
        System.out.println(bean);
        Object bean2 = app.getBean("id01");
        System.out.println(bean2);
        System.out.println(bean==bean2);  // false 说明是原型模式创建的bean2
    }
}

原型模式的克隆分为浅拷贝和深拷贝。

  • 浅拷贝:
    • 对于数据类型是基本类型的成员变量,浅拷贝会直接进行值传递,也就是将该属性值复制一份给新的对象
    • 对于引用类型的成员变量,仅把内存地址复制一份给新的对象,实际上两个变量都指向同一个实例。
  • 深拷贝:
    • 基本类型还是引用类型,都会复制创建新的对象。
    • 实现方式1:重写clone()方法
    • 实现方式2:通过对象序列化来实现(推荐)

问:默认情况下,如果属性是对象,拷贝时是哪一种?
答:属于浅拷贝,对象的hashcode相等。
例:

public class PhotoTypeTest {
    public static void main(String[] args) {
        Sheep sheep = new Sheep("tom",1,"白色");
        Sheep friend = new Sheep("jack", 1, "黑色");
        sheep.setFriend(friend);
        Sheep sheep1 =(Sheep) sheep.clone();
        System.out.println("sheep:"+sheep+" sheep.friend:"+sheep.getFriend().hashCode());
        // sheep:Sheep{name='tom', age=1, color='白色} sheep.friend:21685669
        System.out.println("sheep1:"+sheep1+" sheep1.friend:"+sheep1.getFriend().hashCode());
        // sheep1:Sheep{name='tom', age=1, color='白色} sheep1.friend:21685669
        
        //结论:默认情况下,clone()方法对于引用类型成员变量来讲,属于浅拷贝
    }
}

问:如何将引用类型的成员变量也转为深拷贝?
答:有2种方式,具体如下:

public class DeepCloneTarget implements Serializable,Cloneable{
    private static final long serialVersionUID = 1L;

    private String cloneName;
    private String cloneClass;

    public DeepCloneTarget(String cloneName, String cloneClass) {
        this.cloneName = cloneName;
        this.cloneClass = cloneClass;
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}
public class DeepProtoType implements Serializable, Cloneable {

    public String name;
    public DeepCloneTarget deepCloneTarget;  //引用类型

    public DeepProtoType() {
        super();
    }

    //深拷贝 -> 方式1
    @Override
    protected Object clone() throws CloneNotSupportedException {
        Object deep = null;
        // 这里完成对基本数据类型的克隆
        deep = super.clone();
        // 对引用数据类型单独处理
        DeepProtoType deepProtoType = (DeepProtoType) deep;
        Object clone = deepCloneTarget.clone();
        deepProtoType.deepCloneTarget = (DeepCloneTarget) clone;

        return deepProtoType;
    }

    //深拷贝 -> 方式2 通过对象的序列化实现深拷贝(推荐)
    public Object deepClone() {
        //创建流对象
        ByteArrayOutputStream baos = null;
        ObjectOutputStream oos = null;
        ByteArrayInputStream bais = null;
        ObjectInputStream ois = null;

        try {
            //序列化
            baos = new ByteArrayOutputStream();
            oos = new ObjectOutputStream(baos);
            oos.writeObject(this);  //当前这个对象以对象流的方式输出

            //反序列化
            bais = new ByteArrayInputStream(baos.toByteArray());
            ois = new ObjectInputStream(bais);
            DeepProtoType copyObj = (DeepProtoType)ois.readObject();
            return copyObj;
        } catch (IOException e) {
            e.printStackTrace();
            return null;
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
            return null;
        }finally {
            //关闭流
            try {
                baos.close();
                oos.close();
                bais.close();
                ois.close();
            } catch (IOException e) {
                System.out.println(e.getMessage());
            }

        }
    }
}

测试类:

public class DeepProtoTypeTest {
    public static void main(String[] args) throws CloneNotSupportedException {
        DeepProtoType p = new DeepProtoType();
        p.name="rose";
        p.deepCloneTarget=new DeepCloneTarget("name1","class1");

        //方式1完成深拷贝
        DeepProtoType p2=(DeepProtoType)p.clone();

        System.out.println("p.name: "+p.name+" p.deepCloneTarget: "+ p.deepCloneTarget.hashCode());
        System.out.println("p2.name: "+p2.name+" p2.deepCloneTarget: "+ p2.deepCloneTarget.hashCode());
//        p.name: rose p.deepCloneTarget: 21685669
//        p2.name: rose p2.deepCloneTarget: 2133927002

        System.out.println("--------------------");
        //方式2实现深拷贝
        DeepProtoType p3=(DeepProtoType)p.deepClone();

        System.out.println("p.name: "+p.name+" p.deepCloneTarget: "+ p.deepCloneTarget.hashCode());
        System.out.println("p3.name: "+p3.name+" p3.deepCloneTarget: "+ p3.deepCloneTarget.hashCode());
//        p.name: rose p.deepCloneTarget: 21685669
//        p3.name: rose p3.deepCloneTarget: 1915910607
    }
}

原型模式的注意事项和细节:

  1. 创建新的对象比较复杂时,可以利用原型模式简化对象的创建过程,同时也能够提高效率
  2. 不用重新初始化对象,而是动态地获取对象运行时的状态
  3. 如果原始对象发生变化(增加属性),其他克隆对象的也会发生相应的变化,无需修改代码
  4. 在实现深拷贝的时候可能需要比较复杂的代码
  5. 缺点:需要为每一个类配备一个克隆方法,这对全新的类来说不是很难,但对已有的类进行改造时,需要修改其源代码,违背了ocp原则。

建造者模式

盖房子需求

  1. 需要建房子:过程为打桩,砌墙,封顶
  2. 房子有各种各样的,比如普通房,高楼,别墅,
  3. 建房子过程虽然一样,但每一步有所变化。

传统方法实现:

public abstract class AbstractHouse {

    //打地基
    public abstract void buildBasic();

    //砌墙
    public abstract void buildWalls();

    //封顶
    public abstract void roofed();

    //组装
    public void build(){
        buildBasic();
        buildWalls();
        roofed();
    }
}

public class CommonHouse extends AbstractHouse{
    @Override
    public void buildBasic() {
        System.out.println("普通房子打地基-5m");
    }

    @Override
    public void buildWalls() {
        System.out.println("普通房子砌墙-10cm");
    }

    @Override
    public void roofed() {
        System.out.println("普通房子封顶-水泥顶");
    }
}

测试:

public class Client {
    public static void main(String[] args) {
        CommonHouse commonHouse = new CommonHouse();
        commonHouse.build();
    }
}

传统方式的优缺点:
优点:比较好理解,简单易操作
缺点:没有设计缓存层对象,程序的扩展和维护不好,即,把产品和创建产品的过程封装在一起,耦合性增强了。
解决方案:将产品和产品创建过程解耦–> 建造者模式

建造者模式基本介绍:

  1. 建造者模式(Builder Pattern)又叫生成器模式,是一种创建型模式。它可以将复杂对象的建造过程抽象出来(抽象类别),使这个抽象过程的不同实现方法可以构造出不同表现(属性)的对象
  2. 由于实现了构建和装配的解耦。不同的构建器,相同的装配,也可以做出不同的对象;相同的构建器,不同的装配顺序也可以做出不同的对象。也就是实现了构建算法、装配算法的解耦,实现了更好的复用。
  3. 建造者模式是一步一步创建一个复杂对象,它允许用户只通过指定复杂对象的类型和内容就可以构建他们,用户不需要知道内部的具体构建细节。

组成

  • 抽象建造者类(Builder):这个接口规定要实现复杂对象的那些部分的创建,并不涉及具体的部件对象的创建。
  • 具体建造者类(Concrete Builder):实现 Builder 接口,完成复杂产品的各个部件的具体创建方法。在构造过程完成后,提供产品的实例。
  • 产品类(Product):要创建的复杂对象。
  • 指挥者类(Director):调用具体建造者来创建复杂对象的各个部分,在指导者中不涉及具体产品的信息,只负责保证对象各部分完整创建或按某种顺序创建,隔离了客户与对象的生产过程。

代码说明

// 抽象建造者
public abstract class HouseBuilder {
    protected House house =new House();

    public abstract void buildBasic();
    public abstract void buildWalls();
    public abstract void roofed();

    //建造房子,返回
    public House buildHouse(){
        return house;
    }
}

// 具体建造者
public class CommonHouse extends HouseBuilder{

    @Override
    public void buildBasic() {
        house.setBasic("5m");
        System.out.println("普通房子打地基-5m");
    }

    @Override
    public void buildWalls() {
        house.setWall("10cm");
        System.out.println("普通房子砌墙-10cm");
    }

    @Override
    public void roofed() {
        house.setRoofed("水泥顶");
        System.out.println("普通房子封顶-水泥顶");
    }
}

 // 具体建造者2
public class HighHouse extends HouseBuilder{
    @Override
    public void buildBasic() {
        house.setBasic("50m");
        System.out.println("高楼打地基-50m");
    }

    @Override
    public void buildWalls() {
        house.setWall("20cm");
        System.out.println("高楼砌墙-20cm");
    }

    @Override
    public void roofed() {
        house.setRoofed("玻璃");
        System.out.println("高楼封顶-玻璃");
    }
}

// 产品
public class House {
    private String basic;
    private String wall;
    private String roofed;

    public String getBasic() {
        return basic;
    }

    public void setBasic(String basic) {
        this.basic = basic;
    }

    public String getWall() {
        return wall;
    }

    public void setWall(String wall) {
        this.wall = wall;
    }

    public String getRoofed() {
        return roofed;
    }

    public void setRoofed(String roofed) {
        this.roofed = roofed;
    }
}

// 指挥者
public class HouseDirector {
    HouseBuilder houseBuilder = null;

    //构造器传入 houseBuilder
    public HouseDirector(HouseBuilder houseBuilder){
        this.houseBuilder=houseBuilder;
    }

    //通过setter, 传入houseBuilder
    public void setHouseBuilder(HouseBuilder houseBuilder) {
        this.houseBuilder = houseBuilder;
    }

    //如何处理建造房子的流程,交给指挥者
    public House constructHouse(){
        houseBuilder.buildBasic();
        houseBuilder.buildWalls();
        houseBuilder.roofed();
        House house = houseBuilder.buildHouse();
        return house;
    }
}

测试:

public class Client {
    public static void main(String[] args) {
        //改普通房子
        CommonHouse commonHouse = new CommonHouse();
        //准备房子的指挥者
        HouseDirector houseDirector = new HouseDirector(commonHouse);
        //调用指挥者中盖房子的流程方法,获取房子
        House house = houseDirector.constructHouse();
        System.out.println(house.getBasic());
        System.out.println(house.getWall());
        System.out.println(house.getRoofed());

        //通过setter 传入新的房子类型
        houseDirector.setHouseBuilder(new HighHouse());
        House house2 = houseDirector.constructHouse();
        System.out.println(house2.getBasic());
        System.out.println(house2.getWall());
        System.out.println(house2.getRoofed());
    }
}

建造者模式的优缺点

优点

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

缺点

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

建造者模式在JDK的应用和源码分析

  • java.lang.StringBuilder中的建造者模式
package com.ming.creation.builder.jdksrc;

/**
 * @Author: mei_ming
 * @DateTime: 2022/11/24 22:44
 * @Description: StringBuilder源码 与 建造者模式
 */
public class Builder {
    public static void main(String[] args) {
        StringBuilder stringBuilder = new StringBuilder();
        stringBuilder.append("123");
        System.out.println(stringBuilder);
        /**
         * 1. StringBuilder 既充当了指挥者,同时还是具体建造者,
         *   建造方法的实现是由 AbstractStringBuilder 完成,而 StringBuilder 继承了 AbstractStringBuilder
         * public final class StringBuilder extends AbstractStringBuilder
         *     implements java.io.Serializable, CharSequence{}
         *
         * 2. AbstractStringBuilder 实现了 Appendable, 是具体建造者
         * abstract class AbstractStringBuilder
         *     implements Appendable, CharSequence {}
         *
         * 3. Appendable 是接口,定义了多个append(),是抽象建造者
         * public interface Appendable {}
         */
    }
}

创建者模式对比

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

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

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值