java必知必会-设计模式(下)


3.3. 行为型模式

3.3.1. 模板方法模式

  • 模板方法模式(Template Method Pattern),在一个抽象类公开定义了执行它的方法的模板。它的子类可以按需要重写方法实现,但调用将以抽象类中定义的方式进行。
  • 模板方法模式 定义一个操作中的算法的骨架,而将一些步骤延迟到子类中,使得子类可以不改变一个算法的结构,就可以重定义该算法的某些特定步骤
  • 一般模板方法加上final关键字,防止子类重新模板方法
  • 当要完成某个过程。该过程要执行一系列步骤,这一系列的步骤基本相同,但其个别步骤在实现可能不同,考虑使用模板方法模式来处理
public class Client {
    public static void main(String[] args) {
        A a = new B(); a.make();
    }
}

abstract class A {
    //模板方法, make , 模板方法可以做成final , 不让子类去覆盖.
    final void make() {if(gouzi()){B();}C();}
    abstract void B();
    abstract void C();
    boolean gouzi(){return false;}//默认不做任何事,子类可以视情况要不要覆盖它,该方法称为“钩子”。
}

class B extends A{
    void B(){System.out.println("B实现了模板方法B");};
    void C(){System.out.println("B实现了模板方法C");};
    boolean gouzi(){return true;}
}

3.3.2. 命令模式

  • 命令模式:我们经常需要向某些对象发送请求,但是并不知道请求的接收者是谁,也不知道被请求的操作是哪个,只需在程序运行时指定具体的请求接收者即可
  • 命名模式通过命令对象使得请求发送者与请求接收者消除彼此之间的耦合,让对象之间的调用关系更加灵活,实现解耦。
  • 在命名模式中,会将一个请求封装为一个对象,以便使用不同参数来表示不同的请求(即命名),同时命令模式也支持可撤销的操作。
  • 容易设计一个命令队列。只要把命令对象放到列队,就可以多线程的执行命令;容易实现对请求的撤销和重做
  • 命令模式可能导致某些系统有过多的具体命令类,增加了系统的复杂度,这点在使用的时候要注意
  • 命令模式经典使用场景:界面的一个按钮都是一条命令、模拟CMD、订单的撤销/恢复、触发-反馈机制
//命令调用者集合了所有的命令,调用命令时实际调用接收者的方法,而命令可以选择调用不同的接受者方法
public class Client {
    public static void main(String[] args) {
        Receiver receiver = new Receiver();
        ConcreteCommand concreteCommand = new ConcreteCommand(receiver);
        Invoker invoker = new Invoker();
        invoker.takeCommand(concreteCommand);
        invoker.placeCommands();
    }
}

//命令模式 命令接口命令角色Command
interface Command {void execute();}

//请求实现类 命令实现类 依赖接收者 将一个接受者对象与一个动作绑定,调用接受者相应的动作,实现execute
class ConcreteCommand implements Command {
    private Receiver receiver;
    public ConcreteCommand(Receiver receiver){this.receiver = receiver;}
    public void execute() {receiver.buy();}
}

//请求类 接受者Receiver
class Receiver {
    public void buy(){}
}

//命令调用类:调用者角色
class Invoker {
    private List<Command> commandList = new ArrayList<Command>();
    public void takeCommand(Command command){commandList.add(command);}
    public void placeCommands(){
        for (Command command : commandList) {
            command.execute();
        }
    }
}

3.3.3. 访问者模式

  • 访问者模式(Visitor Pattern),封装一些作用于某种数据结构的各元素的操作,它可以在不改变数据结构的前提下定义作用于这些元素的新的操作。
  • 主要将数据结构与数据操作分离,解决 数据结构和操作耦合性问题
  • 访问者模式的基本工作原理是:在被访问的类里面加一个对外提供接待访问者的接口
  • 访问者模式主要应用场景是:需要对一个对象结构中的对象进行很多不同操作(这些操作彼此没有关联),同时需要避免让这些操作"污染"这些对象的类,可以选用访问者模式解决
  • 重载是静态绑定,重写是动态绑定,双分派把重写放在重载之前,以实现在运行时动态判断执行那个子类的方法。
  • 访问者模式可以对功能进行统一,可以做报表、UI、拦截器与过滤器,适用于数据结构相对稳定的系统
  • 因此,如果一个系统有比较稳定的数据结构,又有经常变化的功能需求,那么访问者模式就是比较合适的.
