23种设计模式-创建型模式总结

创建型模式总结

一. 单例模式

保证一个类只有一个实例,并且提供一个访问该实例的全局访问点。

单例模式的优点:

单例模式只生成一个实例,减少了系统的开销,当一个对象的产生需要比较多的资源时,如读取配置,产生其他依赖对象时,则可以通过在应用启动时直接产生一个单例对象,然后永久贮存在内存中来解决。
单例模式可以在系统设置全局的访问点,优化环境共享资源访问,例如可以设计一个单例类,负责所有数据的映射处理。
单例模式的应用场景

常见的五种单例模式实现方式

1. 主要

1.1 饿汉式(线程安全,调用率高。但是不能延时加载。)

饿汉式实现

package cn.design.singleton01;
/*测试饿汉式单例模式*/
public class SingletonDemo1 {
    //1.提供一个静态的私有对象
    private static SingletonDemo1 instance = new SingletonDemo1();//类初始化时,立即加载该对象。(无论是否调用该对象,都会加载,没有延时加载的优势)
    //2.构造器私有
    private SingletonDemo1(){}
    //3.提供一个全局的访问点(由于类加载器在加载静态对象时,天然的线程安全,所以该方法没有同步,调用效率高。)
    public static SingletonDemo1 getInstance() {
        return instance;
    }
}
1.1 懒汉式(线程安全,调用率不高。但是可以延时加载。)

懒汉式实现

package cn.design.singleton01;
/*测试懒汉式单例模式*/
public class SingletonDemo2 {
    //1.提供一个静态的私有对象属性,但是没有初始化,真正使用时候再创建
    private static SingletonDemo2 instance ;
    //2.构造器私有
    private SingletonDemo2(){}
    //3.提供一个全局的访问点(方法同步,调用效率低)
    public static synchronized SingletonDemo2 getInstance() {
        if(instance == null){
            instance = new SingletonDemo2();
        }
        return instance;
    }
}

2. 其他

2.1 双重检测锁式(细粒度同步代码块,由于JVM底层内部模型原因,偶尔会出问题,不建议使用)
package cn.design.singleton01;
/*测试双重检测锁单例模式*/
public class SingletonDemo3 {
    //1.提供一个静态的私有对象属性,但是没有初始化,真正使用时候再创建
    private static SingletonDemo3 instance ;
    //2.构造器私有
    private SingletonDemo3(){}
    //3.提供一个全局的访问点(方法同步,调用效率低)
    public static SingletonDemo3 getInstance() {
        if (instance == null){
            SingletonDemo3 sc;
            synchronized(SingletonDemo3.class){
                sc = instance;
                if (sc == null){
                    synchronized (SingletonDemo3.class){
                        if (sc == null) {
                            sc = new SingletonDemo3();
                        }
                    }
                    instance = sc;
                }
            }
        }
        return instance;
    }
}
2.2 静态内部类式(线程安全,调用率高。但是可以延时加载。)

静态内部类的实现方式

package cn.design.singleton01;
/*
测试静态内部类模式
线程安全,调用效率高,并且实现了延时加载!
*/
public class SingletonDemo4 {
    //1.在内部类中提供一个静态的私有对象
    private static class SingletonClassInstnnce{
        private static final SingletonDemo4 instance = new SingletonDemo4();
    }
    //2.构造器私有
    private SingletonDemo4(){}
    //3.提供一个全局的访问点(由于类加载器在加载静态对象时,天然的线程安全,所以该方法没有同步,调用效率高。)
    public static SingletonDemo4 getInstance() {
        return SingletonClassInstnnce.instance;
    }
}
2.3 枚举单例(线程安全,调用率高。但是不能延时加载。可以天然的防止反射和反序列化漏洞)

枚举式实现

package cn.design.singleton01;
/*测试枚举式单例模式*/
public enum SingletonDemo5 {
    //这个枚举元素,本身就是一个单例
    INSTANCE;
    //添加自己需要的操作
    public void SingletonOperation() {

    }
}

3. 使用反射和反序列化破解单例模式

