【设计模式】通俗易懂的Java设计模式篇!

文章目录

1.设计模式的六大原则

1.1.软件设计开发原则

(1)单一职责原则

  • 一个类只负责一个功能领域中的相应职责,就一个类而言,应该只有一个引起它变化的原因。
  • 就是实现高内聚、低耦合的指导方针。
  • 高内聚:尽可能类的每一个成员方法(最大限度的聚合),模块内部的代码,相互之间的联系越强,内聚就越高,模块的独立性就越好。
  • 低耦合:减少类内部,一个成员方法调用另一个成员方法。

(2)开闭原则

  • 对扩展开放,对修改关闭,在程序需要惊醒扩展的时候,不去修改原有的代码,实现一个热插拔的效果。

(3)里氏替换原则LSP

  • 任何基类可以出现的地方,子类一定可以出现。
  • 在程序中尽量使用基类类型定义对象,而在运行时在确定其子类类型,用子类对象来替换父类对象。

(4)依赖倒转原则

  • 是开闭原则的基础,针对接口编程,依赖于抽象而不依赖于具体。
  • 高层模块不应该依赖于底层模块,二者都应该依赖其抽象。

(5)接口隔离原则

  • 使用多个隔离的接口,比使用单个接口要好,降低类之间的耦合度。

(6)迪米特法则

  • 最少知道原则,一个实体应当尽量少的与其他实体之间发生相互作用,使得系统功能模块相对独立。
  • 类之间的耦合度越低,就越有利于复用,一个处在松耦合中的类一但被修改,不会对关联的类造成太大的波及。

软件设计开发原则的作用

  • 为了让的代码更好重⽤性,可读性,可靠性,可维护性诞⽣出了很多软件设计的原则,这6⼤设计原则是我们要掌握的
  • 将六⼤原则的英⽂⾸字⺟拼在⼀起就是SOLID(稳定的),所以也称之为SOLID原则
1.2.设计模式分类

(1)什么是GOF(Gang of Four)

1994 年,由 四位作者合称 GOF(全拼 Gang of Four)四⼈合著出版了⼀本名为 Design Patterns - Elements of Reusable Object-Oriented Software.
他们所提出的设计模式主要是基于以下的⾯向对象设计原则。
1)对接⼝编程⽽不是对实现编程。
2)优先使⽤对象组合⽽不是继承

(2)常见的三大设计模式分类

  • 创建型模式
    • 提供了一种在创建对象的同时隐藏创建逻辑的方式,使得程序在判断针对某个给定实例需要创建哪些对象时更加灵活。
常用:工厂模式、抽象工厂模式、单例模式、建造者模式
不常用:原型模式
  • 结构型模式
    • 关注类和对象的组合。继承的概念被用来组合接口和定义组合对象获得新功能的方式。
常用:适配器模式、桥接模式、装饰器模式、代理模式
不常用:组合模式、外观模式、享元模式
  • 行为模式
    • 特别关注对象之间的通信
常用:责任链模式、迭代器模式、观察者模式、状态模式、策略模式、模板模式
不常用:备忘录模式、命令模式
几乎不用:访问者模式、中介者模式、解释器模式

2.创建型设计模式-单例设计模式

2.1.单例模式简介

(1)简介

  • 单例设计模式只包含一个对象被称为单例的特殊类,通过单例模式可以保证系统中,应该该模式的类只有一个对象实例。

(2)使用场景

  • 业务系统全局只需要一个对象实例,比如mysql连接,redis连接。
  • Spring IOC容器中的bean默认就是单例。
  • Spring中的@Autowired注入的对象就是单例的。

(3)单例模式分类

  • **懒汉模式:**懒加载,延迟创建对象,只有当需要用到对象的时候才会加载创建

在这里插入图片描述

  • **饿汉模式:**程序启动的时候,就加载创建对象

在这里插入图片描述

(4)实现步骤

  • 私有化构造方法
  • 提供获取单例的方法
2.2.懒汉方式实现

(1)第一种方式

  • 高并发下不能保证创建的对象只有一个,多个线程进入到判空,创建对象返回
