23种设计模式学习笔记(2)

23种设计模式学习笔记(1)
https://blog.csdn.net/qq_51495235/article/details/115358623

5,单例模式

5.1 单例模式的定义和特点

**单例(Singleton)模式的定义:**指一个类只有一个实例,且该类能自行创建这个实例的一种模式。例如,Windows 中只能打开一个任务管理器,这样可以避免因打开多个任务管理器窗口而造成内存资源的浪费,或出现各个窗口显示内容的不一致等错误。

在计算机系统中,还有 Windows 的回收站、操作系统中的文件系统、多线程中的线程池、显卡的驱动程序对象、打印机的后台处理服务、应用程序的日志对象、数据库的连接池、网站的计数器、Web 应用的配置对象、应用程序中的对话框、系统中的缓存等常常被设计成单例。

单例模式在现实生活中的应用也非常广泛,例如公司 CEO、部门经理等都属于单例模型。J2EE 标准中的 ServletgContext 和 ServletContextConfig、Spring 框架应用中的 ApplicationContext、数据库中的连接池等也都是单例模式。

单例模式有 3 个特点:

  1. 单例类只有一个实例对象;
  2. 该单例对象必须由单例类自行创建;
  3. 单例类对外提供一个访问该单例的全局访问点。

单例模式的优点

  • 单例模式可以保证内存里只有一个实例,减少了内存的开销。

  • 可以避免对资源的多重占用。

  • 单例模式设置全局访问点,可以优化和共享资源的访问。

    单例模式的缺点:

    • 单例模式一般没有接口,扩展困难。如果要扩展,则除了修改原来的代码,没有第二种途径,违背开闭原则。
    • 在并发测试中,单例模式不利于代码调试。在调试过程中,如果单例中的代码没有执行完,也不能模拟生成一个新的对象。
    • 单例模式的功能代码通常写在一个类中,如果功能设计不合理,则很容易违背单一职责原则。

5.2 单例模式的结构与实现

5.2.1 单例模式的结构

  1. 单例类:包含一个实例且能自行创建这个实例的类。
  2. 访问类:使用单例的类。

5.2 代码实现

单例设计模式分类两种:

​ 饿汉式:类加载就会导致该单实例对象被创建

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

饿汉式(静态变量)

package com.zhuang.singleton.type1;

/**
 * @Classname SingletonTest01
 * @Description  饿汉式(静态变量)
 * @Date 2021/3/17 9:26
 * @Created by dell
 */

public class SingletonTest01 {
    public static void main(String[] args) {
        Singleton instance = Singleton.getInstance();
        Singleton instance2 = Singleton.getInstance();
        //判断是否为单例
        System.out.println(instance == instance2);
        System.out.println("intstance的哈希值" + instance.hashCode());
        System.out.println("intstance2的哈希值" + instance2.hashCode());
    }
}

class Singleton {

    //1.构造器私有化,外部能new、
    private Singleton() {

    }

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

    //对外部提供一个公有的静态方法
    public static Singleton getInstance() {
        return instance;
    }
}

说明:

​ 该方式在成员位置声明Singleton类型的静态变量,并创建Singleton类的对象instance。instance对象是随着类的加载而创建的。如果该对象足够大的话,而一直没有使用就会造成内存的浪费。

静态代码块

package com.zhuang.singleton.type2;

/**
 * @Classname SingletonTest02
 * @Description  静态代码块
 * @Date 2021/3/17 9:35
 * @Created by dell
 */

public class SingletonTest02 {
    public static void main(String[] args) {
        Singleton2 instance = Singleton2.getInstance();
        Singleton2 instance2 = Singleton2.getInstance();
        //判断是否为单例
        System.out.println(instance == instance2);
        System.out.println("intstance的哈希值" + instance.hashCode());
        System.out.println("intstance2的哈希值" + instance2.hashCode());
    }
}

class Singleton2 {

    //1.构造器私有化,外部能new、
    private Singleton2() {

    }

    //本类内部创建对象实例
    private static Singleton2 instance;

    /*
    在静态代码块中创建对象
     */
    static {
        instance = new Singleton2();
    }

    //对外部提供一个公有的静态方法
    public static Singleton2 getInstance() {
        return instance;
    }
}

说明:

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

懒汉式 线程不安全

package com.zhuang.singleton.type3;

/**
 * @Classname SingletonTest03
 * @Description  懒汉式 线程不安全
 * @Date 2021/3/17 9:39
 * @Created by dell
 */

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);
        System.out.println("intstance的哈希值" + instance.hashCode());
        System.out.println("intstance2的哈希值" + instance2.hashCode());
    }
}

class Singleton {
    private static Singleton instance;

    private Singleton() {

    }

    //提供一个静态的公有方法 当使用到该方法时,才去创建instance
    public static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

说明:

​ 从上面代码我们可以看出该方式在成员位置声明Singleton类型的静态变量,并没有进行对象的赋值操作,那么什么时候赋值的呢?当调用getInstance()方法获取Singleton类的对象的时候才创建Singleton类的对象,这样就实现了懒加载的效果。但是,如果是多线程环境,会出现线程安全问题。

懒汉式(线程安全 , 同步方法)

package com.zhuang.singleton.type4;

/**
 * @Classname SingletonTest04
 * @Description  懒汉式(线程安全 , 同步方法)
 * @Date 2021/3/17 9:46
 * @Created by dell
 */

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);
        System.out.println("intstance的哈希值" + instance.hashCode());
        System.out.println("intstance2的哈希值" + instance2.hashCode());
    }
}