3.1 使用反射破解单例模式
package cn.design.singleton01;
/*测试反射破解单例模式*/
public class SingletonDemo6 {
    //1.提供一个静态的私有对象属性,但是没有初始化,真正使用时候再创建
    private static SingletonDemo6 instance ;
    //2.构造器私有
    private SingletonDemo6(){
        //防止反射破解单例模式
        if(instance != null){
            throw new RuntimeException();
        }
    }
    //3.提供一个全局的访问点(方法同步,调用效率低)
    public static synchronized SingletonDemo6 getInstance() {
        if(instance == null){
            instance = new SingletonDemo6();
        }
        return instance;
    }
}
package cn.design.singleton01;
import java.lang.reflect.Constructor;
/*测试反射破解单例模式*/
public class Client1 {
    public static void main(String[] args) throws Exception {
        SingletonDemo6 s1 = SingletonDemo6.getInstance();
        SingletonDemo6 s2 = SingletonDemo6.getInstance();
        System.out.println(s1);
        System.out.println(s2);
        
        //加载类
        Class<SingletonDemo6> clazz = (Class<SingletonDemo6>) Class.forName("cn.design.singleton01.SingletonDemo6");
        //获取类构造器
        Constructor<SingletonDemo6> c = clazz.getDeclaredConstructor(null);
        //跳过修饰符校验
        c.setAccessible(true);
        //创建对象
        SingletonDemo6 s3 = c.newInstance();
        SingletonDemo6 s4 = c.newInstance();
        System.out.println(s3);
        System.out.println(s4);
    }
}
3.2 使用反序列化破解单例模式
package cn.design.singleton01;

import java.io.ObjectStreamException;
import java.io.Serializable;
/*测试通过反序列化破解单例模式*/
public class SingletonDemo7 implements Serializable {
    //1.提供一个静态的私有对象属性,但是没有初始化,真正使用时候再创建
    private static SingletonDemo7 instance ;
    //2.构造器私有
    private SingletonDemo7(){
        if(instance != null){
            throw new RuntimeException();
        }
    }
    //3.提供一个全局的访问点(方法同步,调用效率低)
    public static synchronized SingletonDemo7 getInstance() {
        if(instance == null){
            instance = new SingletonDemo7();
        }
        return instance;
    }
    //反序列化时,如果定义了readResolve(),则直接返回此方法指定的对象,而不需要再创建新的对象!
    private Object readResolve() throws ObjectStreamException  {
        return instance;
    }
}
package cn.design.singleton01;
import	java.io.ObjectInputStream;
import	java.io.FileInputStream;
import	java.io.ObjectOutputStream;
import java.io.FileOutputStream;
import java.io.Serializable;
/*测试反序列化破解单例模式*/
public class Client2 {
    public static void main(String[] args) throws Exception {
        //创建一个对象
        SingletonDemo7 s1 = SingletonDemo7.getInstance();
        System.out.println(s1);
        
        FileOutputStream fos = new FileOutputStream("/Users/eclipse-workspace/test/a.txt");
        ObjectOutputStream oos = new ObjectOutputStream(fos);
        oos.writeObject(s1);
        oos.close();
        fos.close();

        FileInputStream fis = new FileInputStream("/Users/eclipse-workspace/test/a.txt");
        ObjectInputStream ois = new ObjectInputStream(fis);
        SingletonDemo7 s3 = (SingletonDemo7) ois.readObject();
        System.out.println(s3);
    }
}
3.3 比较多线程下五种单例模式的效率
package cn.design.singleton01;
import	java.util.concurrent.CountDownLatch;
public class Client3 {
    public static void main(String[] args) throws Exception {
        long start = System.currentTimeMillis();
        int threaNum = 10;
        final CountDownLatch latch = new CountDownLatch(threaNum);
        for (int i = 0; i < threaNum; i++) {
            //以匿名内部类的方式创建多线程
            new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int j = 0; j < 100000; j++) {
                        Object o = SingletonDemo1.getInstance();
                        //Object o = SingletonDemo2.getInstance();
                        //Object o = SingletonDemo3.getInstance();
                        //Object o = SingletonDemo4.getInstance();
                        //Object o = SingletonDemo5.INSTANCE;
                    }
                    //计数器减1,直到0
                    latch.countDown();
                }
            }).start();
        }
        //main线程等待其他线程执行完毕
        latch.await();
        long end = System.currentTimeMillis();
        System.out.println("总耗时:"+(end - start));
    }
}