public class Client {
    public static void main(String[] args) {
        ObjectStructure objectStructure = new ObjectStructure();
        objectStructure.attach(new Man());
        objectStructure.attach(new Woman());

        //成功
        Success success = new Success();
        objectStructure.display(success);

        Fail fail = new Fail();
        objectStructure.display(fail);
    }
}

//抽象访问者 Visitor抽象访问者
abstract class Action {
    public abstract void getManResult(Man man);
    public abstract void getWomanResult(Woman woman);
}

//实际访问者  ConcreteVisitor具体访问者
class Success extends Action {
    public void getManResult(Man man) {System.out.println(" 男人给的评价该歌手很成功 !");}
    public void getWomanResult(Woman woman) {System.out.println(" 女人给的评价该歌手很成功 !");}
}

class Fail extends Action {
    public void getManResult(Man man) {System.out.println(" 男人给的评价该歌手失败 !");}
    public void getWomanResult(Woman woman) {System.out.println(" 女人给的评价该歌手失败 !");}
}

//抽象元素 接收一个访问者对象
abstract class Person {
    public abstract void accept(Action action);
}

//实际元素
//使用了双分派, 即
//首先在客户端程序中,将具体状态作为参数传递Woman中(第一次分派)
// 然后Woman 类调用作为参数的 "具体方法" 中方法getWomanResult, 同时将自己(this)作为参数传入,完成第二次的分派
//双分派是指不管类怎么变化,我们都能找到期望的方法运行。
//双分派意味着得到执行的操作取决于请求的种类和两个接收者的类型 -
//假设我们要添加一个Wait的状态类,只需增加一个Action子类即可在客户端调用即可,不需要改动任何其他类的代码
class Woman extends Person{
    public void accept(Action action) {action.getWomanResult(this);}
}

class Man extends Person {
    public void accept(Action action) {action.getManResult(this);}
}

//数据结构,管理很多人(Man , Woman)ObjectStructure
class ObjectStructure {
    private List<Person> persons = new LinkedList<>();
    public void attach(Person p) {persons.add(p);}
    public void detach(Person p) {persons.remove(p);}
    public void display(Action action) {
        for(Person p: persons) {
            p.accept(action);
        }
    }
}

3.3.4. 迭代器模式

  • 如果我们的集合元素是用不同的方式实现的,有数组,还有java的集合类,或者还有其他方式,当客户端要遍历这些集合元素的时候就要使用多种遍历方式,而且还会暴露元素的内部结构,可以考虑使用迭代器模式解决。
  • 迭代器模式,提供一种遍历集合元素的统一接口,用一致的方法遍历集合元素,不需要知道集合对象的底层表示,即:不暴露其内部的结构。
  • 提供了一种设计思想,就是一个类应该只有一个引起变化的原因(叫做单一责任原则)。在聚合类中,我们把迭代器分开,就是要把管理对象集合和遍历对象集合的责任分开,这样一来集合改变的话,只影响到聚合对象。而如果遍历方式改变的话,只影响到了迭代器。
  • 当要展示一组相似对象,或者遍历一组相同对象时使用, 适合使用迭代器模式
    -每个聚合对象都要一个迭代器,会生成多个迭代器不好管理类
public class Client {
    public static void main(String[] args) {
        ConcreteAggregate aggregate = new ConcreteAggregate();
        Iterator iterator = aggregate.createIterator();
        while(iterator.hasNext()) {
            Element e = (Element)iterator.next();
            System.out.println(e.getName());
        }
    }
}

//需要被迭代的对象
class Element {
    private String name;//set/get
    public Element(String name) {this.name = name;}
    public String getName() { return name; }
}
//aggregate接口
interface Aggregate {
    Iterator createIterator();
}

//aggregate接口实现
class ConcreteAggregate implements Aggregate {
    Element[] elements;
    public ConcreteAggregate() {elements = new Element[]{new Element("test1"),new Element("test2")};}
    @Override
    public Iterator createIterator() {return new ConcreteIterator(elements);}
}