public static SingletonLazy getInstance(){
   
     if(instance == null){
   
         instance = new SingletonLazy();
     }
     return instance;
}

(2)第二种方式

  • 高并发下能保证创建的对象是一个,但是synchronized锁定方法,高并发下性能损耗严重
public static synchronized SingletonLazy getInstance(){
   
    if(instance == null){
   
         instance = new SingletonLazy();
     }
     return instance;
}

(3)第三种方式

  • 虽然加了锁,但是同样不能保证单例,锁的位置加的不正确
public static SingletonLazy getInstance(){
   
    if(instance == null){
   
        synchronized (SingletonLazy.class){
   
            instance = new SingletonLazy();
         }
    }
    return instance;
}

(4)第四种方式

  • DCL双重检查锁定(Double-Checked-Locking),多线程下保持高性能

  • instrance = new SingletonLazy(),并不是原子性操作,衍生到内存模型

    • 1.分配空间给对象
    • 2.在空间内创建对象
    • 3.将对象赋值给引用的instance
    • 注意:假如线程是按照1->3->2的顺序,那这个对象是不完整的
  • volatile禁止指令重排

private static volatile SingletonLazy instance;

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

(5)测试

  • 一步一步优化,最终选择第四种作为单例懒汉模式的实现
public class SingletonLazy{
   
    private static volatile SingletonLazy instance;
    
    private SingletonLazy(){
   };

    public static SingletonLazy getInstance(){
   
        if(instance == null){
   
            synchronized (SingletonLazy.class){
     //1
                if(instance == null){
     //2
                    instance = new SingletonLazy();  //3
                }
             }
        }
        return instance;
    }
    public void pub(){
   
        System.out.println("方法调用");
    }
}
1.线程A进入 getInstance() 方法。

2.由于 singleton为 null,线程A在 //1 处进入 synchronized 块。

3.线程A被线程B预占。

4.线程B进入 getInstance() 方法。

5.由于 singleton仍旧为 null,线程B试图获取 //1 处的锁。然而,由于线程A已经持有该锁,线程B在 //1 处阻塞。

6.线程B被线程A预占。

7.线程A执行,由于在 //2 处实例仍旧为 null,线程A还创建一个 Singleton 对象并将其引用赋值给 instance。

8.线程A退出 synchronized 块并从 getInstance() 方法返回实例。

9.线程A被线程B预占。

10.线程B获取 //1 处的锁并检查 instance 是否为 null。

11.由于 singleton是非 null 的,并没有创建第二个 Singleton 对象,由线程A所创建的对象被返回。
public class Main {
   
    public static void main(String[] args) {
   
        SingletonLazy.getInstance().pub();
    }
}

在这里插入图片描述

2.3.饿汉模式实现
  • 饿汉方式:提前创建好对象
  • 优点:实现简单,没有多线程同步问题
  • 缺点:不管有没有使用,instance对象一直占着这段内存
  • 如果对象不大,且创建不复杂,直接用饿汉模式即可,其他情况用懒汉模式
public class SingletonHungry{
   
    
    //提前new好,jvm加载时创建
    private static SingletonHungry instance = new SingletonHungry();
    
    //构造私有化
    private SingletonHungry(){
   };
    
    public static SingletonHungry getInstance(){
   
        return instance;
    }
    public void pub(){
   
        System.out.println("方法调用");
    }
}

在这里插入图片描述

2.4.JDK源码里的单例模式
  • Runtime.java,java运行时的类。

在这里插入图片描述

  • Desktop,java图形类

在这里插入图片描述

3.创建型设计模式-工厂模式

3.1.工厂设计模式简介

(1)简介

  • 它提供了一种创建对象的最佳方式,我们在创建对象时不会对客户端暴漏创建逻辑,并且时通过使用一个共同的的接口来指向新创建的对象

(2)工厂模式的三种实现方式

  • 简单工厂模式:通过传入相关的类型来返回相应的类,这种方式比较单一,可扩展性较差。
  • 工厂方法模式:通过实现类实现相应的方法来决定相应的返回结果,这种方式的可扩展性比较强。
  • 抽象工厂模式:基于上述两种模式的扩展,支持细化产品