二. 简单工厂

概述

工厂模式注重于整体对象的获取,工厂类实际上封装了new对象的过程,而建造者模式旨在复杂对象的详细装配,根据对象的蓝图(我这里理解为对象的实体)在创建者(builder)类中可以调用工厂模式中的工厂类来获取复杂对象的组件对象,从而实现工厂模式和创建者模式的混合使用。

1.1 类图

首先我们按照工厂方法创造玩具,我们来看类图。类图中我们按照玩具的种类分为两种类型:猫形玩具和狗形玩具。这个例子我认为可以更好的理解简单工厂模式和工厂方法模式。首先定义一个产品的接口IToys,然后再定义两个实现类CatImpl和DogImpl,调用者通过玩具工厂Factory制造玩具。(类图为了直观清晰,隐藏了所有对接口实现类的直接依赖关系。)
simpleFactory

1.2 部分代码实现
package cn.design.summary.simplefactory;
/*玩具工厂类*/
public class Factory {
    //工厂类实现方法一
    public static IToys createCat(){
        return new CatImpl();
    }
    public static IToys createDog(){
        return new DogImpl();
    }

    //    //工厂类实现方法二
    public static IToys createToys(String type) {
        switch(type){
            case "cat":
                return new CatImpl();
            case "dog":
                return new DogImpl();
        }
        return null;
    }
}
package cn.design.summary.simplefactory;
/*调用者的两种调用方式*/
public class Client {
    public static void main(String[] args) {
        //使用玩具工厂创建cat对象
        IToys cat1 = Factory.createCat();
        IToys cat2 = Factory.createToys("cat");
        cat2.call();
        //使用玩具工厂创建dog对象
        IToys dog1 = Factory.createDog();
        IToys dog2 = Factory.createToys("dog");
        dog2.call();
    }
}

三. 工厂方法

1.1 类图

factoryMethod

1.2 部分代码实现
package cn.design.summary.factorymethod;
/*调用者*/
public class Client {
    public static void main(String[] args) {
        //创建猫工厂的对象
        IFactory catFactory = new CatFactoryImpl();
        //使用猫工厂对象创建猫对象
        IToys cat = catFactory.createToy();
        cat.call();

        //创建狗工厂对象
        IFactory dogFactory = new DogFactoryImpl();
        //使用狗工厂对象创建狗对象
        IToys dog = dogFactory.createToy();
        dog.call();
    }
}

四. 抽象工厂模式

概述

在工厂方法模式中,只有一个抽象产品类,不同的工厂实现类只能对应制造一种产品实现类的对象,而在抽象工厂中有多个抽象产品类,那么,一个工厂就可以制造多种不属于同一抽象产品类的对象。假设现在有A和B两种产品,形状和颜色是A和B共同的属性,而形状和颜色又能分别抽象为两种抽象产品,分别有红色,黄色,和圆形,方形的实现类,按照抽象工厂的思想,则有如下类图。

1.1 类图

abstractFactory
这样,在A工厂实现类AFactoryImpl和B工厂实现类BFactoryImpl中就都可以创建形状和颜色这两个产品族的对象,需要注意的是,工厂只负责new对象,没有对创建的对象进行装配操作,用户此时拿到的只是多个组件产品对象,这也是和建造者模式的区别。
注意:这里的A和B是形状和颜色这两个产品的更高一级产品,也就是说在抽象工厂模式中,工厂类实际上创造的产品对象要比类图中形状和颜色的抽象产品类更高一级,而在工厂方法模式中,工厂类创造的产品对象,只能是类图中抽象产品其中的一种。这样就便于理解了,网上很多例子没有明确指出这一点,导致对抽象工厂模式和工厂方法模式难以区分。

五. 建造者模式