class Singleton {
    private static Singleton instance;

    private Singleton() {

    }

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

说明:

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

懒汉式(线程安全 , 同步代码块)

package com.zhuang.singleton.type5;

/**
 * @Classname SingletonTest05
 * @Description  懒汉式(线程安全 , 同步代码块)
 * @Date 2021/3/17 9:50
 * @Created by dell
 */

public class SingletonTest05 {
    public static void main(String[] args) {
        System.out.println("懒汉式,线程安全!,同步代码块");
        Singleton instance = Singleton.getInstance();
        Singleton instance2 = Singleton.getInstance();
        //判断是否为单例
        System.out.println(instance == instance2);
        System.out.println("intstance的哈希值" + instance.hashCode());
        System.out.println("intstance2的哈希值" + instance2.hashCode());
    }
}

class Singleton {
    private static Singleton instance;

    private Singleton() {

    }

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

双重检查锁模式是一种非常好的单例实现模式,解决了单例、性能、线程安全问题,上面的双重检测锁模式看上去完美无缺,其实是存在问题,在多线程的情况下,可能会出现空指针问题,出现问题的原因是JVM在实例化对象的时候会进行优化和指令重排序操作。

要解决双重检查锁模式带来空指针异常的问题,只需要使用 volatile 关键字, volatile 关键字可以保证可见性和有序性。

package com.zhuang.singleton.type6;

/**
 * @Classname SingletonTest06
 * @Description  双重检查,推荐使用
 * @Date 2021/3/17 9:54
 * @Created by dell
 */

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);
        System.out.println("intstance的哈希值" + instance.hashCode());
        System.out.println("intstance2的哈希值" + instance2.hashCode());
    }
}

class Singleton {
    private static volatile Singleton instance;

    private Singleton() {

    }

    //提供一个静态的公有方法,加入双重检查代码,加入同步处理的代码,解决懒加载的问题
    //保证效率。推荐使用
    public static synchronized Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                instance = new Singleton();
            }
        }
        return instance;
    }
}

小结:

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

静态内部类实现单例模式!

package com.zhuang.singleton.type7;

/**
 * @Classname SingletonTest07
 * @Description  静态内部类实现单例模式!
 * @Date 2021/3/17 9:59
 * @Created by dell
 */

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);
        System.out.println("intstance的哈希值" + instance.hashCode());
        System.out.println("intstance2的哈希值" + instance2.hashCode());
    }
}

class Singleton {
    private static Singleton instance;

    private Singleton() {

    }

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

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

说明:

​ 第一次加载Singleton类时不会去初始化INSTANCE,只有第一次调用getInstance,虚拟机加载SingletonHolder

并初始化INSTANCE,这样不仅能确保线程安全,也能保证 Singleton 类的唯一性。

小结:

​ 静态内部类单例模式是一种优秀的单例模式,是开源项目中比较常用的一种单例模式。在没有加任何锁的情况下,保证了多线程下的安全,并且没有任何性能影响和空间的浪费。

枚举的方式实现单例模式

package com.zhuang.singleton.type8;

/**
 * @Classname SingletonTest08
 * @Description  枚举的方式实现单例模式
 * @Date 2021/3/17 10:06
 * @Created by dell
 */

public class SingletonTest08 {
    public static void main(String[] args) {
        System.out.println("枚举的方式实现单例模式,推荐使用");
        Singleton instance = Singleton.INSTANCE;
        Singleton instance2 = Singleton.INSTANCE;
        System.out.println(instance == instance2);
        //判断是否为单例
        System.out.println(instance == instance2);
        System.out.println("intstance的哈希值" + instance.hashCode());
        System.out.println("intstance2的哈希值" + instance2.hashCode());
    }
}

/*
枚举
 */
enum Singleton {
    INSTANCE;//属性

    public void method() {
        System.out.println("method()方法被调用...");
    }
}

说明:

​ 枚举方式属于恶汉式方式。

5.3 单例模式的应用场景

  • 需要频繁创建的一些类,使用单例可以降低系统的内存压力,减少 GC。
  • 某类只要求生成一个对象的时候,如一个班中的班长、每个人的身份证号等。
  • 某些类创建实例时占用资源较多,或实例化耗时较长,且经常使用。
  • 某类需要频繁实例化,而创建的对象又频繁被销毁的时候,如多线程的线程池、网络连接池等。
  • 频繁访问数据库或文件的对象。
  • 对于一些控制硬件级别的操作,或者从系统上来讲应当是单一控制逻辑的操作,如果有多个实例,则系统会完全乱套。
  • 当对象需要被共享的场合。由于单例模式只允许创建一个对象,共享该对象可以节省内存,并加快对象访问速度。如 Web 中的配置对象、数据库的连接池等。

5.4 存在的问题

破坏单例模式:

使上面定义的单例类(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;
        }
    }
    

    Test类:

    public class Test {
        public static void main(String[] args) throws Exception {
            //往文件中写对象
            //writeObject2File();
            //从文件中读取对象
            Singleton s1 = readObjectFromFile();
            Singleton s2 = readObjectFromFile();
    
            //判断两个反序列化后的对象是否是同一个对象
            System.out.println(s1 == s2);
        }
    
        private static Singleton readObjectFromFile() throws Exception {
            //创建对象输入流对象
            ObjectInputStream ois = new ObjectInputStream(new FileInputStream("C:\\Users\\dell\\Desktop\\a.txt"));
            //第一个读取Singleton对象
            Singleton instance = (Singleton) ois.readObject();
    
            return instance;
        }
    
        public static void writeObject2File() throws Exception {
            //获取Singleton类的对象
            Singleton instance = Singleton.getInstance();
            //创建对象输出流
            ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("C:\\Users\\dell\\Desktop\\a.txt"));
            //将instance对象写出到文件中
            oos.writeObject(instance);
        }
    }
    

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

  • 反射

    Singleton类:

    public class Singleton {
    
        //私有构造方法
        private Singleton() {}
        
        private static volatile Singleton instance;
    
        //对外提供静态方法获取该对象
        public static Singleton getInstance() {
    
            if(instance != null) {
                return instance;
            }
    
            synchronized (Singleton.class) {
                if(instance != null) {
                    return instance;
                }
                instance = new Singleton();
                return instance;
            }
        }
    }
    

    Test类:

    public class Test {
        public static void main(String[] args) throws Exception {
            //获取Singleton类的字节码对象
            Class clazz = Singleton.class;
            //获取Singleton类的私有无参构造方法对象
            Constructor constructor = clazz.getDeclaredConstructor();
            //取消访问检查
            constructor.setAccessible(true);
    
            //创建Singleton类的对象s1
            Singleton s1 = (Singleton) constructor.newInstance();
            //创建Singleton类的对象s2
            Singleton s2 = (Singleton) constructor.newInstance();
    
            //判断通过反射创建的两个Singleton对象是否是同一个对象
            System.out.println(s1 == s2);
        }
    }
    

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

