设计模式总结(一)

  本文根据狂神说和黑马课程总结,大家可以去看原版视频讲解。
  设计模式就是解决特定问题的一系列套路,不是语法规定,而是一套用来提高代码可复用性、可维护性、可读性以及安全性的解决方案。

创建型模式

  描述如何创建对象

  • 单例模式、工厂模式、抽象工厂模式、建造者模式、原型模式
结构型模式

  描述将对象或类按某种布局组成更大的结构

  • 适配器模式、桥接模式、装饰模式、组合模式、外观模式、享元模式、代理模式
行为型模式

  描述类或者对象之间如何相互协作,完成单个对象无法完成的内容

  • 模板方法模式、命令模式、迭代器模式、观察者模式、中介者模式、备忘录模式、解释器模式、状态模式、策略模式、职责链模式、访问者模式
OOP七大原则
  • 开闭原则:对扩展开放,对修改关闭
  • 里氏替换原则:继续必须确保父类所拥有的性质在子类中仍然成立(继承父类时尽量不要改变父类原有的功能)
  • 依赖倒置原则:要面向接口编程,不要面向实现编程。
  • 单一职责原则:控制类的粒度大小、将对象解耦、提高其内聚性。一个方法尽可能只做一件事
  • 接口隔离原则:要为各个类建立它们需要的专用接口
  • 迪米特法则:与你的直接朋友交谈,不跟陌生人说话,A和B沟通,B和C沟通,但不能A和C沟通
  • 合成复用原则:尽量先使用组合或者聚合等关联关系来实现(组合已有对象生成新对象的功能),其次才考虑使用继承关系来实现。
单例模式

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

饿汉式

  类加载就会导致该实例对象被创建

  • 静态变量方式实现
// 饿汉式:静态成员的方式
public class Singleton {
    // 私有构造方法
    private Singleton(){

    }
    // 在本类中创建该类对象
    private static Singleton instance = new Singleton();

    // 提供一个公共的访问方式,让外界获取该对象
    public static Singleton getInstance(){
        return instance;
    }
}
// 客户测试类:测试是否为同一对象
public class Client {
    public static void main(String[] args) {
        // 创建Singleton类的对象
        Singleton instance1 = Singleton.getInstance();
        Singleton instance2 = Singleton.getInstance();
        System.out.println(instance1 == instance2);
    }
}
  • 静态代码块方式实现
public class Singleton {
    // 私有构造方法
    private Singleton(){

    }
    // 声明Singleton类的变量
    private static Singleton instance; // 此时为null

    // 在静态代码块中进行赋值
    static {
        instance = new Singleton();
    }

    // 对外获取该类对象的方法
    public static Singleton getInstance(){
        return instance;
    }
}
懒汉式

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

  • 方式1(线程不安全)
public class Singleton {
    private Singleton(){

    }
    // 声明Singleton类型的变量
    private static Singleton instance;

    // 对外提供访问方式
    public static Singleton getInstance(){
        // 判断instance是否为null,如果为null,则还未创建对象
        // 如果创建,直接返回instance
        if(instance == null){
            instance = new Singleton();
        }
        return instance;
    }
}
public class Client{
    public static void main(String[] args){
        Singleton instance1 = new Singleton();
        Singleton instance2 = new Singleton();
        System.out.println(instance1 == instance2); 
        // 因为加入了判断null,所以此时输出为true,只有一个对象
    }
}
  • 方式2(线程安全)
public class Singleton {
    private Singleton(){

    }
    // 声明Singleton类型的变量
    private static Singleton instance;

    // 对外提供访问方式
    public static synchronized Singleton getInstance(){
        // 判断instance是否为null,如果为null,则还未创建对象
        // 如果创建,直接返回instance
        if(instance == null){
            instance = new Singleton(); // 可认为是写操作
        }
        return instance;
    }
}
// 假设此时是多线程的情况,线程1获得执行权,进入判断中,此时就算线程2拿到执行权
// 也只会等待线程1执行完
// synchronized修饰静态方法,是类锁:所以无论多少对象,只有1把锁,并且这还是单例模式,只有一个对象
  • 方法3(线程安全)