(3)应用场景

  • 解耦:分离职责,把复杂对象的创建和使用的过程分开
  • 复用代码,维护成本降低:
    • 如果对象创建复杂且多出要用到,如果每处都进行编写,则很多重复代码,如果业务逻辑发生了改变,需用四处修改;
    • 使用工厂模式统一创建,则只要修改工厂类即可,降低成本

在这里插入图片描述

3.2.简单工厂模式

(1)简介

  • 简单工厂模式又称静态工厂模式,可以根据参数的不同返回不同类的实例,专门定义一个类来负责创建其他类的实例,被创建的实例通常都是具有共同的父类。由于工厂方法是静态的方法,可以通过类名直接调用,只需传入简单的参数即可。

(2)核心组成

  • Factory:工厂类,简单工厂模式的核心,它负责实现创建所有的内部逻辑。

  • IProduct:抽象产品类,简单工厂模式所创建的所有对象的父接口,描述所有实例共有的公共方法。

  • Product:具体产品类,是简单工厂模式的创建目标。

(3)实现步骤

  • 创建抽象产品类,里面有产品的抽象方法,由具体的产品类去实现
  • 创建具体产品类,继承了他们的父类,并实现具体方法
  • 创建工厂类,提供了一个静态方法createXXX用来生产产品,只需要传入你想产品名称

(4)编码实现

  • 统一支付下单接口Pay
public interface Pay {
   

    /**
     * 统一下单接口
     */
    void unifiedOrder();

}
  • 微信支付下单实现类WechatPay
public class WechatPay implements Pay{
   
    @Override
    public void unifiedOrder() {
   
        System.out.println("微信支付统一下单接口");
    }
}
  • 支付宝支付下单实现类AliPay
public class AliPay implements Pay {
   
    @Override
    public void unifiedOrder() {
   
        System.out.println("支付宝支付统一下单接口");
    }
}
  • 统一创建工厂类SimplePayFactory
public class SimplePayFactory {
   
    public static Pay createPay(String payType){
   
        if(payType == null){
   
            return null;
        }
        if("WECHAT_PAY".equalsIgnoreCase(payType)){
   
            return new WechatPay();
        }else if("ALI_PAY".equalsIgnoreCase(payType)){
   
            return new AliPay();
        }
        //扩展更多
        return null;
    }
}
  • 测试创建
public static void main(String[] args) {
   
    Pay pay = SimplePayFactory.createPay("ALI_PAY");
    pay.unifiedOrder();
}

在这里插入图片描述

(5)类图关系

在这里插入图片描述

  • 创建顶层支付接口Pay。
  • AliPay、WechatPay分别实现Pay接口。
  • 创建工厂类,编写静态方法createPay(),根据传入的参数确定创建那种支付实现类。

(6)优点和缺点

  • 优点
    • 将对象的创建和对象本身的业务处理分离可以降低系统的耦合度,使得两者修改起来都相对容易。
  • 缺点
    • 工厂类的职责相对过重,增加新的产品需要修改工厂类的判断逻辑没这一点于开闭原则是相违背的。
3.3.工厂方法模式

(1)简介

  • 工厂方法模式又称工厂模式,是对简单工厂模式的进一步抽象化,其好处是可以使系统在不修改原来代码的前提下引进新的产品,既满足开闭原则。

  • 通过工厂父类定义负责创建产品的公共接口,通过子类来确定所需创建的类型。

  • 相比简单工厂而言,此种方法具有更多的可扩展性和复用性,同时也增强了代码的可读性。

  • 将类的实例化(具体产品的创建)延迟到工厂类的子类(具体工厂)中完成,即由子类来决定应该实例化哪一个类。

(2)核心组成

  • IProduct:抽象产品类,描述所有实例所共有的公共接口。
  • Product:具体产品类,实现抽象产品类的接口,工厂类创建对象,如果有多个需要定义多个。
  • IFactory:抽象工厂类,描述具体工厂的公共接口。
  • Factory:具体工厂类,实现创建产品类对象,实现抽象工厂类的接口,如果又多个需要定义多个。

(3)编码实现

  • 支付类型工厂接口PayFactory
public interface PayFactory {
   