概述

建造者模式其实是工厂模式获取产品对象以后的下一步工作优化,将不同的产品对象按照某一蓝图(复杂产品的实体类)进行装配的过程。而Client最终拿到的是装配好的一个复杂对象。

1.1 类图

builder

1.2 代码实现
package cn.design.summary.builder;
/*复杂产品的实体类(设计蓝图)*/
public class Product {
    private String partA; //可以是任意类型

    private String partB;

    private String partC;

    public String getPartA() {
        return partA;
    }

    public void setPartA(String partA) {
        this.partA = partA;
    }

    public String getPartB() {
        return partB;
    }

    public void setPartB(String partB) {
        this.partB = partB;
    }

    public String getPartC() {
        return partC;
    }

    public void setPartC(String partC) {
        this.partC = partC;
    }

    @Override
    public String toString() {
        return "Product{" +
                "partA='" + partA + '\'' +
                ", partB='" + partB + '\'' +
                ", partC='" + partC + '\'' +
                '}';
    }
}


package cn.design.summary.builder;
/*抽象建造者*/
public abstract class AbstractBuilder {
    protected Product product = new Product();//new一个蓝图对象
	//定义三个抽象方法,为蓝图设置属性
    public abstract void buildPartA();
    public abstract void buildPartB();
    public abstract void buildPartC();
   //返回装配完整的蓝图对象
    public Product getResult(){
        return product;
    }
}


package cn.design.summary.builder;
/*真正的建造者*/
public class ConcreteBuilder extends AbstractBuilder {
    @Override
    public void buildPartA() {
        product.setPartA("A产品");//此处的A产品,B产品,C产品完全可以由工厂模式获得
    }

    @Override
    public void buildPartB() {
        product.setPartB("B产品");
    }

    @Override
    public void buildPartC() {
        product.setPartC("C产品");
    }
}



package cn.design.summary.builder;
/*指导者*/
public class Director {
    private AbstractBuilder builder;

    //1 构造方法的方式注入builder对象
    public Director(AbstractBuilder builder){
        this.builder = builder;
    }

    //2 set方法注入builder对象
    public void setBuilder(AbstractBuilder builder){
        this.builder = builder;
    }

    public Product construct(){
        builder.buildPartA();
        builder.buildPartB();
        builder.buildPartC();
        return builder.getResult();
    }
}


package cn.design.summary.builder;
/*调用者*/
public class Client {
    public static void main(String[] args) {
        AbstractBuilder builder = new ConcreteBuilder();
        Director director = new Director(builder);
        Product product = director.construct();
        System.out.println(product);
    }
}

六. 原型模式

在介绍原型模式之前先介绍一下Java中的拷贝

1. 拷贝

Java中的拷贝分为引用的拷贝和对象的拷贝

1.1 引用的拷贝

创建一个指向对象的引用变量的拷贝。

Teacher teacher = new Teacher("Taylor",26);
Teacher otherteacher = teacher;
System.out.println(teacher);
System.out.println(otherteacher);

输出结果:

blog.Teacher@355da254
blog.Teacher@355da254

结果分析:由输出结果可以看出,它们的地址值是相同的,那么它们肯定是同一个对象。teacher和otherteacher只是引用而已,他们都指向了一个相同的对象Teacher(“Taylor”,26)。 这就叫做引用拷贝。
引用拷贝

1.2 对象的拷贝

创建对象本身的一个副本。

Teacher teacher = new Teacher("Swift",26); 
Teacher otherteacher = (Teacher)teacher.clone(); 
System.out.println(teacher);
System.out.println(otherteacher);

输出结果:

blog.Teacher@355da254
blog.Teacher@4dc63996

结果分析:由输出结果可以看出,它们的地址是不同的,也就是说创建了新的对象, 而不是把原对象的地址赋给了一个新的引用变量,这就叫做对象拷贝。
对象的拷贝
注:深拷贝和浅拷贝都是针对对象拷贝来说的
以上拷贝知识参考了这篇博文https://blog.csdn.net/riemann_/article/details/87217229

原型模式概述