//迭代器实现
class ConcreteIterator implements Iterator {
    Element[] elements;
    int position = 0; //遍历的位置
    public ConcreteIterator(Element[] elements) {this.elements = elements;}
    //判断是否还有下一个元素
    @Override
    public boolean hasNext() {
        if(position >= elements.length || elements[position] == null) {
            return false;
        }else {
            return true;
        }
    }
    @Override
    public Object next() {
        Element element = elements[position];
        position += 1;
        return element;
    }
}

3.3.5. 观察者模式

public class Client {
    public static void main(String[] args) {
        Subject subject = new Subject();
        new BinaryObserver(subject);
        subject.setState(15);
    }
}

//通知者
class Subject {
    private List<Observer> observers = new ArrayList<Observer>();
    private int state;
    public int getState() {return state;}
    //通知核心
    public void setState(int state) {
        this.state = state;
        notifyAllObservers();
    }
    public void attach(Observer observer){observers.add(observer);}
    public void notifyAllObservers(){
        for (Observer observer : observers) {
            observer.update();
        }
    }
}

//观察者抽象
abstract class Observer {
    protected Subject subject;
    public abstract void update();
}

//观察者
class BinaryObserver extends Observer{
    public BinaryObserver(Subject subject){
        this.subject = subject;
        this.subject.attach(this);
    }
    @Override
    public void update() {
        System.out.println( "Binary String: " + Integer.toBinaryString( subject.getState() ) );
    }
}

3.3.6. 中介者模式

  • 中介者模式(Mediator Pattern),用一个中介对象来封装一系列的对象交互。中介者使各个对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互
  • 比如MVC模式,C(Controller控制器)是M(Model模型)和V(View视图)的中介者,在前后端交互时起到了中间人的作用
  • 多个类相互耦合,会形成网状结构, 使用中介者模式将网状结构分离为星型结构,进行解耦
  • 中介者承担了较多的责任,一旦中介者出现了问题,整个系统就会受到影响
  • 如果设计不当,中介者对象本身变得过于复杂,这点在实际使用时,要特别注意
public class Client {
    public static void main(String[] args) {
        Mediator mediator = new ConcreteMediator();
        ConcreteColleague concreteColleague = new ConcreteColleague(mediator, "alarm");
        concreteColleague.SendMessage(0);
    }
}

//同事抽象类
abstract class Colleague {
    private Mediator mediator;
    public String name;
    public Colleague(Mediator mediator, String name) {this.mediator = mediator;this.name = name;mediator.Register(name, this);}
    public Mediator GetMediator() {return this.mediator;}
    public abstract void SendMessage(int stateChange);
    public Colleague(){}

}

//具体同事类
class ConcreteColleague extends Colleague {
    public ConcreteColleague(Mediator mediator, String alarm) {
        super(mediator,alarm);
    }
    @Override
    public void SendMessage(int stateChange) {this.GetMediator().GetMessage(stateChange, this.name);}
    public void StartCoffee() {System.out.println("It's time to startcoffee!");}
}

//抽象中介者
abstract class Mediator {
    public abstract void Register(String colleagueName, Colleague colleague);//将给中介者对象,加入到集合中
    public abstract void GetMessage(int stateChange, String colleagueName);//接收消息, 具体的同事对象发出
}

//具体的中介者类
class ConcreteMediator extends Mediator {
    private HashMap<String, Colleague> colleagueMap = new HashMap<String, Colleague>();//集合,放入所有的同事对象
    private HashMap<String, String> interMap = new HashMap<String, String>();
    public ConcreteMediator() {}

    @Override
    public void Register(String colleagueName, Colleague colleague) {
        colleagueMap.put(colleagueName, colleague);
        if (colleague instanceof ConcreteColleague) {interMap.put("ConcreteColleague", colleagueName);}
    }

    //具体中介者的核心方法
    //1. 根据得到消息,完成对应任务
    //2. 中介者在这个方法,协调各个具体的同事对象,完成任务
    @Override
    public void GetMessage(int stateChange, String colleagueName) {
        if (colleagueMap.get(colleagueName) instanceof ConcreteColleague) {
            if (stateChange == 0) {((ConcreteColleague) (colleagueMap.get(interMap.get("ConcreteColleague")))).StartCoffee();}
        }
    }
}