注意:枚举方式不会出现这两个问题。

问题的解决

  • 序列化、反序列方式破坏单例模式的解决方法

    在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;
        }
        
        /**
         * 下面是为了解决序列化反序列化破解单例模式
         */
        private Object readResolve() {
            return SingletonHolder.INSTANCE;
        }
    }
    

    源码解析:

    ObjectInputStream类

    public final Object readObject() throws IOException, ClassNotFoundException{
        ...
        // if nested read, passHandle contains handle of enclosing object
        int outerHandle = passHandle;
        try {
            Object obj = readObject0(false);//重点查看readObject0方法
        .....
    }
        
    private Object readObject0(boolean unshared) throws IOException {
    	...
        try {
    		switch (tc) {
    			...
    			case TC_OBJECT:
    				return checkResolve(readOrdinaryObject(unshared));//重点查看readOrdinaryObject方法
    			...
            }
        } finally {
            depth--;
            bin.setBlockDataMode(oldMode);
        }    
    }
        
    private Object readOrdinaryObject(boolean unshared) throws IOException {
    	...
    	//isInstantiable 返回true,执行 desc.newInstance(),通过反射创建新的单例类,
        obj = desc.isInstantiable() ? desc.newInstance() : null; 
        ...
        // 在Singleton类中添加 readResolve 方法后 desc.hasReadResolveMethod() 方法执行结果为true
        if (obj != null && handles.lookupException(passHandle) == null && desc.hasReadResolveMethod()) {
        	// 通过反射调用 Singleton 类中的 readResolve 方法,将返回值赋值给rep变量
        	// 这样多次调用ObjectInputStream类中的readObject方法,继而就会调用我们定义的readResolve方法,所以返回的是同一个对象。
        	Object rep = desc.invokeReadResolve(obj);
         	...
        }
        return obj;
    }
    
  • 反射方式破解单例的解决方法

    public class Singleton {
    
        //私有构造方法
        private Singleton() {
            /*
               反射破解单例模式需要添加的代码
            */
            if(instance != null) {
                throw new RuntimeException();
            }
        }
        
        private static volatile Singleton instance;
    
        //对外提供静态方法获取该对象
        public static Singleton getInstance() {
    
            if(instance != null) {
                return instance;
            }
    
            synchronized (Singleton.class) {
                if(instance != null) {
                    return instance;
                }
                instance = new Singleton();
                return instance;
            }
        }
    }
    

    说明:

    这种方式比较好理解。当通过反射方式调用构造方法进行创建创建时,直接抛异常。不运行此中操作。

5.5 JDK源码解析-Runtime类

Runtime类就是使用的单例设计模式。

  1. 通过源代码查看使用的是哪儿种单例模式

    public class Runtime {
        private static Runtime currentRuntime = new Runtime();
    
        /**
         * Returns the runtime object associated with the current Java application.
         * Most of the methods of class <code>Runtime</code> are instance
         * methods and must be invoked with respect to the current runtime object.
         *
         * @return  the <code>Runtime</code> object associated with the current
         *          Java application.
         */
        public static Runtime getRuntime() {
            return currentRuntime;
        }
    
        /** Don't let anyone else instantiate this class */
        private Runtime() {}
        ...
    }
    

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

  2. 使用Runtime类中的方法

    public class RuntimeDemo {
        public static void main(String[] args) throws IOException {
            //获取Runtime类对象
            Runtime runtime = Runtime.getRuntime();
    
            //返回 Java 虚拟机中的内存总量。
            System.out.println(runtime.totalMemory());
            //返回 Java 虚拟机试图使用的最大内存量。
            System.out.println(runtime.maxMemory());
    
            //创建一个新的进程执行指定的字符串命令,返回进程对象
            Process process = runtime.exec("ipconfig");
            //获取命令执行后的结果,通过输入流获取
            InputStream inputStream = process.getInputStream();
            byte[] arr = new byte[1024 * 1024* 100];
            int b = inputStream.read(arr);
            System.out.println(new String(arr,0,b,"gbk"));
        }
    }
    

式创建对象非常高效,根本无须知道对象创建的细节。例如,Windows 操作系统的安装通常较耗时,如果复制就快了很多。

原型模式的优点:

  • Java自带的原型模式基于内存二进制流的复制,在性能上比直接 new 一个对象更加优良。
  • 可以使用深克隆方式保存对象的状态,使用原型模式将对象复制一份,并将其状态保存起来,简化了创建对象的过程,以便在需要的时候使用(例如恢复到历史某一状态),可辅助实现撤销操作。

原型模式的缺点:

  • 需要为每一个类都配置一个 clone 方法
  • clone 方法位于类的内部,当对已有类进行改造的时候,需要修改代码,违背了开闭原则。
  • 当实现深克隆时,需要编写较为复杂的代码,而且当对象之间存在多重嵌套引用时,为了实现深克隆,每一层对象对应的类都必须支持深克隆,实现起来会比较麻烦。因此,深克隆、浅克隆需要运用得当。

6.2 原型模式的结构与实现

6.2.1 原形模式的结构

原型模式包含以下主要角色。

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

6.2.2 代码实现

浅克隆:创建一个新对象,新对象的属性和原来对象完全相同,对于非基本类型属性,仍指向原有属性所指向的对象的内存地址。

深克隆:创建一个新对象,属性中引用的其他对象也会被克隆,不再指向原有对象地址。

6.2.2.1 浅拷贝

IdCard

package com.zhuang.prototype.shallowclone;

/**
 * @Classname IdCard
 * @Description 浅拷贝的示例
 * @Date 2021/3/19 12:16
 * @Created by dell
 */

public class IdCard {
    private String id;

    public IdCard(String id) {
        this.id = id;
    }

    @Override
    public String toString() {
        return "IdCard{" +
                "id=" + id +
                '}';
    }
}

Person

package com.zhuang.prototype.shallowclone;

/**
 * @Classname Person
 * @Description 浅拷贝的示例
 * @Date 2021/3/19 12:17
 * @Created by dell
 */

public class Person implements Cloneable {

    private String name;
    private int age;
    private IdCard idCard;

    public Person() {
    }

    public Person(String name, int age, IdCard idCard) {
        this.name = name;
        this.age = age;
        this.idCard = idCard;
    }

    public String getName() {
        return name;
    }

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

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public IdCard getIdCard() {
        return idCard;
    }

    public void setIdCard(IdCard idCard) {
        this.idCard = idCard;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", idCard=" + idCard + ", idCard.hashCode=" + idCard.hashCode() +
                '}';
    }

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

PersonTest

package com.zhuang.prototype.shallowclone;

/**
 * @Classname PersonTest
 * @Description 浅拷贝测试类
 * @Date 2021/3/19 12:17
 * @Created by dell
 */

public class PersonTest {
    public static void main(String[] args) throws Exception {
        Person person = new Person("张三", 20, new IdCard("10086"));

        Person person1 = (Person) person.clone();
        Person person2 = (Person) person.clone();

        System.out.println(person);
        System.out.println(person1);
        System.out.println(person2);

    }
}

我们发现可以通过实现implements Cloneable来完成浅拷贝,基本变量是值传递克隆,而引用对象IdCard则是引用传递,这不符合我们面向对象思想,每一个Person应该都有一个独立的IdCard,而不是共用一个,而要解决这种问题,我们需要使用深克隆

6.2.2.2 深拷贝(第一种)

IdCard

package com.zhuang.prototype.deepclone.one;

/**
 * @Classname IdCard
 * @Description 深克隆的示例
 * @Date 2021/3/19 12:33
 * @Created by dell
 */
//实现Cloneable接口
public class IdCard implements Cloneable {
    private String id;

    public IdCard(String id) {
        this.id = id;
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

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

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

Person

package com.zhuang.prototype.deepclone.one;

/**
 * @Classname Person
 * @Description 深克隆的示例
 * @Date 2021/3/19 12:33
 * @Created by dell
 */

public class Person implements Cloneable {
    private String name;
    private int age;
    private IdCard idCard;

    public Person(String name, int age, IdCard idCard) {
        this.name = name;
        this.age = age;
        this.idCard = idCard;
    }

    public IdCard getIdCard() {
        return idCard;
    }

    public void setIdCard(IdCard idCard) {
        this.idCard = idCard;
    }

    @Override
    public String toString() {
        return "Person{" +
                "personHashCode=" + this.hashCode() +
                ", name='" + name + '\'' +
                ", age=" + age +
                ", idCard=" + idCard +
                ", idCardHashCode=" + idCard.hashCode() +
                '}';
    }

    //深克隆需要自己手动实现,在对象引用中也要实现clone方法
    @Override
    protected Object clone() throws CloneNotSupportedException {
        //完成基本数据类型的拷贝
        //通过new关键字创建的对象是引用类型

        Object person = super.clone();

        //对引用类型单独处理
        Person p = (Person) person;
        IdCard idCard = (IdCard) p.getIdCard().clone(); //实现自己的克隆
        p.setIdCard(idCard);
        return p;
    }
}

PersonTest

package com.zhuang.prototype.deepclone.one;

import java.io.Serializable;

/**
 * @Classname PersonTest
 * @Description 深克隆测试类
 * @Date 2021/3/19 12:33
 * @Created by dell
 */

public class PersonTest implements Serializable {
    public static void main(String[] args) throws Exception {

        Person person = new Person("张三", 20, new IdCard("10086"));

        Person person1 = (Person) person.clone();
        Person person2 = (Person) person.clone();

        System.out.println(person);
        System.out.println(person1);
        System.out.println(person2);

    }
}

使用这种深克隆的方式,完美的解决了当数据类型为引用类型时,只是拷贝原引用对象地址而不是一个全新的引用对象的引用,但是这种实现有一个很大的弊端,需要在每一个对象中都实现clone方法,如果类全是你自己写的,那自然没问题,实现一下就行了,不过有点麻烦。但是,如果你引用的是第三方的一个类,无法修改源代码,这种方式,显然就无法实现深克隆了

6.2.2.2 深拷贝(第二种)

IdCard

package com.zhuang.prototype.deepclone.two;

/**
 * @Classname IdCard
 * @Description 深克隆的示例2
 * @Date 2021/3/19 12:33
 * @Created by dell
 */
//实现Serializable接口
public class IdCard implements Serializable {
    private String id;

    public IdCard(String id) {
        this.id = id;
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

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

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

Person

package com.zhuang.prototype.deepclone.two;

import java.io.*;

/**
 * @Classname Person
 * @Description 深克隆的示例2
 * @Date 2021/3/19 12:33
 * @Created by dell
 */

public class Person implements Serializable {
    private String name;
    private int age;
    private IdCard idCard;

    public Person(String name, int age, IdCard idCard) {
        this.name = name;
        this.age = age;
        this.idCard = idCard;
    }

    public IdCard getIdCard() {
        return idCard;
    }

    public void setIdCard(IdCard idCard) {
        this.idCard = idCard;
    }

    @Override
    public String toString() {
        return "Person{" +
                "personHashCode=" + this.hashCode() +
                ", name='" + name + '\'' +
                ", age=" + age +
                ", idCard=" + idCard +
                ", idCardHashCode=" + idCard.hashCode() +
                '}';
    }

    //序列化的方式
    public Person deelClone() {
        //创建流对象
        ByteArrayOutputStream bos = null;
        ObjectOutputStream oos = null;
        ByteArrayInputStream bis = null;
        ObjectInputStream ois = null;

        try {
            //序列化
            bos = new ByteArrayOutputStream();
            oos = new ObjectOutputStream(bos);
            oos.writeObject(this);

            //反序列化
            bis = new ByteArrayInputStream(bos.toByteArray());
            ois = new ObjectInputStream(bis);
            return (Person) ois.readObject();
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        } finally {
            try {
                ois.close();
                bis.close();
                oos.close();
                bos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

PersonTest

package com.zhuang.prototype.deepclone.two;

/**
 * @Classname PersonTest
 * @Description 深克隆测试类
 * @Date 2021/3/19 12:33
 * @Created by dell
 */

public class PersonTest {
    public static void main(String[] args) throws Exception {
        //创建一个对象
        Person person = new Person("张三", 20, new IdCard("10086"));

        //克隆两个对象
        Person person1 = (Person) person.deelClone();
        Person person2 = (Person) person.deelClone();
		System.out.println("深拷贝(第二种 实现序列化接口)");
        //打印三人信息
        System.out.println(person);
        System.out.println(person1);
        System.out.println(person2);

    }
}

这种方式我们需要手动编写deepClone方法,使用Java流中的序列化与反序列化来实现深克隆,但是这种实现,需要在每一个类中都继承序列化Serializable接口,这种方式,如果你调用的是第三方类,也有可能第三方类上没有实现Serializable序列化接口,但是一般来说,大多都会实现,总的来说,这种比较推荐使用,而且效率也高

6.3 原型模式的应用场景

  • 对象之间相同或相似,即只是个别的几个属性不同的时候。
  • 创建对象成本较大,例如初始化时间长,占用CPU太多,或者占用网络资源太多等,需要优化资源。
  • 创建一个对象需要繁琐的数据准备或访问权限等,需要提高性能或者提高安全性。
  • 系统中大量使用该类对象,且各个调用者都需要给它的属性重新赋值。
  • 在 Spring中,原型模式应用的非常广泛,例如 scope=‘prototype’、JSON.parseObject() 等都是原型模式的具体应用。

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

  • 创建新的对象比较复杂时,可以利用原型模式简化对象的创建过程,同时也能提高效率
  • 不用重新初始化对象,动态地获得对象运行时的状态
  • 如果原始对象发生变化(增加或减少属性),其他克隆对象也会发生相应的变化,无需修改代码

7,工厂模式

7.1 工厂模式的定义和特点

工厂模式的定义:定义一个创建产品对象的工厂接口,将产品对象的实际创建工作推迟到具体子工厂类当中。这满足创建型模式中所要求的“创建与使用相分离”的特点。

按实际业务场景划分,工厂模式有 3 种不同的实现方式,分别是简单工厂模式、工厂方法模式和抽象工厂模式。

我们把被创建的对象称为“产品”,把创建产品的对象称为“工厂”。如果要创建的产品不多,只要一个工厂类就可以完成,这种模式叫“简单工厂模式”。

在简单工厂模式中创建实例的方法通常为静态(static)方法,因此简单工厂模式(Simple Factory Pattern)又叫作静态工厂方法模式(Static Factory Method Pattern)。

简单来说,简单工厂模式有一个具体的工厂类,可以生成多个不同的产品,属于创建型设计模式。简单工厂模式不在 GoF 23 种设计模式之列。

简单工厂模式每增加一个产品就要增加一个具体产品类和一个对应的具体工厂类,这增加了系统的复杂度,违背了“开闭原则”。

优点:

  1. 工厂类包含必要的逻辑判断,可以决定在什么时候创建哪一个产品的实例。客户端可以免除直接创建产品对象的职责,很方便的创建出相应的产品。工厂和产品的职责区分明确。
  2. 客户端无需知道所创建具体产品的类名,只需知道参数即可。
  3. 也可以引入配置文件,在不修改客户端代码的情况下更换和添加新的具体产品类。

缺点:

  1. 简单工厂模式的工厂类单一,负责所有产品的创建,职责过重,一旦异常,整个系统将受影响。且工厂类代码会非常臃肿,违背高聚合原则。
  2. 使用简单工厂模式会增加系统中类的个数(引入新的工厂类),增加系统的复杂度和理解难度
  3. 系统扩展困难,一旦增加新产品不得不修改工厂逻辑,在产品类型较多时,可能造成逻辑过于复杂
  4. 简单工厂模式使用了 static 工厂方法,造成工厂角色无法形成基于继承的等级结构。

7.2 工厂模式的结构与实现

简单工厂模式的主要角色如下:

  • 简单工厂(SimpleFactory):是简单工厂模式的核心,负责实现创建所有实例的内部逻辑。工厂类的创建产品类的方法可以被外界直接调用,创建所需的产品对象。
  • 抽象产品(Product):是简单工厂创建的所有对象的父类,负责描述所有实例共有的公共接口。
  • 具体产品(ConcreteProduct):是简单工厂模式的创建目标。

Shape

package com.zhuang.factory.simplefactory;

/**
 * @Classname Shape
 * @Description  产品接口类
 * @Date 2021/3/18 15:40
 * @Created by dell
 */

public interface Shape {
    void draw();
}

Circle

package com.zhuang.factory.simplefactory;

/**
 * @Classname Circle
 * @Description 产品实现类
 * @Date 2021/3/18 15:43
 * @Created by dell
 */

public class Circle implements Shape {
    @Override
    public void draw() {
        System.out.println("Rectangle-->绘制圆形");
    }
}

Rectangle

package com.zhuang.factory.simplefactory;

/**
 * @Classname Rectangle
 * @Description 产品实现类
 * @Date 2021/3/18 15:40
 * @Created by dell
 */

public class Rectangle implements Shape {
    @Override
    public void draw() {
        System.out.println("Rectangle-->绘制长方形");
    }
}

Square

package com.zhuang.factory.simplefactory;

/**
 * @Classname Square
 * @Description 产品实现类
 * @Date 2021/3/18 15:40
 * @Created by dell
 */

public class Square implements Shape {
    @Override
    public void draw() {
        System.out.println("Rectangle-->绘制方形");
    }
}

ShapeFactory

package com.zhuang.factory.simplefactory;

/**
 * @Classname ShapeFactory
 * @Description 简单工厂类
 * @Date 2021/3/18 15:43
 * @Created by dell
 */

public class ShapeFactory {
    public static Shape createShape(String shapeType) {
        if ("Rectangle".equalsIgnoreCase(shapeType)) {
            return new Rectangle();
        }
        if ("Circle".equalsIgnoreCase(shapeType)) {
            return new Circle();
        }
        if ("Square".equalsIgnoreCase(shapeType)) {
            return new Square();
        }
        return null;
    }
}

ShapeFactoryTest

package com.zhuang.factory.simplefactory;

/**
 * @Classname ShapeFactoryTest
 * @Description 简单工厂测试类
 * @Date 2021/3/18 15:47
 * @Created by dell
 */

public class ShapeFactoryTest {
    public static void main(String[] args) {
        Shape rectangle = ShapeFactory.createShape("Rectangle");
        rectangle.draw();

        Shape circle = ShapeFactory.createShape("Circle");
        circle.draw();

        Shape square = ShapeFactory.createShape("Square");
        square.draw();

    }
}

Java.util.Calendar 源码使用到了简单工厂模式

7.3 抽象工厂模式的定义和特点

抽象工厂模式(Abstract Factory)是围绕一个超级工厂创建其他工厂。该超级工厂又称为其他工厂的工厂。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。在抽象工厂模式中,接口是负责创建一个相关对象的工厂,不需要显式指定它们的类,每个生成的工厂都能按照工厂模式提供对象。

使用抽象工厂模式一般要满足以下条件。

  • 系统中有多个产品族,每个具体工厂创建同一族但属于不同等级结构的产品。
  • 系统一次只可能消费其中某一族产品,即同族的产品一起使用。

主要优点如下:

  • 可以在类的内部对产品族中相关联的多等级产品共同管理,而不必专门引入多个新的类来进行管理。
  • 当需要产品族时,抽象工厂可以保证客户端始终只使用同一个产品的产品组。
  • 抽象工厂增强了程序的可扩展性,当增加一个新的产品族时,不需要修改原代码,满足开闭原则。

其缺点是:

  • 当产品族中需要增加一个新的产品时,所有的工厂类都需要进行修改。增加了系统的抽象性和理解难度。

7.4 抽象工厂模式的结构与实现

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

7.3.2 代码实现

Shape

package com.zhuang.factory.absfactory;

/**
 * @Classname Shape
 * @Description  产品接口类
 * @Date 2021/3/18 15:40
 * @Created by dell
 */

public interface Shape {
    void draw();
}

Circle

package com.zhuang.factory.absfactory;

/**
 * @Classname Circle
 * @Description 产品实现类
 * @Date 2021/3/18 15:43
 * @Created by dell
 */

public class Circle implements Shape {
    @Override
    public void draw() {
        System.out.println("Rectangle-->绘制圆形");
    }
}

Square

package com.zhuang.factory.absfactory;

/**
 * @Classname Square
 * @Description  产品实现类
 * @Date 2021/3/18 15:40
 * @Created by dell
 */

public class Square implements Shape {
    @Override
    public void draw() {
        System.out.println("Rectangle-->绘制方形");
    }
}

Rectangle

package com.zhuang.factory.absfactory;


/**
 * @Classname Rectangle
 * @Description 产品实现类
 * @Date 2021/3/18 15:40
 * @Created by dell
 */

public class Rectangle implements Shape {
    @Override
    public void draw() {
        System.out.println("Rectangle-->绘制长方形");
    }
}

ShapeFactory

package com.zhuang.factory.absfactory;

/**
 * @Classname ShapeFactory
 * @Description 简单工厂类
 * @Date 2021/3/18 15:43
 * @Created by dell
 */

public class ShapeFactory extends AbstractFactory {
    @Override
    public Shape createShape(String shapeType) {
        if ("Rectangle".equalsIgnoreCase(shapeType)) {
            return new Rectangle();
        }
        if ("Circle".equalsIgnoreCase(shapeType)) {
            return new Circle();
        }
        if ("Square".equalsIgnoreCase(shapeType)) {
            return new Square();
        }
        return null;
    }

    @Override
    public Color createColor(String colorType) {
        return null;
    }
}

Color

package com.zhuang.factory.absfactory;

/**
 * @Classname Color
 * @Description  颜色产品接口类
 * @Date 2021/3/18 16:03
 * @Created by dell
 */

public interface Color {
    void fill();
}

Red

package com.zhuang.factory.absfactory;

/**
 * @Classname Red
 * @Description  颜色产品实现类
 * @Date 2021/3/18 16:03
 * @Created by dell
 */

public class Red implements Color {
    @Override
    public void fill() {
        System.out.println("Red-->填充红色");
    }
}

Yellow

package com.zhuang.factory.absfactory;

/**
 * @Classname Yellow
 * @Description 颜色产品实现类
 * @Date 2021/3/18 16:04
 * @Created by dell
 */

public class Yellow implements Color {
    @Override
    public void fill() {
        System.out.println("Red-->填充黄色");
    }
}

Black

package com.zhuang.factory.absfactory;

/**
 * @Classname Black
 * @Description 颜色产品实现类
 * @Date 2021/3/18 16:05
 * @Created by dell
 */

public class Black implements Color {
    @Override
    public void fill() {
        System.out.println("Red-->填充黑色");
    }
}

ColorFactory

package com.zhuang.factory.absfactory;

import com.zhuang.factory.simplefactory.Circle;
import com.zhuang.factory.simplefactory.Rectangle;
import com.zhuang.factory.simplefactory.Square;

/**
 * @Classname ColorFactory
 * @Description 颜色工厂类的编写
 * @Date 2021/3/18 16:05
 * @Created by dell
 */

public class ColorFactory extends AbstractFactory {
    @Override
    public Shape createShape(String shapeType) {
        return null;
    }

    @Override
    public Color createColor(String colorType) {
        if ("Red".equalsIgnoreCase(colorType)) {
            return new Red();
        }
        if ("Black".equalsIgnoreCase(colorType)) {
            return new Black();
        }
        if ("Yellow".equalsIgnoreCase(colorType)) {
            return new Yellow();
        }
        return null;
    }
}

AbstractFactory

package com.zhuang.factory.absfactory;

/**
 * @Classname AbstractFactory
 * @Description 产品家族抽象类
 * @Date 2021/3/18 16:06
 * @Created by dell
 */

public abstract class AbstractFactory {
    public abstract Shape createShape(String shapeType);

    public abstract Color createColor(String colorType);
}

AbstractFactoryProducer

package com.zhuang.factory.absfactory;

/**
 * @Classname AbstractFactoryProducer
 * @Description 抽象类的工厂类
 * @Date 2021/3/18 16:10
 * @Created by dell
 */

public class AbstractFactoryProducer {
    public static AbstractFactory createFactory(String choice) {
        if ("Shape".equalsIgnoreCase(choice)) {
            return new ShapeFactory();
        }
        if ("Color".equalsIgnoreCase(choice)) {
            return new ColorFactory();
        }
        return null;
    }
}

AbstractFactoryProducerTest

package com.zhuang.factory.absfactory;

/**
 * @Classname AbstractFactoryProcucerTest
 * @Description 抽象类的工厂类测试类
 * @Date 2021/3/18 16:15
 * @Created by dell
 */

public class AbstractFactoryProducerTest {
    public static void main(String[] args) {
        AbstractFactory shapeFactory = AbstractFactoryProducer.createFactory("Shape");
        assert shapeFactory != null;
        Shape rectangle = shapeFactory.createShape("Rectangle");
        Shape circle = shapeFactory.createShape("Circle");
        Shape square = shapeFactory.createShape("Square");
        rectangle.draw();
        circle.draw();
        square.draw();
        System.out.println("====================================");
        AbstractFactory colorFactory = AbstractFactoryProducer.createFactory("Color");
        Color red = colorFactory.createColor("Red");
        Color yellow = colorFactory.createColor("Yellow");
        Color black = colorFactory.createColor("Black");
        red.fill();
        yellow.fill();
        black.fill();

    }
}

7.5 抽象工厂模式的应用场景

  1. 当需要创建的对象是一系列相互关联或相互依赖的产品族时,如电器工厂中的电视机、洗衣机、空调等。
  2. 系统中有多个产品族,但每次只使用其中的某一族产品。如有人只喜欢穿某一个品牌的衣服和鞋。
  3. 系统中提供了产品的类库,且所有产品的接口相同,客户端不依赖产品实例的创建细节和内部结构。

8,建造者模式

8.1 建造者模式的定义和特点

建造者(Builder)模式的定义:指将一个复杂对象的构造与它的表示分离,使同样的构建过程可以创建不同的表示,这样的设计模式被称为建造者模式。它是将一个复杂的对象分解为多个简单的对象,然后一步一步构建而成。它将变与不变相分离,即产品的组成部分是不变的,但每一部分是可以灵活选择的。

该模式的主要优点如下:

  1. 封装性好,构建和表示分离。
  2. 扩展性好,各个具体的建造者相互独立,有利于系统的解耦。
  3. 客户端不必知道产品内部组成的细节,建造者可以对创建过程逐步细化,而不对其它模块产生任何影响,便于控制细节风险。

其缺点如下:

  1. 产品的组成部分必须相同,这限制了其使用范围。
  2. 如果产品的内部变化复杂,如果产品内部发生变化,则建造者也要同步修改,后期维护成本较大。

8.2 建造者模式的结构与实现

8.2.1 建造者模式的结构

  1. 产品角色(Product):它是包含多个组成部件的复杂对象,由具体建造者来创建其各个零部件。
  2. 抽象建造者(Builder):它是一个包含创建产品各个子部件的抽象方法的接口,通常还包含一个返回复杂产品的方法 getResult()。
  3. 具体建造者(Concrete Builder):实现 Builder 接口,完成复杂产品的各个部件的具体创建方法。
  4. 指挥者(Director):它调用建造者对象中的部件构造与装配方法完成复杂对象的创建,在指挥者中不涉及具体产品的信息。

8.2.2 代码实现

House 产品角色

package com.zhuang.builder;

/**
 * @Classname House
 * @Description  产品实现类
 * @Date 2021/3/20 11:49
 * @Created by dell
 */

public class House {
    private String ground;
    private String wall;
    private String roofed;

    public House() {
    }

    public House(String ground, String wall, String roofed) {
        this.ground = ground;
        this.wall = wall;
        this.roofed = roofed;
    }

    public String getGround() {
        return ground;
    }

    public void setGround(String ground) {
        this.ground = ground;
    }

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

    @Override
    public String toString() {
        return "House{" +
                "ground='" + ground + '\'' +
                ", wall='" + wall + '\'' +
                ", roofed='" + roofed + '\'' +
                '}';
    }
}

HouseBuilder 抽象建造者

package com.zhuang.builder;

/**
 * @Classname HouseBuilder
 * @Description 抽象建造者
 * @Date 2021/3/20 11:49
 * @Created by dell
 */

public abstract class HouseBuilder {
    //创建产品对象
    protected House house = new House();

    //生产产品流程
    public abstract void buildGround();

    public abstract void buildWall();

    public abstract void buildRoofed();

    //返回产品对象
    public House getHouse() {
        return house;
    }
}

HighHouse 具体建造者

package com.zhuang.builder;

/**
 * @Classname HighHouse
 * @Description 具体建造者
 * @Date 2021/3/20 11:51
 * @Created by dell
 */

public class HighHouse extends HouseBuilder {

    @Override
    public void buildGround() {
        house.setGround("100平");
        System.out.println("高楼:打地基");
    }

    @Override
    public void buildWall() {
        house.setWall("50米");
        System.out.println("高楼:砌墙50米");
    }

    @Override
    public void buildRoofed() {
        house.setRoofed("天窗");
        System.out.println("别墅:盖天窗");
    }
}

VillaHouse 具体建造者

package com.zhuang.builder;

/**
 * @Classname VillaHouse
 * @Description  具体建造者
 * @Date 2021/3/20 11:51
 * @Created by dell
 */

public class VillaHouse extends HouseBuilder {

    @Override
    public void buildGround() {
        house.setGround("200平");
        System.out.println("别墅:打地基");
    }

    @Override
    public void buildWall() {
        house.setWall("10米");
        System.out.println("别墅:砌墙10米");
    }

    @Override
    public void buildRoofed() {
        house.setRoofed("天花板");
        System.out.println("别墅:盖天花板");
    }
}

HouseDirector 指挥者

package com.zhuang.builder;

/**
 * @Classname HouseDirector
 * @Description  工程指挥者
 * @Date 2021/3/20 11:50
 * @Created by dell
 */

public class HouseDirector {
    private HouseBuilder houseBuilder;

    public HouseDirector(HouseBuilder houseBuilder) {
        this.houseBuilder = houseBuilder;
    }

    public House build() {
        houseBuilder.buildGround();
        houseBuilder.buildWall();
        houseBuilder.buildRoofed();
        return houseBuilder.getHouse();
    }
}

Client

package com.zhuang.builder;

/**
 * @Classname Client
 * @Description  产品试用客户端
 * @Date 2021/3/20 11:51
 * @Created by dell
 */

public class Client {
    public static void main(String[] args) {
        House house1 = new HouseDirector(new VillaHouse()).build();
        System.out.println(house1);
        System.out.println("============================================");
        House house2 = new HouseDirector(new HighHouse()).build();
        System.out.println(house2);
    }
}

8.3 建造者模式的应用场景

  • 相同的方法,不同的执行顺序,产生不同的结果。
  • 多个部件或零件,都可以装配到一个对象中,但是产生的结果又不相同。
  • 产品类非常复杂,或者产品类中不同的调用顺序产生不同的作用。
  • 初始化一个对象特别复杂,参数多,而且很多参数都具有默认值。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值