如果通过new产生一个对象需要非常繁琐的数据准备或访问权限,则可以使用原型模式。其实也就是java中的克隆技术,以某个对象为原型,复制出新的对象。显然,新的对象具备原型对象的特点。优势:效率高(直接克隆,避免了重新执行构造过程步骤)。
克隆类似于new,但是不同于new。并且克隆出的新对象改变不会影响原型对象,然后,再修改克隆对象的值。实际上由于Java提供了一个Cloneable的克隆标识接口,原型模式在Java中的使用就是实现深克隆和浅克隆的过程。

1.3 类图

prototype

1.4 浅拷贝详解

只负责克隆原型对象本身,被拷贝对象的所有变量都含有与原对象相同的值,而且对其他对象的引用仍然是指向原来的对象。即浅拷贝只负责当前对象实例,对引用的对象不做拷贝。
代码案例:有一所学校原型school1,它有两个属性,String类型的校名,和ClassRoom类型的教室,ClassRoom类只有一个属性是教室名。
代码如下:

package cn.design.prototype06;
public class School implements Cloneable{
    private String name;
    private ClassRoom classRoom;
    public School(String name, ClassRoom classRoom) {
        this.name = name;
        this.classRoom = classRoom;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public ClassRoom getClassRoom() {
        return classRoom;
    }
    public void setClassRoom(ClassRoom classRoom) {
        this.classRoom = classRoom;
    }
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}


package cn.design.prototype06;
public class ClassRoom {
    private String name;
    public ClassRoom(String name) {
        this.name = name;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
}


package cn.design.prototype06;
public class ShallowCloneTest {
    public static void main(String[] args) throws CloneNotSupportedException {
        ClassRoom room = new ClassRoom("教室1");
        School school1 = new School("清华", room);
        System.out.println("克隆原型school1");
        System.out.println("school1的地址:"+school1);
        System.out.println("school1的名字:"+school1.getName());
        System.out.println("school1的教室地址"+school1.getClassRoom().getName());
        System.out.println("school1的教室:"+school1.getClassRoom().getName());

        System.out.println("-----------------------------------");
        //克隆
        School school2 = (School) school1.clone();
        System.out.println("克隆的学校school2");
        System.out.println("school2的地址:"+school2);
        System.out.println("school2的名字:"+school2.getName());
        System.out.println("school2的教室地址"+room);
        System.out.println("school2的教室:"+school2.getClassRoom().getName());
    }
}

输出结果:

克隆原型school1
school1的地址:cn.design.prototype06.School@61bbe9ba
school1的名字:清华
school1的教室地址:cn.design.prototype06.ClassRoom@610455d6
school1的教室:教室1
-----------------------------------
克隆的学校school2
school2的地址:cn.design.prototype06.School@511d50c0
school2的名字:清华
school2的教室地址cn.design.prototype06.ClassRoom@610455d6
school2的教室:教室1

结果分析:可以看到浅拷贝在克隆原型school1的基础上得到了一个新的对象school2(之所以说是新的对象,是因为school2得到了一个不同于school1的堆内存地址),两个对象的属性值是一样的,而且修改school2中的属性是不会对school1产生任何影响的。但是,两个对象的ClassRoom属性对象仍然指向同一块内存地址,这就意味着如果改变对象room的名字,school1和school2的ClassRoom属性名都会改变。

1.5 深拷贝详解

不仅对原型对象的地址进行克隆,还克隆其引用对象的地址。
被拷贝对象的所有的变量都含有与原来对象相同的值,除了引用其他对象的变量。引用其他对象的变量将指向一个被拷贝的新对象,而不再是原有被引用对象。即深拷贝把要拷贝的对象所引用的对象也都拷贝了一次。
使用Object类的clone()进行深克隆,代码如下:

package cn.design.clone.deep;
public class School implements Cloneable{
    private String name;
    private ClassRoom classRoom;
    public School(String name, ClassRoom classRoom) {
        this.name = name;
        this.classRoom = classRoom;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public ClassRoom getClassRoom() {
        return classRoom;
    }
    public void setClassRoom(ClassRoom classRoom) {
        this.classRoom = classRoom;
    }
    @Override
    protected Object clone() throws CloneNotSupportedException {
        School school = (School) super.clone();
        school.classRoom = (ClassRoom) classRoom.clone();
        return school;
    }
}


package cn.design.clone.deep;
public class ClassRoom implements Cloneable {
    private String name;
    public ClassRoom(String name) {
        this.name = name;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}


package cn.design.clone.deep;
public class ShallowCloneTest {
    public static void main(String[] args) throws CloneNotSupportedException {
        ClassRoom room = new ClassRoom("教室1");
        School school1 = new School("清华", room);
        System.out.println("克隆原型school1");
        System.out.println("school1的地址:"+school1);
        System.out.println("school1的名字:"+school1.getName());
        System.out.println("school1的教室地址:"+school1.getClassRoom());
        System.out.println("school1的教室:"+school1.getClassRoom().getName());

        System.out.println("-----------------------------------");
        //克隆
        School school2 = (School) school1.clone();
        System.out.println("克隆的学校school2");
        System.out.println("school2的地址:"+school2);
        System.out.println("school2的名字:"+school2.getName());
        System.out.println("school2的教室地址"+school2.getClassRoom());
        System.out.println("school2的教室:"+school2.getClassRoom().getName());
    }
}

输出结果:

克隆原型school1
school1的地址:cn.design.clone.deep.School@61bbe9ba
school1的名字:清华
school1的教室地址:cn.design.clone.deep.ClassRoom@610455d6
school1的教室:教室1
-----------------------------------
克隆的学校school2
school2的地址:cn.design.clone.deep.School@511d50c0
school2的名字:清华
school2的教室地址cn.design.clone.deep.ClassRoom@60e53b93
school2的教室:教室1

结果分析:从结果看出深克隆不仅对原型本身的地址进行了克隆,也对其引用的对象地址进行了克隆。
综上,深克隆和浅克隆的本质区别就是是否对原型引用的对象地址进行了克隆,没有对原型引用的对象地址进行克隆就是浅克隆,反之就是深克隆。
在单例模式的学习中,我们发现在对一个对象序列化以后,再反序列化会得到一个新的对象,这里补充一种使用序列化和反序列化的方式深克隆对象的方法:

package cn.design.clone.serializeclone;

import java.io.Serializable;

public class School implements Serializable {
    