    Pay createPay();
}
  • 支付宝类型工厂实现AliPayFactory
public class AliPayFactory implements PayFactory {
   
    @Override
    public Pay createPay() {
   
        return new AliPay();
    }
}
  • 微信类型工厂实现WechatPayFactory
public class WechatPayFactory implements PayFactory {
   
    @Override
    public Pay createPay() {
   
        return new WechatPay();
    }
}
  • 测试
public static void main(String[] args) {
   
     PayFactory pay = new AliPayFactory();
     pay.createPay().unifiedOrder();
}

在这里插入图片描述

(4)类关系图

在这里插入图片描述

  • 创建支付接口Pay。

  • 创建支付实现类AliPay、WechatPay。

  • 创建支付创建工厂PayFactory。

  • 创建工厂创建实现类AliPayFactory、WechatPayFactory。

  • 客户端生产某个支付类,直接new 具体支付工厂类即可。

(5)优点和缺点

  • 优点
    • 符合开闭原则,增加一个产品类,只需要实现其父类接口,不会对原有的类进行修改。
    • 符合单一职责原则,每个工厂只负责生产对应的产品。
    • 使用者只需知道产品的抽象类,无需关心它的实现类,满足迪米特法则。
  • 缺点
    • 每增加一个产品就要增加一个类
    • 每个产品都要有对应的具体工厂和具体产品类
3.4.抽象工厂模式

(1)简介

  • 抽象工厂模式是基于简单工厂和工厂方法模式的扩展,是工厂方法模式的升级版,当需要创建的产品有多个产品线时使用抽象工厂模式是比较好的选择,抽象工厂模式在spring种应用最为广泛的一种设计模式。

(2)背景

  • 工厂方法模式引入工厂等级结构,解决了简单工厂模式中工厂类职责过重的问题。
  • 但工厂方法模式中每个工厂只创建一类具体类的对象,后续发展可能会导致工厂类过多,因此将一些相关的具体类组成一个“具体类族”,由同一个工厂来统一生产,强调的是一系列相关的产品对象。

(3)实现步骤

1.定义两个接口 Pay、Refund。

2.创建具体的Pay产品、创建具体的Refund产品。

3.创建抽象工厂OrderFactory接口,里面两个方法createPay、createRefund。

4.创建支付宝产品族AliOrderFactory,实现OrderFactory抽象工厂。

5.创建微信支付产品族WechatOrderFactory,实现OrderFactory抽象工厂。

6.定义一个超级工厂创造器,通过传递参数获取对应的工厂。

(4)类关系图

在这里插入图片描述

  • 抽象工厂涉及到产品族,一个工厂创建这一族的所有产品。

  • 创建支付接口,生产支付具体类,PayFactory,定义抽象方法统一下单方法unifiedOrder()。

  • 创建支付实现类,实现PayFactory接口,实现具体的unifiedOrder()方法,AliPay、WechatPay。

  • 创建退款接口,生产退款具体类,RetundFactory,定义抽象方法统一退款方法retund()。

  • 创建退款实现类,实现RetundFactory接口,实现具体的retund()方法,AliRetund、WechatRetund。

  • 定义OrderFactory,订单工厂中包含订单的支付createPay、订单退款createRetund()。

  • 创建AliOrderFactory、WechatOrderFactory实现OrderFactory中的createPay()、createRetund()方法。

  • AliOrderFactory、WechatOrderFactory分别返回对应的支付、退款方法。

  • 创建超级工厂类,传入参数返回相应的订单工厂实现类。

(5)编码实现

  • 超级工厂OrderFactory
public interface OrderFactory {
   

    PayFactory createPay();

    RefundFactory createRefund();
}
  • 支付工厂Pay
public interface Pay {
   

    /**
     * 统一下单接口
     */
    void unifiedOrder();

}
  • 退款工厂Retund
public interface Refund {
   

    /**
     * 退款
     */
    void refund();

}
  • 产品族具体实现AliOrderFactory
public class AliOrderFactory implements OrderFactory {
   
    @Override
    public PayFactory createPay() {
   
        return new AliPay();
    }