// 由于getInstance()方法,绝大部分都是读操作,读操作是线程安全的,所以没必要让每个线程必须持有锁才能调用该方法,需要调整加锁的时机,由此产生:双重检查锁模式
// 而且此时需要将静态变量加上关键字volatile
// 原因是:
/*
在多线程的情况下,可能会出现空指针问题,问题问题的原因是JVM在实例化对象的时候进行优化和指令重排序操作,所以最好使用volatile关键字,该关键字可以保证可见性和有序性
因为new Singleton()不是原子指针,可以分为三步:(1)分配对象内存;(2)调用构造器方法,执行初始化;(3)将对象引用赋值给变量
JVM实际运行的时候,其中,2和3可能会发生重排序,因为2和3依托1,但是2和3关联不大
假设线程1获得锁进入创建对象实例中,这个时候发生了指令重排序(1,3,2),当线程1执行到第三步时,线程2刚好进入,由于此时对象已经不为null,所以线程2可以自由访问该对象,然而该对象还未初始化,所以线程2访问时将会发生异常。
*/
public class Singleton {
    private Singleton(){

    }
    private static Singleton instance;

    public static Singleton getInstance(){
        // 第一次判断,如果不为null,不需要抢占锁,直接返回对象
        if(instance == null){
            synchronized (Singleton.class){
                // 第二次判断
                if(instance == null){
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}
恶汉式-枚举方式

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

public enum Singleton {
    INSTANCE;
}
public class Client {
    public static void main(String[] args) {
        Singleton instance1 = Singleton.INSTANCE;
        Singleton instance2 = Singleton.INSTANCE;
        System.out.println(instance1 == instance2); // true
    }
}
单例模式存在的问题
  • 破坏单例模式

    • 序列化和反序列化
    public class Singleton implements Serializable {
        private Singleton(){
    
        }
        private static Singleton instance;
    
        public static Singleton getInstance(){
            // 第一次判断,如果不为null,不需要抢占锁,直接返回对象
            if(instance == null){
                instance = new Singleton();
            }
            return instance;
        }
    }
    public class Client {
        public static void main(String[] args) throws Exception{
            writeObjectFIle();
            // 两次读到的不一样
            readObjectFromFile();
            readObjectFromFile();
        }
        public static void readObjectFromFile() throws Exception{
            ObjectInputStream ois = new ObjectInputStream(new FileInputStream("D:\\a1.txt"));
            Singleton instance = (Singleton) ois.readObject();
            System.out.println(instance);
            ois.close();
        }
        public static void writeObjectFIle() throws Exception{
            Singleton instance = Singleton.getInstance();
            ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("D:\\a1.txt"));
            oos.writeObject((instance));
            oos.close();
        }
    }
    
    • 反射:因为反射可以得到类的私有构造方法,获得后即可创建对象
  • 解决方案

    • 序列化和反序列化:在Singleton类中添加readResolve()方法,在反序列化时被反射调用,如果定义了这个方法,就返回这个方法的值,如果没有定义,则返回新new出来的对象

      public class Singleton implements Serializable {
          private Singleton(){
      
          }
          private static Singleton instance;
      
          public static Singleton getInstance(){
              // 第一次判断,如果不为null,不需要抢占锁,直接返回对象
              if(instance == null){
                  instance = new Singleton();
              }
              return instance;
          }
          // 当进行反序列化时,会自动调用该方法,将该方法的返回值直接返回
          public Object readResolve(){
              return instance;
          }
      }
      // 此时修正后,读取出来的是没有问题的,两次读取的内容均相同
      
    • 反射破坏单例的解决方法:需要在Singleton类的私有构造方法中添加一个判断条件

    public class Singleton{
        private Singleton(){
            if(instance != null){
                throw new RuntimeException();
            }
        }
        private static Singleton instance;
    
        public static Singleton getInstance(){
            // 第一次判断,如果不为null,不需要抢占锁,直接返回对象
            if(instance == null){
                instance = new Singleton();
            }
            return instance;
        }
        // 当进行反序列化时,会自动调用该方法,将该方法的返回值直接返回
        public Object readResolve(){
            return instance;
        }
    }
    
  • Runtime使用的就是单例模式

工厂模式

  工厂模式是三种工厂模式的总称:(1)简单工厂模式;(2)工厂方法模式;(3)抽象工厂模式。该模式的作用是实现创建者和调用者的分离。核心本质:(1)实例化对象不使用new,用工厂方法代替;(2)将选择实现类,创建对象统一管理和控制。从而将调用者跟实现类解耦。

  • 简单工厂模式:用户不需要自己去创建对象,由工厂创建对象,并且由工厂执行创建对象的一系列复杂操作,用户不用关心复杂的创建过程,用户想要什么对象,只需要通过工厂创建就好了。
public interface Car{
    void name();
}
public class wuLing implements Car{
    @Override
    void name(){
        System.out.println("wuLing car");
    }
}
public class Tesla implements Car{
    @Override
    void name(){
        System.out.println("Tesla car");
    }
}
public class CarFactory{
    public static Car getCar(String car){
        if(car.equals("wuLing")){
            return new wuLing();
        }else if(car.equals("Tesla")){
            return new Tesla();
        }else{
            return null;
        }
    }
}
public class Consumer{
    public static void main(String[] args){
        /*
        之前创建方式,相当于自己创建了一辆车。这样的话需要了解接口和实现类
        Car car1 = new wuLing();
        Car car2 = new Tesla();
        */
        
        // 简单工厂模式:从工厂里面买车,只需要传入参数
        Car car1 = CarFactory.getCar("wuLing");
    }
}
// 以上模式有一个缺点,如果工厂需要增加车型,需要对CarFactory进行修改,这样不符合开闭原则
// 由此出现了工厂方法模式
  • 工厂方法模式:为了满足不同用户的需求,工厂无法创建所有的宝马车系,于是又单独分出很多个具体的工厂,每个工厂都创建出一个用户需求的对象。用户需要指定哪个具体的工厂才能生产需要的车。
// 在完全满足开闭原则的情况下,实现工厂方法模式(实践中可能不会完全按照七大原则进行)
public interface CarFactory{
    Car getCar();
}
public class TeslaFactory implements CarFactory{
    @Override
    Car getCar(){
        return new Tesla();
    }
}
public class WuLingFactory implements CarFactory{
    @Override
    Car getCar(){
        return new Wuling();
    }
}
public class Consumer{
    public static void main(String[] args){
        Car car1 = new WuLingFactory().getCar();
        Car car2 = new TeslaFactory().getCar();
    }
}
// 此时如何用户需要哈罗单车的时候,不需要修改原有代码,只需要增加一个Bike类实现CarFactory就可以了
// 但是每增加一个,就需要增加一个工厂类
抽象工厂模式

  抽象工厂模式提供了一个创建一系列相关或者相互依赖对象的接口,无需指定它们具体的类

// 围绕一个超级工厂创建其他工厂
// 手机产品接口
public interface IphoneProdct{
    void start();
    void shutdown();
    void callup();
    void sendMS();
}
// 路由器接口
public interface IRouterProduct{
    void start();
    void shutdown();
    void openwifi();
    void setting();
}
// 小米手机
public class XiaomiPhone implements IphoneProduct{
    @Override
    public void start(){
        System.out.println("开启小米手机");
    }
    @Override
    public void shutdown(){
        System.out.println("关闭小米手机");
    }
    @Override
    public void callup(){
        System.out.println("小米打电话")}
    @Override
    public void sendMS(){
        System.out.println("小米发短信");
    }
}
// 华为手机
public class HuaweiPhone implements IphoneProduct{
    @Override
    public void start(){
        System.out.println("开启华为手机");
    }
    @Override
    public void shutdown(){
        System.out.println("关闭华为手机");
    }
    @Override
    public void callup(){
        System.out.println("华为打电话")}
    @Override
    public void sendMS(){
        System.out.println("华为发短信");
    }
}
// 小米手机
public class XiaomiPhone implements IphoneProduct{
    @Override
    public void start(){
        System.out.println("开启小米手机");
    }
    @Override
    public void shutdown(){
        System.out.println("关闭小米手机");
    }
    @Override
    public void callup(){
        System.out.println("小米打电话")}
    @Override
    public void sendMS(){
        System.out.println("小米发短信");
    }
}
// 华为手机
public class HuaweiPhone implements IphoneProduct{
    @Override
    public void start(){
        System.out.println("开启华为手机");
    }
    @Override
    public void shutdown(){
        System.out.println("关闭华为手机");
    }
    @Override
    public void callup(){
        System.out.println("华为打电话")}
    @Override
    public void sendMS(){
        System.out.println("华为发短信");
    }
}
// 华为手机
public class HuaweiPhone implements IphoneProduct{
    @Override
    public void start(){
        System.out.println("开启华为手机");
    }
    @Override
    public void shutdown(){
        System.out.println("关闭华为手机");
    }
    @Override
    public void callup(){
        System.out.println("华为打电话")}
    @Override
    public void sendMS(){
        System.out.println("华为发短信");
    }
}
// 华为手机
public class HuaweiPhone implements IphoneProduct{
    @Override
    public void start(){
        System.out.println("开启华为手机");
    }
    @Override
    public void shutdown(){
        System.out.println("关闭华为手机");
    }
    @Override
    public void callup(){
        System.out.println("华为打电话")}
    @Override
    public void sendMS(){
        System.out.println("华为发短信");
    }
}
// 华为路由器
public class HuaweiRouter implements IRouterProduct{
    @Override
    public void start(){
        System.out.println("开启华为路由器");
    }
    @Override
    public void shutdown(){
        System.out.println("关闭华为路由器");
    }
    @Override
    public void openwifi(){
        System.out.println("打开华为wifi")}
    @Override
    public void setting(){
        System.out.println("华为路由器设置");
    }
}
// 小米路由器跟上面代码类似,故省略

开始创建工厂,该工厂是抽象的抽象。

// 抽象产品工厂
public interface IProductFactory{
    // 生产手机
    IphoneProduct iphoneProduct();
    // 生产路由器
    IRouterProduct routerProduct();
}
// 小米工厂
public class XiaomiFactory implements IProductFactory{
    @Override
    public IphoneProduct iphoneProduct(){
        return new XiaomiPhone();
    }
    @Override
    public IRouterProduct routerProduct(){
        return new XiaomiRouter();
    }
}
// 华为工厂
public class HuaweiFactory implements IProductFactory{
    @Override
    public IphoneProduct iphoneProduct(){
        return new HuaweiPhone();
    }
    @Override
    public IRouterProduct routerProduct(){
        return new HuaweiRouter();
    }
}
// 客户端
public class Client{
    public static void main(String[] args){
        // 小米工厂
        XiaomiFactory xiaomiFactory = new XiaomiFactory();
        IphoneProduct iphoneProduct = xiaomiFactory.iphoneProduct();
        
        IRouterProduct iRouterProduct = xiaomiFactory.routerProduct();
    }
}
// 抽象工厂模式有纵向和横向,创建的超级工厂是抽象工厂的抽象工厂
// 该抽象工厂定义的是不同类型的产品的抽象工厂(可能有不同厂家)
// 比如上面手机和手机是一种类型的产品,但是由两个工厂实现的
// 上面方法存在缺点,比如小米工厂中新增加一个产品,需要先在超级工厂增加对应方法,后面实现的类都要进行修改,重写该方法。但是可以增加产品族,就是增加产品的品牌
代理模式

  代理模式是SpringAOP的 底层。在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用
在这里插入图片描述

优点

  • 代理模式可以隐藏真实对象的实现细节,使得客户端无需知晓真实对象的工作方式和结构
  • 通过代理类来间接访问真实类,可以在不修改真实类的情况下,对其进行扩展、优化或者添加安全措施
  • 代理模式实现起来简单,易于扩展和维护,符合面向对象设计原则中的开闭原则
静态代理

  代码编写期进行代理类和被代理类关联的代理方式,具体实现是创建一个代理类,通常需要实现与被代理类相同接口或继承被代理类。

角色分析:

  • 抽象角色:一般会使用接口或抽象类来解决(租房)
  • 真实角色:被代理的角色(房东)
  • 代理角色:代理真实角色(中介),代理真实角色后,一般会做一些附属操作。
  • 客户:访问代理对象的人(租房的人)
// 租房
public interface Rent{
    public void rent();
}
// 房东
public class Host implements Rent{
    @Override
    public void rent(){
        System.out.println("房东要出租房子");
    }
}
// 客户
public class Client{
    public static void main(String[] args){
        // 未使用代理模式的租房
        Host host = new Host();
        host.rent();
        // 使用代理模式后:通过代理租房,但是中介会做一些附属操作
        Proxy proxy = new Proxy(host);
        
        proxy.rent();
    }
}
// 中介
public class Proxy implements Rent{
    private Host host;
    public Proxy(){
        
    }
    public Proxy(Host host){
        this.host = host;
    }
    
    public void rent(){
        seeHouse();
        host.rent();
        hetong();
        fare();
    }
    
    // 看房(中介可以做一些附属操作)
    public void seeHouse(){
        System.out.println("中介带看房");
    }
    
    // 签合同
    public void hetong(){
        System.out.println("签租赁合同");
    }
    
    // 收中介费
    public void fare(){
        System.out.println("收中介费");
    }
}

优点

  • 公共业务交给代理角色,实现了业务的分工
  • 公共业务发生扩展的时候,方便集中管理
  • 可以使业务逻辑更加清晰,真正的业务逻辑中不用去关注一些公共的业务

缺点

  • 一个真实角色就会产生一个代理角色:代码量会翻倍——开发效率会变低。因为真实角色需要实现抽象类。
动态代理
  • 动态代理和静态代理角色一样
  • 动态代理的代理类是动态生成的,不是直接写好的
  • 动态代理分为两大类:基于接口的动态代理,基于类的动态代理
    • 基于接口——JDK动态代理
    • 基于类——cglib
    • Java字节码实现:javassist
JDK动态代理
  • 它是在运行运行时动态生成代理类,也就是说我们在编写代码时并不知道具体代理的是什么类,而是在程序运行时动态生成。
  • 对象必须实现一个或多个接口

InvocationHandler:由代理实例的调用处理程序实现的接口

Proxy:提供创建动态代理类和实例的静态方法,它也是由这些方法创建的所有动态代理类的超类

// 等我们会用这个类,自动生成代理类!
public class ProxyInvocationHandler implements InvocationHandler{
    // 被代理的接口
    private Rent rent;
    
    public void setRent(Rent rent){
        this.rent = rent;
    }
    
    // 生成得到代理类
    public Object getProxy(){
        // 通过反射得到类加载器,得到对应的接口
        return Proxy.newProxyInstance(this.getClass().getClassLoader(), rent.getClass().getInterfaces(), this);
    }
    
    // 重写的方法。处理代理实例,并返回结果
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable{
        // 动态代理的本质,就是使用反射机制实现
        seeHouse();
        Object result = method.invoke(rent, args);
        fare();
        return result;
    }
    
    public void seeHouse(){
        System.out.println("中介带看房");
    }
    
    public void fare(){
        System.out.println("支付中介费")}
}
public class Client{
    public static void main(String[] args){
        // 真实角色
        Host host = new Host();
        
        // 代理角色:现在没有
        ProxyInvocationHandler pih = new ProxyInvocationHandler();
        // 通过调用程序处理角色来处理我们需要调用的接口对象
        pih.setRent(host);
        // 下面的proxy就是动态生成的代理类,而非我们自己固定写好的
        // 注意返回值类型是强转成接口,而非是接口的实现类
        Rent proxy = (Rent)pih.getProxy();
        proxy.rent();
    }
}

动态传入参数

// 等我们会用这个类,自动生成代理类!
public class ProxyInvocationHandler implements InvocationHandler{
    private Object target;
    // 传入参数
    public void setTarget(Object target){
        this.target = target;
    }
 
    public Object getProxy(){
        return Proxy.newProxyInstance(this.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
    }
    
    // 重写的方法。处理代理实例,并返回结果
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable{
        // 动态代理的本质,就是使用反射机制实现
        log(method.getName());
        Object result = method.invoke(target, args);
        return result;
    }
    
    public void log(String msg){
        System.out.println("执行了" + msg + "方法");
    }
}
public class Client{
    public static void main(String[] args){
        // 真实角色
        UserServiceImpl userService = new UserServiceImpl();
        
        // 代理角色:现在没有
        ProxyInvocationHandler pih = new ProxyInvocationHandler();
        // 通过调用程序处理角色来处理我们需要调用的接口对象
        pih.setTarget(userService);
        // 下面的proxy就是动态生成的代理类,而非我们自己固定写好的
        UserServiceImpl proxy = (UserServiceImpl)pih.getProxy();
        proxy.delete(); // 此时是delete方法
    }
}

动态代理的好处

  • 一个动态代理类代理的是一个接口,一般就是对应的一类业务(如果还有一个UserServiceImplTwo实现了UserService,也重写了一些方法,那只需要改动上述程序中main函数的第一行即可,不需要再重新写一个代理类了)
  • 一个动态代理类可以代理多个类,只要是实现了同一个接口即可
CGLIB动态代理

  假设上述示例中没有定义UserService接口,只是定义一个普通的包含CRUD方法的类,此时JDK代理无法使用,因为JDK代理要求必须定义接口,对接口进行代理

  • CGLIB是在运行时动态生成代理类的方式,使用的库是CGLIB,和JDK代理相比,它不是动态生成一个实现了接口的代理类,而是直接在内存中构建一个被代理类的子类,并重写父类的方法进行代理。
public class UserServiceITest {

    public void create(){
        System.out.println("创建成功");
    }

    public void read(){
        System.out.println("创建成功");
    }
}
public class ProxyHandlerTest implements MethodInterceptor {

    private Object target;

    public ProxyHandlerTest(Object target){
        this.target = target;
    }

    /**
     * Cglib通过Enhancer生成代理类,通过实现MethodInterceptor接口,实现intercept方法
     */
    public Object getProxyObject(Class clazz){
        // 创建Enhancer对象,类似于JDK代理中的Proxy类
        Enhancer enhancer = new Enhancer();
        // 设置父类的字节码对象
        // cglib的代理类其实根据被代理对象生成的子类
        enhancer.setSuperclass(clazz);
        // 设置回调函数
        // 表示代理对象的方法都会执行MethodInterceptor的子实现类的intercept方法。
        enhancer.setCallback(this);
        // 创建代理对象
        Object proxyObject = enhancer.create();
        return proxyObject;
    }

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        log(method.getName());
        // 调用被代理对象的方法
        Object result = method.invoke(target, objects);
        return result;
    }
    public void log(String msg){
        System.out.println("执行了" + msg + "方法");
    }
}
public class Client {
    public static void main(String[] args) {
        // 创建被代理对象
        UserServiceITest userServiceITest = new UserServiceITest();
        // 创建代理对象处理程序
        ProxyHandlerTest ph = new ProxyHandlerTest(userServiceITest);
        // 获取代理对象
        UserServiceITest proxyObject = (UserServiceITest) ph.getProxyObject(UserServiceITest.class);
        // 调用被代理对象的方法
        proxyObject.create();
    }
}
JDK和CGLIB的区别
  • JDK动态代理只能对实现了接口的类生成代理,而不能针对类
  • CGLIB是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法因为是继承,所以该类或方法最好不要声明成final
  • JDK动态代理要求目标类必须要实现接口,而CGLIB动态代理则没有这个限制
  • JDK动态代理相对于CGLIB动态代理来说,因为实现方式不同,生成的代理类效率会低
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值