    private String name;
    
    private ClassRoom classRoom;

    public School(String name, ClassRoom classRoom) {
        this.name = name;
        this.classRoom = classRoom;
    }

    public String getName() {
        return name;
    }

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

    public ClassRoom getClassRoom() {
        return classRoom;
    }

    public void setClassRoom(ClassRoom classRoom) {
        this.classRoom = classRoom;
    }
}


package cn.design.clone.serializeclone;

import java.io.Serializable;

public class ClassRoom implements Serializable {

    private String name;

    public ClassRoom(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

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


package cn.design.clone.serializeclone;

import java.io.*;
/*序列化反序列化实现对象的深克隆*/
public class SerializeCloneTest {
    public static void main(String[] args) throws Exception {
        ClassRoom room = new ClassRoom("教室1");
        School school1 = new School("清华", room);
        System.out.println("school1的地址" + school1);
        System.out.println("school1的教室地址" + school1.getClassRoom());
        //序列化对象
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(bos);
        oos.writeObject(school1);
        byte[] bytes = bos.toByteArray();

        ByteArrayInputStream bis = new ByteArrayInputStream(bytes);
        ObjectInputStream ois = new ObjectInputStream(bis);
        School school2 = (School) ois.readObject();
        System.out.println("school2的地址" + school2);
        System.out.println("school2的教室地址" + school2.getClassRoom());
    }
}

输出结果:

school1的地址cn.design.clone.serializeclone.School@61bbe9ba
school1的教室地址cn.design.clone.serializeclone.ClassRoom@610455d6
school2的地址cn.design.clone.serializeclone.School@34c45dca
school2的教室地址cn.design.clone.serializeclone.ClassRoom@52cc8049
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值