3.3.7. 解释器模式

  • 在编译原理中,一个算术表达式通过词法分析器形成词法单元,而后这些词法单元再通过语法分析器构建语法分析树,最终形成一颗抽象的语法分析树。这里的词法分析器和语法分析器都可以看做是解释器
  • 解释器模式(Interpreter Pattern):是指给定一个语言(表达式),定义它的文法的一种表示,并定义一个解释器,使用该解释器来解释语言中的句子(表达式)
  • 应用场景:可以将一个需要解释执行的语言中的句子表示为一个抽象语法树 ;一些重复出现的问题可以用一种简单的语言来表达 一个简单语法需要解释的场景;这样的例子还有,比如编译器、运算表达式计算、正则表达式、机器人等
  • 当有一个语言需要解释执行,可将该语言中的句子表示为一个抽象语法树,就可以考虑使用解释器模式,让程序具有良好的扩展性
  • 使用解释器可能带来的问题:解释器模式会引起类膨胀、解释器模式采用递归调用方法,将会导致调试非常复杂、效率可能降低.
//表达式接口
public interface Expression {
    public boolean interpret(String context);
}

//表达式实现
public class TerminalExpression implements Expression {
    private String data;
    public TerminalExpression(String data){this.data = data; }
    @Override
    public boolean interpret(String context) {
        if(context.contains(data)){return true;}
        return false;
    }
}

public class OrExpression implements Expression {
    private Expression expr1 = null;
    private Expression expr2 = null;
    public OrExpression(Expression expr1, Expression expr2) { 
        this.expr1 = expr1;
        this.expr2 = expr2;
    }
    @Override
    public boolean interpret(String context) {      
        return expr1.interpret(context) || expr2.interpret(context);
    }
}

public class AndExpression implements Expression {
    private Expression expr1 = null;
    private Expression expr2 = null;
    public AndExpression(Expression expr1, Expression expr2) { 
        this.expr1 = expr1;
        this.expr2 = expr2;
    }
    @Override
    public boolean interpret(String context) {      
        return expr1.interpret(context) && expr2.interpret(context);
    }
}

public class client {
    //规则:Robert 和 John 是男性
    public static Expression getMaleExpression(){
        Expression robert = new TerminalExpression("Robert");
        Expression john = new TerminalExpression("John");
        return new OrExpression(robert, john);    
    }
    //规则:Julie 是一个已婚的女性
    public static Expression getMarriedWomanExpression(){
        Expression julie = new TerminalExpression("Julie");
        Expression married = new TerminalExpression("Married");
        return new AndExpression(julie, married);    
    }
    public static void main(String[] args) {
        Expression isMale = getMaleExpression();
        Expression isMarriedWoman = getMarriedWomanExpression();
        System.out.println("John is male? " + isMale.interpret("John"));
        System.out.println("Julie is a married women? " + isMarriedWoman.interpret("Married Julie"));
    }
}

3.3.8. 备忘录模式

  • 备忘录模式(Memento Pattern)在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到原先保存的状态
  • 备忘录对象主要用来记录一个对象的某种状态,或者某些数据,当要做回退时,可以从备忘录对象里获取原来的数据进行恢复操作
  • 给用户提供了一种可以恢复状态的机制,可以使用户能够比较方便地回到某个历史的状态
  • 如果类的成员变量过多,势必会占用比较大的资源,而且每一次保存都会消耗一定的内存, 这个需要注意
  • 适用的应用场景:1、后悔药。 2、打游戏时的存档。 3、Windows 里的 ctri + z。 4、IE 中的后退。 4、数据库的事务管理
  • 为了节约内存,备忘录模式可以和原型模式配合使用
public class Client {
    public static void main(String[] args) {
        Originator originator = new Originator();
        Caretaker caretaker = new Caretaker();
        originator.setState(" 状态#1 攻击力 100 ");
        //保存了当前的状态
        caretaker.add(originator.saveStateMemento());
        originator.setState(" 状态#2 攻击力 80 ");
        caretaker.add(originator.saveStateMemento());
        originator.setState(" 状态#3 攻击力 50 ");
        caretaker.add(originator.saveStateMemento());
        System.out.println("当前的状态是 =" + originator.getState());
        //希望得到状态 1, 将 originator 恢复到状态1
        originator.getStateFromMemento(caretaker.get(0));
        System.out.println("恢复到状态1 , 当前的状态是");
        System.out.println("当前的状态是 =" + originator.getState());
    }
}

