Head First设计模式笔记

本文介绍了设计模式的基本概念,包括常见的设计模式如策略模式、观察者模式等,以及设计原则如开闭原则、依赖倒置原则等。同时,文章还详细解析了每种模式的适用场景和实现方式。
  • 设计模式入门(XIII)
    设计大师关心的是建立弹性的设计可以维护可以应付改变

    策略模式
    观察者模式
    装饰者模式
    工厂模式
    单件模式
    命令模式
    适配器模式与外观模式
    模板方法模式
    迭代器与组合模式
    状态模式
    代理模式
    复合模式
    
    桥接模式
    生成器模式
    责任链模式
    蝇量模式
    解释器模式
    中介者模式
    备忘录模式
    原型模式
    访问者模式

  • 软件开发的一个不变真理(P8)
    不管当初软件设计得多好,一段时间之后,总是需要成长与改变,否则软件就会“死亡”。

  • 设计原则
    找出应用中可能需要变化之处,把它们独立出来,不要和那些不需要变化的代码混在一起。(P9)
        换句话说,如果每次新的需求一来,都会使某方面的代码发生变化,那么你就可以确定,这部分的代码需要被抽出来,和其他稳定的代码有所区分。

        这样的概念很简单,几乎是每个设计模式背后的精神所在。所有的模式都提供了一套方法让“系统中的某部分改变不影响其他部分”。

    针对接口编程,而不是针对实现编程。(P11)
        “针对接口编程”真正的意思是“针对超类型(supertype)编程”。
        “针对超类型编程”这句话,可以更明确地说成“变量的声明类型应该是超类型,通常是一个抽象类或者是一个接口,如此,只要是具体实现了该超类型的类所产生的对象都可以赋给这个变量。”
        其实就是利用多态技术。

    多用组合,少用继承。(P23)
        使用组合(composition)建立系统具有很大的弹性。

    为了交互对象之间的松耦合设计而努力。(P53)
        松耦合的设计之所以能让我们建立有弹性的OO系统,能够应对变化,是因为对象之间的互相依赖降到了最低。

    类应该对扩展开发,对修改关闭。(P86)
        我们的目标是允许类容易扩展,在不修改现有代码的情况下,就可搭配新的行为。如能实现这样的目标,有什么好处呢?这样的设计具有弹性可以应对改变,可以接受新的功能来应对改变的需求。
        在选择需要被扩展的代码部分时要小心。每个地方都采用开放-关闭原则,是一种浪费,也没必要,还会导致代码变得复杂且难以理解。
        
    要依赖抽象,不要依赖具体类(P139)
        依赖倒置原则(Dependency Inversion Principle) P140

    “最少知识”原则(P265)
        只和你的密友谈话。
        这是说,当你在设计一个系统,不管是任何对象,你都要注意它所交互的类有哪些,并注意它和这些类是如何交互的。
        又叫得墨忒耳法则(Law of Demeter)

    好莱坞原则(P296)
        别调用(打电话给)我们,我们会调用(打电话给)你。

    一个类应该只有一个引起变化的原因(P339)
        区分设计中的责任,是最困难的事情之一。
        类的每个责任都有改变的潜在区域。超过一个责任,意味着超过一个改变的区域。
        尽量让每个类保持单一责任。

  • 策略模式(P24)
    STRATEGY
    策略模式定义了算法族,分别封装起来,让它们之间可以互相替换。
    此模式让算法的变化独立于使用算法的客户。

  • 观察者模式(P44)
    OBSERVER
    
    生活中的例子:订报纸。
    报社一旦出版了新的报纸,就会送给每个订阅者。订阅者可以取消订阅。
    
    专业术语:
        在观察者模式中,报社为Subject(主题),订阅者为Observer(观察者)

    观察者模式定义了对象之间的一对多依赖,这样依赖,当一个对象改变状态时,它的所有依赖者都会收到通知并自动更新。

    Java内置的观察者模式:
    主题需要继承类Observable(可观察的)。
        Observable通知的方法有两个:
            notifyObservers()和notifyObservers(Object arg)
            前者只是对后者的一个简单调用。
            notifyObservers() {
                notifyObservers(null);
            }
            arg就是要告诉观察者的数据对象。

            只所以存在notifyObservers()方法,是为了让观察者可以利用pull(拉)的方式从主题获取自己所需要的信息。

        在Observable通知之前,需要手动调用方法setChange(),标记状态已经改变。
            这么做的主要目的是让你可以认为控制push(推送)

    观察者需要实现接口Observerable
        接口内的update(Observable o, Object arg)方法第一个参数是主题,第二个参数是数据对象。
            这样做是为了让观察者知晓是哪个主题推送的数据。

    观察者模式的代表人物——MVC。

    Java中的Swing、RMI、JavaBeans等都用了观察者模式。

  • 装饰者模式(P91)
    DECORATOR

    装饰者模式动态地将责任附加到对象上。若要扩展功能,装饰着提供了比继承更具弹性的替代方案。
    
    装饰者与被装饰者有着相同的超类。

    Java中的例子:Java I/O。
    java.io使用了装饰者模式:
        
        其中FilterInputStream是一个抽象装饰者。
        而FileInputStream,StringBufferInputStream,ByteArrayInputStream这些InputStream类是可以被装饰者包起来的具体组件。
        FilterInputStream的子类们都有一个InputStream类型的变量,以达到装饰的目的。

        提示:所以我们可以继承FilterInputStream来编写自己的装饰者,对I/O进行处理。

  • 工厂模式(P134)
    FACTORY METHOD

    工厂方法模式(Factory Method Pattern)通过让子类决定该创建的对象是什么,来达到将对象创建的过程封装的目的。

    工厂方法模式定义了一个创建对象的接口,但由子类决定要实例化的类是哪一个。工厂方法让类把实例化推迟到子类。

    依赖倒置原则(P143)
        指导方针(避免在OO设计中违反依赖倒置原则):
            变量不可以持有具体类的引用。
                如果使用new,就会持有具体类的引用。你可以改用工厂来避开这样的做法(当然,你在工厂中还是必须要new具体的类,只是不在当前类中做,把所有的这种可能变化封装到工厂中)。
            
            不要让类派生自具体类。
            
            不要覆盖基类中已实现的方法。

  • 抽象工厂模式(P156)
    抽象工厂模式提供一个接口,用于创建相关或依赖对象的家族,而不需要明确指定具体类。

  • 单件模式(P177)
    SINGLETON

    单件模式(Singleton Pattern)确保一个类只有一个实例,并提供一个全局访问点。
    
    普通的单件模式:

        public class Singleton {
            private static Singleton uniqueInstance;

            // 存取修饰符(Access Modifier)private让类不能通过构造函数实例化
            private Singleton() {}

            public static Singleton getInstance() {
                if (uniqueInstance == null) {
                    uniqueInstance = new Singleton();
                }
                return uniqueInstance;
            }
        }

    
    单件模式如果是用来表示唯一的资源,必须要考虑多线程问题。
    解决多线程有三种典型方法:
        1.把getInstance方法synchronized
            如果你的应用程序可以接受getInstance()造成的额外负担,就可以考虑这个方法。
            补充:synchronized一个方法可能造成程序执行效率下降100倍。

            public class Singleton {
                private static Singleton uniqueInstance;

                private Singleton() {}

                public static synchronized Singleton getInstance() {
                    if (uniqueInstance == null) {
                        uniqueInstance = new Singleton();
                    }
                    return uniqueInstance;
                }
            }

        2.使用“急切”创建实例,而不用延迟实例化的做法:
        有可能你的程序会过早的申请导致资源的浪费。

            public class Singleton {
                // 这样写其实等同于在静态初始化器中写
                private static Singleton uniqueInstance = new Singleton();

                private Singleton() {}

                public static Singleton getInstance() {
                    return uniqueInstance;
                }
            }

        3.用“双重检查加锁”(double-checked locking),在getInstance()中减少使用同步:
            补充:volatile,
            volatile a:
                比如两个线程在运行中,第一个线程已经初始化,将变量a载入了缓存后,这时候第二个线程修改了a的值。如果a没有用volatile修饰,那么第一个线程使用的可能是缓存中未改过的a(可能而已)。但是使用volatile修饰符后,会保证每次是从a的内存地址中加载a的值。
                volatile是不对a进行优化。              

            public class Singleton {
                private static Singleton uniqueInstance;

                private Singleton() {}

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


  • 命令模式(P206)
    COMMAND

    命令模式将“请求”封装成对象,以便使用不同的请求、队列或者日志来参数化其他对象。命令模式也支持可撤销的操作。

 

    命令模式的更多用途:队列请求以及日志请求,
        在Java中ExecutorService就利用了命令模式实现了队列请求。其中的execute方法就是把Runnable加入WorkQueue并用Thread包装调用。
 
    空对象(P214)
    空对象(null object)是一种代替判断对象是否为空的方法,例如:
    传统写法:

    class A implements BaseClass {
        void process() {
            //TODO
        }
    }
    class Main {
        private BaseClass a;
        private BaseClass b;
        fd cgf
        {
            a = new A();
            b = null;
        }
 
        void run() {
            if (a != null) {
                a.process();                    
            }
            if (b != null) {
                b.process();                    
            }
        }
 
        public static void main() {
            new Main().run();
        }
    }

    空对象写法:

    class NoA implements BaseClass {
        void process() {
            //NOTHING
        }
    }    
    class Main {
        private BaseClass a;
        private BaseClass b;
        private BaseClass nullObject = new NoA();
 
        {
            a = new A();
            b = nullObject;
        }
 
        void run() {
            a.process();                    
            b.process();                    
        }
 
        public static void main() {
            new Main().run();
        }
    }


  • 适配器模式(P243)
    ADAPTER

    适配器模式将一个类的接口,转换成客户期望的另一个接口。适配器让原本接口不兼容的类可以合作无间。

    对象适配器



  类的适配器

  类的适配器:通过多重继承实现(P244)    Java没有多重继承,不能采用这种方式。
    对象的适配器:通过组合实现(P243)
    
    装饰者模式/适配器模式/外观模式
        装饰者模式的目的是加入新的行为或者责任;
        适配器模式的目的是转换接口
        外观模式的目的是简化接口,组合子系统

  • 外观模式(P254)
    FACADE

    外观模式提供了一个统一的接口,用来访问子系统中的一群接口。外观定义了一个高层接口,让子系统更容易使用。

    外观模式只是提供你更直接的操作,并未将原来的子系统阻隔起来。外观没有封装子系统的类,只是提供简化的接口。所以客户如果觉得有必要,依然可以直接使用子系统。这是外观模式一个很好的特征:提供简化的接口的同时,依然将系统完整的功能暴露出来,以供需要的人使用。

    外观和适配器可以包装许多类,但是外观的意图是简化接口,而适配器的意图是将接口转换成不同的接口


  • 模板方法模式(P289)
    METHOD 

    模板方法模式在一个方法中定义了一个算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。

    模板方法定义了一个算法的步骤,并允许子类为一个或多个步骤提供实现。


    abstract class AbstractClass {

        final void templateMethod() {

            primitiveOperation1();

            primitiveOperation2();

            concreteOperation();

            hook();

        }

        abstract void primitiveOperation1();    //由子类完成的步骤

        abstract void primitiveOperation2();    //由子类完成的步骤

        final void concreteOperation() {    //通用的步骤

            // 这里是实现

        }

        void hook() {}    //这是一个具体的方法,但它什么事情都不做。我们称这种方法为hook(钩子)。子类可以视情况决定要不要覆盖它们。

    }


    final修饰符让子类无法覆盖。
    钩子是一种被声明在抽象类中的方法,但只有空的或者默认的实现。钩子的存在,可以让子类有能力对算法的不同点进行挂钩。要不要挂钩,由子类自行决定。
 

    当你的子类“必须”提供算法中某个方法或步骤的实现时,就使用抽象方法。如果算法的这个部分是可选的,就用钩子。

    策略模式 vs 模板方法模式(P308
        策略模式定义了一个算法家族(实现同一个(或者相近)目的的一组算法),并让这些算法可以互换(由客户决定)。
        模板方法模式定义了一个算法的大纲,由子类实现某些步骤。

        策略模式和模板方法模式都封装算法,一个用组合,一个用继承。

    工厂方法是模板方法的一个特殊版本。

  •  迭代器模式(P336)
    ITERATOR
    
    迭代器模式提供一种方法顺序访问一个聚合对象中的各个元素,而又不暴露其内部的表示。
    

    迭代器意味着没有次序。只是取出所有的元素,并不表示取出元素的先后就代表元素的大小次序。

  • 组合模式(P356)
    COMPOSITE

    组合模式允许你将对象组合成树形结构来表现“整体/部分”层次结构。组合能让客户以一致的方式处理个别对象以及对象组合。

    组合模式的重点:
    1.以树的形式管理数据;
    2.树中的节点有相同的父类——Component;
    3.节点分为两类:Leaf和Composite,Composite又由子树构成(Composite和Leaf)。

    SWT中就用了组合模式管理各个组件,所有的组件都是Composite的子类。

    如果你要不断地遍历一个组合,而且它的每一个子节点都需要进行某些计算,那你就应该使用缓存来临时保存结果,省去遍历的开支。


  • 状态模式(P410)
    STATE

    状态模式允许对象在内部状态改变时改变它的行为,对象看起来好像修改了它的类。

    

    客户和Context交流。调用Context里面的方法,再由Context调用实际的State类中相应方法,以此实现State的跳转。
    Context(上下文)是一个类,它可以拥有一些内部状态。

    状态模式 vs 策略模式
        它们拥有相同的类图。但是意图不一样。
        状态模式中,当前状态在状态对象集合中游走改变,以反映出context内部的状态,因此context的行为也会跟着改变。但是context的客户对于状态对象了解不多,甚至根本是浑然不觉。
        策略模式中,客户通常主动指定Context所要组合的策略对象是哪一个。


  • 代理模式(P460)
    PROXY

    代理模式为另一个对象提供一个替身或占位符以控制对这个对象的访问。

    使用代理模式创建代表(representative)对象,让代表对象控制某对象的访问,被代理的对象可以是远程的对象、创建开销大的对象或需要安全控制的对象。
        远程代理控制访问远程对象(RMI)
        虚拟代理控制访问创建开销大的资源
        保护代理基于权限控制对资源的访问

    虚拟代理(Virtual Proxy) (P462)
        虚拟代理作为创建开销大的对象的代表。虚拟代理经常直到我们真正需要一个对象的时候才创建它。

        例子:显示CD封面(P463)
            在CD封面没有从服务器上下载完成之前,由虚拟代理控制,在界面上显示“CD封面加载中,请稍候···”,一旦加载完成,代理就把显示的职责委托给Icon。
            ImageProxy的工作是,
                首先创建一个ImageIcon,然后从网络URL上加载图像。
                在加载的过程中,ImageProxy显示“CD封面加载中,请稍候···”。
                当图像加载完毕,IamgeProxy把所有方法调用委托给真正的ImageIcon。

    代理模式 vs 装饰者模式
        代理控制对象的访问
        装饰者为对象增加行为

    动态代理
        Java在java.lang.reflect中有自己的代理支持,利用这个包你可以在运行时动态地创建一个代理类,实现一个或多个接口,并将方法的调用转发到你所指定的类。因为实际的代理类是在运行时创建的,我们称这个Java技术为动态代理。

    保护代理(P477)
        保护代理是一种根据访问权限决定客户可否访问对象的代理。

    对象村的配对(P475)

        对象村的约会服务系统。

PersonBean接口


public interface PersonBean {

    String getName();

    String getGender();

    String getInterests();

    int getHotOrNotRating();


    void setName(String name);

    void setGender(String gender);

    void setInterests(String interests);    // setName, setGender, setInterests()是一个人的基本信息,只能由自己进行设置,别人不能修改。

    void setHotOrNotRating(int rating);    // setHotOrNotRating()是一个人的评分,只能由其他人进行评分,自己不能修改。

}


PersonBeanImpl实现PersonBean接口

public class PersonBeanImpl implements PersonBean {

    String name;

    String gender;

    String interests;

    int rating;

    int ratingCount = 0;


    @Override

    public String getName() {

        return name;

    }


    @Override

    public String getGender() {

        return gender;

    }


    @Override

    public String getInterests() {

        return interests;

    }


    @Override

    public int getHotOrNotRating() {

        if (ratingCount == 0) {

            return 0;

        }

        return rating / ratingCount;

    }


    @Override

    public void setName(String name) {

        this.name = name;

    }


    @Override

    public void setGender(String gender) {

        this.gender = gender;

    }


    @Override

    public void setInterests(String interests) {

        this.interests = interests;

    }


    @Override

    public void setHotOrNotRating(int rating) {

        this.rating += rating;

        ratingCount++;

    }

}


OwnerInvocationHandler,拥有者自己使用
注意:InvocationHandler不是代理,只是代理的辅助工具。代理会把调用转给它处理(调用方法invoke,proxy指明了是哪个代理调用的,method指明了调用的方法,args是参数)。

import java.lang.reflect.InvocationHandler;

import java.lang.reflect.InvocationTargetException;

import java.lang.reflect.Method;


public class OwnerInvocationHandler implements InvocationHandler {

    PersonBean person;


    public OwnerInvocationHandler(PersonBean person) {

        this.person = person;

    }


    @Override

    public Object invoke(Object proxy, Method method, Object[] args) throws IllegalAccessException {

        try {

            if (method.getName().startsWith("get")) {

                return method.invoke(person, args);

            } else if (method.getName().equals("setHotOrNotRating")) {

                throw new IllegalAccessException();

            } else if (method.getName().startsWith("set")) {

                return method.invoke(person, args);

            }

        } catch (InvocationTargetException e) {

            e.printStackTrace();

        }

        return null;

    }

}


NonOwnerInvocationHandler,其他人使用

import java.lang.reflect.InvocationHandler;

import java.lang.reflect.InvocationTargetException;

import java.lang.reflect.Method;


public class NonOwnerInvocationHandler implements InvocationHandler {

    PersonBean person;


    public NonOwnerInvocationHandler(PersonBean person) {

        this.person = person;

    }


    @Override

    public Object invoke(Object proxy, Method method, Object[] args) throws IllegalAccessException {

        try {

            if (method.getName().startsWith("get")) {

                return method.invoke(person, args);

            } else if (method.getName().equals("setHotOrNotRating")) {

                return method.invoke(person, args);

            } else if (method.getName().startsWith("set")) {

                throw new IllegalAccessException();

            }

        } catch (InvocationTargetException e) {

            e.printStackTrace();

        }

        return null;

    }

}


MatchMakingTestDrive,测试

import java.lang.reflect.Proxy;

public class MatchMakingTestDrive {

    public static void main(String[] args) {

        MatchMakingTestDrive test = new MatchMakingTestDrive();

        test.drive();

    }


    public void drive() {

        // 从数据库取出一个人

        PersonBean joe = getPersonFromDatabase("Joe Javabean");

        PersonBean ownerProxy = getOwnerProxy(joe);

        System.out.println("Name is " + ownerProxy.getName());

        ownerProxy.setInterests("bowling, Go");

        System.out.println("Interests set from owner proxy");

        try {

            ownerProxy.setHotOrNotRating(10);

        } catch (Exception e) {

            System.out.println("Can't set rating from owner proxy");

        }

        System.out.println("Rating is " + ownerProxy.getHotOrNotRating());


        PersonBean nonOwnerProxy = getNonOwnerProxy(joe);

        System.out.println("Name is " + nonOwnerProxy.getName());

        try {

            nonOwnerProxy.setInterests("bowling, Go");

        } catch (Exception e) {

            System.out.println("Can't set interests from non owner proxy");

        }

        nonOwnerProxy.setHotOrNotRating(3);

        System.out.println("Rating set from non owner proxy");

        System.out.println("Rating is " + nonOwnerProxy.getHotOrNotRating());

    }


    public PersonBean getPersonFromDatabase(String name) {

        return null;

    }


    public PersonBean getOwnerProxy(PersonBean person) {

        return (PersonBean) Proxy.newProxyInstance(

                person.getClass().getClassLoader(),

                person.getClass().getInterfaces(),

                new OwnerInvocationHandler(person)

        );

    }


    public PersonBean getNonOwnerProxy(PersonBean person) {

        return (PersonBean) Proxy.newProxyInstance(

                person.getClass().getClassLoader(),

                person.getClass().getInterfaces(),

                new NonOwnerInvocationHandler(person)

        );

    }

}



  • MVC(P528)
    Model-View-Controller

    视图:
        用来呈现模型。视图通常直接从模型中取得它需要显示的状态与数据。
    控制器:
        取得用户的输入并解读其对模型的意思。
    模型:
        模型持有所有的数据、状态和程序逻辑。模型没有注意到视图和控制器,虽然它提供了操纵和检索状态的接口,并发送状态改变通知给观察者。

    1.你是用户——你和视图交互。
        视图是模型的窗口。当你对视图做一些事时(比方说,按下“播放”按钮),视图就告诉控制器你做了什么。控制器会负责处理。 
    2.控制器要求模型改变状态。
        控制器解读你的动作,并告知模型如何做出对应的动作。 翻译你的动作给模型。
    3.控制器也可能要求视图做改变。
        比方说,控制器可以将界面上的某些按钮或菜单项变成有效或无效。
    4.当模型状态改变时,模型会通知视图。
    5.视图向模型询问状态。

    控制器——策略模式
    视图——组合模式
    模型——观察者模式

    模型不用知道控制器和视图,只需要利用观察者模式,在状态改变的时候通知观察者们。
    在MVC中,模型对视图和控制器一无所知。换句话说,它们之间是完全解耦的。模型只知道,有一些观察者它需要通知。模型还提供一些接口,供视图和控制器获得并设置状态。

    弯管机写得真垃圾,以后重写。
    M:管理数据和程序逻辑;
    C:图形交互和数据交互;
    V:图形界面

    Model 2
    Model 2是MVC在Web上的应用。
    在Model 2中,控制器实现成Servlet,而JSP/HTML实现视图。

  • 设计模式的描述(P585)
    模式的分类或类目:SINGLETON Object Creational
        类目中所有的模式都是以一个“名称”开始的,名称是模式中很重要的一部分。

    Intent 意图
        描述该模式是什么。

    Motivation 动机
        给出了问题以及如何解决这个问题的具体场景。

    Applicability 适用性
        描述模式可以被应用在什么场合。

    Structure 结构
        提供了图示,显示出参与此模式的类之间的关系。

    Participants 参与者
        描述在此设计中所涉及到的类和对象在模式中的责任和角色。

    Collaborations 协作
        告诉我们Participants(参与者)如何在此模式中合作。

    Consequences 结果
        描述采用此模式之后可能产生的效果:好的与不好的。

    Implementation/Sample Code 实现
        提供了你在实现该模式时需要使用的技巧,以及你应该小心面对的问题。

    Knonw Uses 已知应用
        用来描述以及在真实系统中发现的模式例子。

    Related Patterns 相关模式
        描述了与此模式和其他模式之间的关系。

内容概要:本文档介绍了基于3D FDTD(时域有限差分)方法在MATLAB平台上对微带线馈电的矩形天线进行仿真分析的技术方案,重点在于模拟超MATLAB基于3D FDTD的微带线馈矩形天线分析[用于模拟超宽带脉冲通过线馈矩形天线的传播,以计算微带结构的回波损耗参数]宽带脉冲信号通过天线结构的传播过程,并计算微带结构的回波损耗参数(S11),以评估天线的匹配性能和辐射特性。该方法通过建立三维电磁场模型,精确求解麦克斯韦方程组,适用于高频电磁仿真,能够有效分析天线在宽频带内的响应特性。文档还提及该资源属于一个涵盖多个科研方向的综合性MATLAB仿真资源包,涉及通信、信号处理、电力系统、机器学习等多个领域。; 适合人群:具备电磁场与微波技术基础知识,熟悉MATLAB编程及数值仿真的高校研究生、科研人员及通信工程领域技术人员。; 使用场景及目标:① 掌握3D FDTD方法在天线仿真中的具体实现流程;② 分析微带天线的回波损耗特性,优化天线设计参数以提升宽带匹配性能;③ 学习复杂电磁问题的数值建模与仿真技巧,拓展在射频与无线通信领域的研究能力。; 阅读建议:建议读者结合电磁理论基础,仔细理解FDTD算法的离散化过程和边界条件设置,运行并调试提供的MATLAB代码,通过调整天线几何尺寸和材料参数观察回波损耗曲线的变化,从而深入掌握仿真原理与工程应用方法。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值