    @Override
    public RefundFactory createRefund() {
   
        return new AliRefund();
    }
}
  • 产品组具体产品AliPay
public class AliPay implements PayFactory {
   
    @Override
    public void unifiedOrder() {
   
        System.out.println("支付宝支付统一下单接口");
    }
}
  • 产品组具体产品AliRetund
public class AliRefund implements RefundFactory {
   
    @Override
    public void refund() {
   
        System.out.println("支付宝退款");
    }
}
  • 产品族具体实现WechatOrderFactory
public class WechatOrderFactory implements OrderFactory {
   
    @Override
    public PayFactory createPay() {
   
        return new WechatPay();
    }

    @Override
    public RefundFactory createRefund() {
   
        return null;
    }
}
  • 产品组具体产品WechatPay
public class WechatPay implements PayFactory {
   
    @Override
    public void unifiedOrder() {
   
        System.out.println("微信支付统一下单接口");
    }
}

  • 产品组具体产品WechatRetund
public class WechatRefund implements RefundFactory {
   
    @Override
    public void refund() {
   
        System.out.println("微信退款");
    }
}
  • 总创建工厂
public class FactoryProducer {
   

    public static OrderFactory getFactory(String type){
   
        if("WECHAT".equalsIgnoreCase(type)){
   
            return new WechatOrderFactory();
        }else if("ALI".equalsIgnoreCase(type)){
   
            return new AliOrderFactory();
        }
        return null;
    }
}
  • 测试
OrderFactory ali = FactoryProducer.getFactory("ALI");
ali.createPay().unifiedOrder();
ali.createRefund().refund();

在这里插入图片描述

(6)优点和缺点

  • 优点
    • 当一个产品族中的多个对象被设计成一起工作时,它能保证使用方始终只使用同一个产品族中的对象
    • 产品等级结构扩展容易,如果需要增加多一个产品等级,只需要增加新的工厂类和产品类即可, 比如增加银行支付、退款
  • 缺点
    • 产品族扩展困难,要增加一个系列的某一产品,既要在抽象的工厂和抽象产品里修改代码,不是很符合开闭原则
    • 增加了系统的抽象性和理解难度

4.创建型设计模式-原型设计模式

4.1.原型模式简介

(1)简介

  • 原型设计模式Prototype
    • 是一种对象创建型模式,使用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象,主要用于创建重复的对象,同时又能保证性能
    • 工作原理是将一个原型对象传给那个要发动创建的对象,这个要发动创建的对象通过请求原型对象拷贝自己来实现创建过程
    • 应该是最简单的设计模式了,实现一个接口,重写一个方法即完成了原型模式

(2)核心组成

  • Prototype: 声明克隆方法的接口,是所有具体原型类的公共父类,Cloneable接口
  • ConcretePrototype : 具体原型类
  • Client: 让一个原型对象克隆自身从而创建一个新的对象

(3)应用场景

  • 创建新对象成本较大,新的对象可以通过原型模式对已有对象进行复制来获得
  • 如果系统要保存对象的状态,做备份使用

(4)深浅拷贝

  • 浅拷贝实现 Cloneable,深拷贝是通过实现 Serializable 读取二进制流

  • 浅拷贝

如果原型对象的成员变量是基本数据类型(int、double、byte、boolean、char等),将复制一份给克隆对象。
如果原型对象的成员变量是引用类型,则将引用对象的地址复制一份给克隆对象,也就是说原型对象和克隆对象的成员变量指向相同的内存地址。
通过覆盖Object类的clone()方法实现浅克隆
  • 深拷贝
无论原型对象的成员变量是基本数据类型还是引用数据类型,都将复制一份给克隆对象,如果需要实现深克隆,可以通过序列化(serializable)等方式来实现。
  • 原型模式是内存二进制流的拷贝,比new对象性能高很多。
4.2.原型模式案例实战

(1)编码实现

编写Person实体,浅克隆实现Cloneable,深克隆实现Serializable

public class Person implements Cloneable, Serializable {
   

    private String name;

    private int age;

    private List<String> list = new ArrayList<>();

    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 List<String> getList() {
   
        return list;
    }

    public void setList(List<String> list) {
   
        this.list = list;
    }