//备忘录对象
class Memento {
    private String state;
    public Memento(String state) {this.state = state;}
    public String getState() {return state;}
}

//守护者对象,负责保存多个备忘录对象
class Caretaker {
    //在List 集合中会有很多的备忘录对象
    private List<Memento> mementoList = new ArrayList<Memento>();
    public void add(Memento memento) {mementoList.add(memento);}
    //获取到第index个Originator 的 备忘录对象(即保存状态)
    public Memento get(int index) {return mementoList.get(index);}
}

//originator : 对象(需要保存状态的对象)
class Originator {
    private String state;//状态信息
    public String getState() {return state;}
    public void setState(String state) {this.state = state;}
    //编写一个方法,可以保存一个状态对象 Memento
    //因此编写一个方法,返回 Memento
    public Memento saveStateMemento() {return new Memento(state);}
    //通过备忘录对象,恢复状态(将自己的属性转成传入来的属性)
    public void getStateFromMemento(Memento memento) {state = memento.getState();}
}

3.3.9. 状态模式

  • 状态模式(State Pattern):它主要用来解决对象在多种状态转换时,需要对外输出不同的行为的问题。状态和行为是一一对应的,状态之间可以相互转换
  • 当一个对象的内在状态改变时,允许改变其行为,这个对象看起来像是改变了其类
  • 方便维护。将容易产生问题的if-else语句删除了,如果把每个状态的行为都放到一个类中,每次调用方法时都要判断当前是什么状态,不但会产出很多if-else语句,而且容易出错,容易增删状态
  • 会产生很多类。每个状态都要一个对应的类,当状态过多时会产生很多类,加大维护难度
  • 应用场景:当一个事件或者对象有很多种状态,状态之间会相互转换,对不同的状态要求有不同的行为的时候,可以考虑使用状态模式
public class Client {
    public static void main(String[] args) {
        Context context = new Context();
        StartState startState = new StartState();
        startState.doAction(context);
        System.out.println(context.getState().toString());
        StopState stopState = new StopState();
        stopState.doAction(context);
        System.out.println(context.getState().toString());
    }
}

//状态接口
interface State {
    void doAction(Context context);
}

//状态实现
class StartState implements State {
    public void doAction(Context context) {
        System.out.println("Player is in start state");
        context.setState(this);
    }
    public String toString(){return "Start State";}
}

class StopState implements State {
    public void doAction(Context context) {
        System.out.println("Player is in stop state");
        context.setState(this);
    }
    public String toString(){return "Stop State";}
}

//context
class Context {
    private State state;
    public void setState(State state){
        this.state = state;
    }
    public State getState(){
        return state;
    }
}
//将接口实现类放入到传入参数context的接口属性中,调用传入参数context可获得接口实现类及调用其方法

3.3.10. 策略模式

  • 策略模式(Strategy Pattern)中定义算法族,分别封装起来,让他们之间可以互相替换,此模式让算法的变化独立于使用算法的客户分别封装行为接口,实现算法族,超类里放行为接口对象,在子类里具体设定行为对象。原则就是:分离变化部分,封装接口,基于接口编程各种功能。此模式让行为的变化独立于算法的使用者
  • 策略模式的关键是:分析项目中变化部分与不变部分
  • 策略模式的核心思想是:多用组合/聚合 少用继承;用行为类组合,而不是行为的继承。更有弹性
  • 提供了可以替换继承关系的办法: 策略模式将算法封装在独立的Strategy类中使得你可以独立于其Context改变它,使它易于切换、易于理解、易于扩展
  • 需要注意的是:每添加一个策略就要增加一个类,当策略过多是会导致类数目庞大