    //浅克隆方法
    @Override
    public Person clone() throws CloneNotSupportedException {
   
        return (Person)super.clone();
    }
	//深克隆方法
    public Object deepClone(){
   
        try{
   
            //输出 序列化
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(baos);
            oos.writeObject(this);

            //输入 反序列化
            ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
            ObjectInputStream ois = new ObjectInputStream(bais);
            Person copyObj = (Person)ois.readObject();

            return copyObj;
        }catch (Exception e){
   
            e.printStackTrace();
            return null;
        }
    }

    @Override
    public String toString() {
   
        return "Persion{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", list=" + list +
                '}';
    }
}
Person person1 = new Person();
person1.setName("李祥");
person1.setAge(20);
person1.getList().add("aaa");
//Person person2 = (Person) person1.deepClone();
Person person2 = (Person) person1.deepClone();
person2.getList().add("ccc");
System.out.println(person2);
  • 调用浅克隆方法,运行接果

在这里插入图片描述

  • 调用深克隆方法,运行接果

在这里插入图片描述

(2)优点和缺点

  • 优点
    • 当创建新的对象实例较为复杂时,使用原型模式可以简化对象的创建过程,可以提高新实例的创建效率。
    • 可辅助实现撤销操作,使用深克隆的方式保存对象的状态,使用原型模式将对象复制一份并将其状态保存起来,以便在需要的时候使用恢复到到历史状态。
  • 缺点
    • 需要为每一个类配备一个克隆方法,对已有的类进行改造时,需要修改源代码,违背了“开闭原则”。
    • 在实现深克隆时需要编写较为复杂的代码,且当对象之间窜在多重的嵌套引用时,需要对每一层对象都必须支持深克隆。

5.创建型设计模式-建造者模式

5.1.建造者模式简介

(1)简介

  • 建造者模式(Builder Pattern)
    • 使用多个简单的对象一步一步构建称一个复杂的对象,将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
    • 允许用户只通过指定复杂对象的类型和内容就可以构建他们,不需要知道内部的具体构建细节

(2)类关系图

在这里插入图片描述

  • 创建顶层构建者接口ComputerB

  • 8
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
1) 优秀的程序应该是这样的:阅读时,感觉很优雅;新增功能时,感觉很轻松;运行时,感觉很快速,这就需要设计模式支撑。2) 设计模式包含了大量的编程思想,讲授和真正掌握并不容易,网上的设计模式课程不少,大多讲解的比较晦涩,没有真实的应用场景和框架源码支撑,学习后,只知其形,不知其神。就会造成这样结果: 知道各种设计模式,但是不知道怎么使用到真实项目。本课程针对上述问题,有针对性的进行了升级 (1) 授课方式采用 图解+框架源码分析的方式,让课程生动有趣好理解 (2) 系统全面的讲解了设计模式,包括 设计模式七大原则、UML类图-类的六大关系、23种设计模式及其分类,比如 单例模式的8种实现方式、工厂模式的3种实现方式、适配器模式的3种实现、代理模式的3种方式、深拷贝等3) 如果你想写出规范、漂亮的程序,就花时间来学习下设计模式吧课程内容和目标本课程是使用Java来讲解设计模式,考虑到设计模式比较抽象,授课采用 图解+框架源码分析的方式1) 内容包括: 设计模式七大原则(单一职责、接口隔离、依赖倒转、里氏替换、开闭原则、迪米特法则、合成复用)、UML类图(类的依赖、泛化和实现、类的关联、聚合和组合) 23种设计模式包括:创建型模式:单例模式(8种实现)、抽象工厂模式、原型模式、建造者模式、工厂模式。结构型模式:适配器模式(3种实现)、桥接模式、装饰模式、组合模式、外观模式、享元模式、代理模式(3种实现)。行为型模式:模版方法模式、命令模式、访问者模式、迭代器模式、观察者模式、中介者模式、备忘录模式、解释器模式(Interpreter模式)、状态模式、策略模式、职责链模式(责任链模式)2) 学习目标:通过学习,学员能掌握主流设计模式,规范编程风格,提高优化程序结构和效率的能力。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

互联网小阿祥

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值