public class Client {
    public static void main(String[] args) {
        Context context = new Context(new OperationAdd());    
        System.out.println("10 + 5 = " + context.executeStrategy(10, 5));
        context = new Context(new OperationSubstract());      
        System.out.println("10 - 5 = " + context.executeStrategy(10, 5));
    }
}
//策略接口
interface Strategy {
    public int doOperation(int num1, int num2);
}
//策略实现
class OperationAdd implements Strategy{
    @Override
    public int doOperation(int num1, int num2) {
        return num1 + num2;
    }
}
class OperationSubstract implements Strategy{
    @Override
    public int doOperation(int num1, int num2) {
        return num1 - num2;
    }
}
//context
class Context {
    private Strategy strategy;
    public Context(Strategy strategy){
        this.strategy = strategy;
    }
    public int executeStrategy(int num1, int num2){
        return strategy.doOperation(num1, num2);
    }
}

3.3.11. 职责链模式

  • 职责链模式Chain of Responsibility Pattern责任链模式,为请求创建了一个接收者对象的链对请求的发送者和接收者进行解耦。
  • 职责链模式通常每个接收者都包含对另一个接收者的引用。如果一个对象不能处理该请求,那么它会把相同的请求传给下一个接收者,依此类推。
  • 性能会受到影响,特别是在链比较长的时候,因此需控制链中最大节点数量,一般通过在Handler中设置一个最大节点数量,在setNext()方法中判断是否已经超过阀值,超过则不允许该链建立,避免出现超长链无意识地破坏系统性能
  • 最佳应用场景:有多个对象可以处理同一个请求时,比如:多级请求、请假/加薪等审批流程、Java Web中Tomcat对Encoding的处理、拦截器
public class Client {
    public static void main(String[] args) {

        DepartmentApprover departmentApprover = new DepartmentApprover("张主任");//创建相关的审批人
        CollegeApprover collegeApprover = new CollegeApprover("李院长");
        SchoolMasterApprover schoolMasterApprover = new SchoolMasterApprover("佟校长");

        departmentApprover.setApprover(collegeApprover);//需要将各个审批级别的下一个设置好 (处理人构成环形: )
        collegeApprover.setApprover(schoolMasterApprover);
        schoolMasterApprover.setApprover(departmentApprover);

        departmentApprover.processRequest(new PurchaseRequest(31000, 1));
        collegeApprover.processRequest(new PurchaseRequest(200, 2));
    }
}

//处理者抽象
abstract class Approver {
    Approver approver;  //下一个处理者
    String name; // 名字
    public Approver(String name) {this.name = name;}
    public void setApprover(Approver approver) {this.approver = approver;}
    //处理审批请求的方法,得到一个请求, 处理是子类完成,因此该方法做成抽象
    public abstract void processRequest(PurchaseRequest purchaseRequest);
}

//具体处理者
class CollegeApprover extends Approver {
    public CollegeApprover(String name) {super(name);}
    @Override
    public void processRequest(PurchaseRequest purchaseRequest) {
        if(purchaseRequest.getPrice() > 5000 && purchaseRequest.getPrice() <= 10000) {
            System.out.println(" 请求编号 id= " + purchaseRequest.getId() + " 被 " + this.name + " 处理");
        }else {
            approver.processRequest(purchaseRequest);
        }
    }
}

class DepartmentApprover extends Approver {
    public DepartmentApprover(String name) {super(name);}
    @Override
    public void processRequest(PurchaseRequest purchaseRequest) {
        if(purchaseRequest.getPrice() <= 5000) {
            System.out.println(" 请求编号 id= " + purchaseRequest.getId() + " 被 " + this.name + " 处理");
        }else {
            approver.processRequest(purchaseRequest);
        }
    }
}

class SchoolMasterApprover extends Approver {
    public SchoolMasterApprover(String name) {super(name);}
    @Override
    public void processRequest(PurchaseRequest purchaseRequest) {
        if(purchaseRequest.getPrice() > 10000) {
            System.out.println(" 请求编号 id= " + purchaseRequest.getId() + " 被 " + this.name + " 处理");
        }else {
            approver.processRequest(purchaseRequest);
        }
    }
}

//请求类
class PurchaseRequest {
    private float price = 0.0f; //请求金额
    private int id = 0;
    public PurchaseRequest(float price, int id) {
        this.price = price;
        this.id = id;
    }
    public float getPrice() {
        return price;
    }
    public int getId() {
        return id;
    }
}

总结

本文介绍了设计模式的使用,如有问题欢迎私信和评论

  • 26
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

编程岁月

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

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

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

打赏作者

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

抵扣说明:

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

余额充值