设计模式(二)——建造者、装饰器、模板设计、适配器、策略、代理、原型、组合、观察者模式

 

目录

1、建造者模式

2、装饰器模式

3、模板设计模式

4、适配器模式

5、策略模式

6、代理模式

7、原型模式

8、组合模式

9、观察者模式


1、建造者模式

产品,省略get、set方法

class Computer {
    private String cpu;
    private String gpu;
    private String memory;
    private String hd;
}

建造者:

当需要扩展时,只需继承ComputerBuilder 接口,并实现相应方法。

interface ComputerBuilder {
    void setCpu();
    void setGpu();
    void seMemery();
    void setHd();
    Computer build();
}
// 高品质电脑
class AdvancedComputerBuilder implements ComputerBuilder {
    private Computer computer = new Computer();
    @Override
    public void setCpu() {
        computer.setCpu("i7 9700K");
    }
    @Override
    public void setGpu() {
        computer.setGpu("GTX3090");
    }
    @Override
    public void seMemery() {
        computer.setMemory("32G");
    }
    @Override
    public void setHd() {
        computer.setHd("500G固态+2T机械");
    }
    public Computer build() {
        return computer;
    }
}

指挥者:

class Director {
    public Computer build(ComputerBuilder cb) {
        cb.setCpu();
        cb.setGpu();
        cb.seMemery();
        cb.setHd();
        return cb.build();
    }
}

调用:

public class Test {
    public static void main(String[] args) {
        AdvancedComputerBuilder acb = new AdvancedComputerBuilder();
        Director director = new Director();
        Computer computer = director.build(acb);
        System.out.println(computer);
    }
}

优点:

1)创建对象的过程稳定不变的(因为有ComputerBuilder接口来稳定过程)

2)创建对象的过程只写了一次,没有重复代码(由指挥者完成)

3)当需要扩展指挥者的时候,不用修改之前的代码

2、装饰器模式

业务需求:

业务场景:卖咖啡(四种咖啡)
Decaf Espresso DrakRoast HouseBlend
因为所有咖啡都有共性,因为把共性提到一个父类:Beverage
abstract class Beverage {
    private String description;
    public Beverage(String description) {
        this.description = description;
    }
    public abstract double cost();
    public String getDescription() {
        return description;
    }
    public void setDescription(String description) {
        this.description = description;
    }
}
class Decaf extends Beverage {
    public Decaf() {
        super("无咖啡因咖啡");
    }
    @Override
    public double cost() {
        return 1;
    }
}
class Espresso extends Beverage {
    public Espresso() {
        super("浓缩咖啡");
    }
    @Override
    public double cost() {
        return 2;
    }
}
class DrakRoast extends Beverage {
    public DrakRoast() {
        super("焦炒咖啡");
    }
    @Override
    public double cost() {
        return 1.5;
    }
}
class HouseBlend extends Beverage {
    public HouseBlend() {
        super("混合咖啡");
    }
    @Override
    public double cost() {
        return 3;
    }
}

 问题分析:

如果要在咖啡中填加牛奶、摩卡等时,按这种设计就需要添加相应的类:

为Decaf加牛奶:class DecafWithMilk

为Espresso加牛奶:class Espresso

......

这样添加下去就会导致类大量增加,这就无法适应新的扩展。

采用装饰器模式:

// 装饰器
abstract class Condiment extends Beverage {
    protected Beverage beverage;
    public Condiment(Beverage beverage) {
        super("调料");
        this.beverage = beverage;
    }
}
class Milk extends Condiment {
    public Milk(Beverage beverage) {
        super(beverage);
    }
    @Override
    public double cost() {
        return beverage.cost() + 0.2;
    }
    @Override
    public String getDescription() {
        String msg = beverage.getDescription() + " 牛奶";
        return msg;
    }
}
class Soy extends Condiment {
    public Soy(Beverage beverage) {
        super(beverage);
    }
    @Override
    public double cost() {
        return beverage.cost() + 0.1;
    }
    @Override
    public String getDescription() {
        return beverage.getDescription() + " 豆浆";
    }
}

UML类图:

优点:

1)扩展功能时,不会违背开闭原则,只需增加一个装饰器实现就行。

缺点:

1)装饰器模式虽然减少了类数量,但仍然会增加很多具体的小类,导致不够直观清晰

2)使用时可能需要更多的对象来表示继承关系中的一个对象(如:上述例子需要添加牛奶、豆浆,需要这两个对象都要实现)

3)多层装饰比较复杂,如排查问题不宜发现问题所在 

3、模板设计模式

模板模式在一个方法中定义一个业务的骨架,其中一些具体逻辑实现由子类进行实现。

非模板模式代码:

    public static void main(String[] args) {
        System.out.println("程序执行开始。。。");
        long startTime = System.currentTimeMillis();
        LinkedList<Integer> linkedList = new LinkedList<>();
        for (int i = 0; i < 1000000; i++) {
            linkedList.add(0, i);
        }
        long endTime = System.currentTimeMillis();
        System.out.println("程序执行用时:" + (endTime - startTime) + "ms");
        System.out.println("程序执行结束。。。");
    }

 模板模式:

abstract class Template {
    public void template() {
        System.out.println("程序执行开始。。。");
        long startTime = System.currentTimeMillis();
        code();
        long endTime = System.currentTimeMillis();
        System.out.println("程序执行用时:" + (endTime - startTime) + "ms");
        System.out.println("程序执行结束。。。");
    }

    protected abstract void code();
}

class TestA extends Template {
    @Override
    protected void code() {
        LinkedList<Integer> linkedList = new LinkedList<>();
        for (int i = 0; i < 1000000; i++) {
            linkedList.add(0, i);
        }
    }
}

4、适配器模式

一个类的接口转换成客户希望的另一个接口。是配置模式让那些不兼容的类可以在一起工作。

设置一个简单的业务逻辑来讲解适配器模式:

class Calc {
    public int add(int a, int b) {
        return a + b;
    }
}

此类用来求两个数的和,这时需求修改,需要求三个数的和。使用适配器模式:

class Calc {
    public int add(int a, int b) {
        return a + b;
    }
}
// 适配器
class CalcAdapter {
    private Calc calc;
    public CalcAdapter(Calc c) {
        calc = c;
    }
    public int add(int a, int b, int c) {
        return calc.add(calc.add(a, b), c);
    }
}
public class Test {
    public static void main(String[] args) {
        Calc c = new Calc();
        CalcAdapter calcAdapter = new CalcAdapter(c);
        calcAdapter.add(1, 2, 3);
    }
}

5、策略模式

例:

abstract class Duck {
    public void quack() {
        System.out.println("叫两声");
    }
    public void swim() {
        System.out.println("游泳");
    }
    public abstract void display();
}
class MallardDuck extends Duck {
    @Override
    public void display() {
        System.out.println("绿头鸭子");
    }
}
class RubberDuck extends Duck {
    @Override
    public void quack() {
        System.out.println("捏两下才叫");
    }

    @Override
    public void display() {
        System.out.println("外观是橡皮鸭");
    }
}

若需要增加一个fly方法,最简单方式就是在Duck添加一个fly方法,但这样就会导致Duck的子类都具备了fly方法,但橡皮鸭RubberDuck子类不应有该方法,故这样直接设计有问题。

针对上述问题进行优化:

interface Flyable {
    void fly();
}
interface Quackable{
    void quack();
}
abstract class Duck {
    public void swim() {
        System.out.println("游泳");
    }
    public abstract void display();
}
// 绿头鸭会叫并会飞
class MallardDuck extends Duck implements Flyable, Quackable {
    @Override
    public void display() {
        System.out.println("绿头鸭子");
    }
    @Override
    public void fly() {
        System.out.println("会飞");
    }
    @Override
    public void quack() {
        System.out.println("叫两声");
    }
}
// 橡皮鸭被捏才会叫且不会飞
class RubberDuck extends Duck implements Quackable {
    @Override
    public void quack() {
        System.out.println("捏两下才叫");
    }
    @Override
    public void display() {
        System.out.println("外观是橡皮鸭");
    }
}

将fly与quack设计为接口,需要实现该方法时再实现该接口。但仍然有个问题,新加一个鸭子角色时都要进行判断是否具有某些功能,且代码没有重复性可言,比如都有fly方法那就要都实现fly,导致这类代码过于臃肿。

采用策略模式,实现运行时功能修改: 

// 飞动作的接口
interface FlyBehavior {
    void fly();
}
interface QuackBehavior{
    void quack();
}
// 实现类
class FlyWithWings implements FlyBehavior {
    @Override
    public void fly() {
        System.out.println("用翅膀飞");
    }
}
class FlyNoWay implements FlyBehavior {
    @Override
    public void fly() {
        System.out.println("不会飞");
    }
}
class FlyWithKick implements FlyBehavior {
    @Override
    public void fly() {
        System.out.println("踢飞");
    }
}
class Quack implements QuackBehavior {
    @Override
    public void quack() {
        System.out.println("嘎嘎叫");
    }
}
// 鸭子抽象类
abstract class Duck {
    protected FlyBehavior fb;
    protected QuackBehavior qb;
    public FlyBehavior getFb() {
        return fb;
    }
    public void setFb(FlyBehavior fb) {
        this.fb = fb;
    }
    public QuackBehavior getQb() {
        return qb;
    }
    public void setQb(QuackBehavior qb) {
        this.qb = qb;
    }
    public void swim() {
        System.out.println("游泳");
    }
    public void performFly() {
        fb.fly();
    }
    public void performQuack() {
        qb.quack();
    }
    public abstract void display();
}
// 绿头鸭会叫并会飞
class MallardDuck extends Duck {
    public MallardDuck() {
        this.fb = new FlyNoWay();
        this.qb = new Quack();
    }
    @Override
    public void display() {
        System.out.println("绿头鸭子");
    }
}
// 橡皮鸭被捏才会叫且不会飞
class RubberDuck extends Duck {
    public RubberDuck() {
        this.fb = new FlyWithWings();
        this.qb = new Quack();
    }
    @Override
    public void display() {
        System.out.println("外观是橡皮鸭");
    }
}
public class Test {
    public static void main(String[] args) {
        RubberDuck rd = new RubberDuck();
        rd.performQuack();
        rd.swim();
        rd.display();
        rd.performFly();
        // 运行时修改飞行动作
        rd.setFb(new FlyWithKick());
        rd.performFly();
    }
}

6、代理模式

动态代理简单案例:

interface ICalc {
    int add(int a, int b);
    int sub(int a, int b);
    int mul(int a, int b);
    int div(int a, int b);
}
class CalcImpl implements ICalc {

    @Override
    public int add(int a, int b) {
        System.out.println("+");
        return a + b;
    }

    @Override
    public int sub(int a, int b) {
        System.out.println("-");
        return a - b;
    }

    @Override
    public int mul(int a, int b) {
        System.out.println("*");
        return a * b;
    }

    @Override
    public int div(int a, int b) {
        System.out.println("/");
        return a / b;
    }
}

class MyHandler implements InvocationHandler {

    private Object target;
    public MyHandler(Object target) {
        this.target = target;
    }

    /**
     * 动态代理执行的方法
     * @param proxy 动态代理的对象
     * @param method 调用的接口方法
     * @param args 调用的接口方法的参数
     * @return
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println(method.getName() + " 开始,参数是" + Arrays.toString(args));
        Object result = method.invoke(target, args);
        System.out.println(method.getName() + " 结束,结果是" + result);

        return 0;
    }
}
public class Test {
    public static void main(String[] args) {
        ClassLoader cl = Test.class.getClassLoader();
        ICalc proxy = (ICalc) Proxy.newProxyInstance(cl, new Class[]{ICalc.class}, new MyHandler(new CalcImpl()));
        proxy.add(1, 2);
    }
}

7、原型模式

使用原型模式:
  1. 必须让目标类实现Cloneable接口,改接口没有任何抽象方法,仅仅是一个标记接口。表示实现该接口的类可以被克隆必须重写
  2. java.long.Object类的clone方法,将返回权限改为public
class WeekReport implements Cloneable, Serializable {
    private int id;
    private String emp;
    private String summary;
    private String plain;
    private String suggestion;
    private Date time;

    ...
    get、set方法
    ...
    toString方法
    ...
    

    @Override
    public Object clone() throws CloneNotSupportedException {
        try {
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(out);
            oos.writeObject(this);
            oos.close();
            // 从内存中读取数据
            byte[] bytes = out.toByteArray();
            InputStream in = new ByteArrayInputStream(bytes);
            ObjectInputStream ois = new ObjectInputStream(in);
            Object clone = ois.readObject();
            ois.close();
            return clone;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

8、组合模式

要求:以层级的方式显示各个菜单项,并能满足功能的扩展。

// 菜单项
class MenuItem {

    private String name;
    private String description;

    public MenuItem(String name, String description) {
        this.name = name;
        this.description = description;
    }

    public String getName() {
        return name;
    }

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

    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }

    public void print(String prefix) {
        System.out.println(prefix + getName() + ":" + getDescription());
    }
}
// 菜单
class Menu {
    private List<MenuItem> list;

    private String name;
    private String description;

    public Menu(String name, String description) {
        this.name = name;
        this.description = description;
    }

    public String getName() {
        return name;
    }

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

    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }

    public void add(MenuItem mc) {
        list.add(mc);
    }

    public void print(String prefix) {
        System.out.println(prefix + " (" + getName() + ") " + getDescription());
        Iterator<MenuItem> iterator = list.iterator();
        while (iterator.hasNext()) {
            MenuItem menuItem = iterator.next();
            menuItem.print("\t" + prefix);
        }
    }

}

上述代码存在问题:

  1. 代码冗余,重复代码太多
  2. 该菜单只能添加菜单子项不能添加菜单,无法做到二级及以上菜单
  3. 无法显示层级,没解耦功能扩展代价太大

通过抽取重复项以及递归方式优化代码:

// 菜单组件,用户抽取菜单和菜单单项的共性
abstract class MenuComponent {
    private String name;
    private String description;
    public MenuComponent(String name, String description) {
        this.name = name;
        this.description = description;
    }

    public String getName() {
        return name;
    }

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

    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }

    public abstract void print(String prefix);
}
// 菜单项
class MenuItem extends MenuComponent {

    public MenuItem(String name, String description) {
        super(name, description);
    }
    public void print(String prefix) {
        System.out.println(prefix + getName() + ":" + getDescription());
    }
}
// 菜单
class Menu extends MenuComponent {
    private List<MenuComponent> list;
    public Menu(String name, String description) {
        super(name, description);
        list = new ArrayList<>();
    }
    public void add(MenuComponent mc) {
        list.add(mc);
    }
    public void print(String prefix) {
        System.out.println(prefix + " (" + getName() + ") " + getDescription());
        Iterator<MenuComponent> iterator = list.iterator();
        while (iterator.hasNext()) {
            MenuComponent menuComponent = iterator.next();
            menuComponent.print("\t" + prefix);
        }
    }

}

分析:看似完成了代码的抽取以及层级的显示,但这时需求来了,需要添加一个功能,增加价格以及该菜品是否为荤素。如果直接在源代码中修改就违反了开闭原则,故这种设计也存在缺陷。(注:不要说在设计之前就把这些需求封装好,没有人能把所有情况考虑完毕,现在是加价格,下次如果加甜辣程度呢?故这种修改只能在客户端也就是用户这边进行修改)

解决方案:

// 为保证菜单和菜单项具有相同接口,在它们的父类这里把所需的方法都定义出来
abstract class MenuComponent {
    private String name;
    private String description;
    public MenuComponent(String name, String description) {
        this.name = name;
        this.description = description;
    }

    public String getName() {
        return name;
    }

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

    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }

    public abstract void print(String prefix);
    public List getList() {
        throw new UnsupportedOperationException();
    }

    // 这些方法对于菜单和菜单项而言都只有一些有意义的,为什么非要定义在父类中?
    // 为了:组合模式使得用户对单个对象和组合对象的使用具有一致性
    // 属于菜单的方法
    public void add(MenuComponent mc) {
        throw new UnsupportedOperationException();
    }
    public void remove(MenuComponent mc) {
        throw new UnsupportedOperationException();
    }
    public MenuComponent getChild(int i) {
        throw new UnsupportedOperationException();
    }
    // 属于菜单项的方法
    public double getPrice() {
        throw new UnsupportedOperationException();
    }
    public boolean isVegetarian() {
        throw new UnsupportedOperationException();
    }

    public CompositeIterator iterator() {
        throw new UnsupportedOperationException();
    }
}
// 菜单项
class MenuItem extends MenuComponent {
    private boolean vegetarian;
    private double price;
    @Override
    public boolean isVegetarian() {
        return vegetarian;
    }
    public void setVegetarian(boolean vegetarian) {
        this.vegetarian = vegetarian;
    }
    @Override
    public double getPrice() {
        return price;
    }
    public void setPrice(double price) {
        this.price = price;
    }

    public MenuItem(String name, String description, boolean vegetarian, double price) {
        super(name, description);
        this.vegetarian = vegetarian;
        this.price = price;
    }
    public void print(String prefix) {
        String str = vegetarian ? "(素食)" : "";
        System.out.println(prefix + getName() + str + ":" + getDescription());
    }
}
// 菜单
class Menu extends MenuComponent {
    private List<MenuComponent> list;
    public Menu(String name, String description) {
        super(name, description);
        list = new ArrayList<>();
    }

    public List<MenuComponent> getList() {
        return list;
    }

    public void add(MenuComponent mc) {
        list.add(mc);
    }

    @Override
    public void remove(MenuComponent mc) {
        list.remove(mc);
    }

    @Override
    public MenuComponent getChild(int i) {
        return list.get(i);
    }

    public void print(String prefix) {
        System.out.println(prefix + " (" + getName() + ") " + getDescription());
        Iterator<MenuComponent> iterator = list.iterator();
        while (iterator.hasNext()) {
            MenuComponent menuComponent = iterator.next();
            menuComponent.print("\t" + prefix);
        }
    }
    public CompositeIterator iterator() {
        return new CompositeIterator(list.iterator());
    }
}
// 迭代器模式
class CompositeIterator implements Iterator<MenuComponent> {
    private Stack<Iterator<MenuComponent>> s = new Stack<>();
    public CompositeIterator(Iterator<MenuComponent> it) {
        s.push(it);
    }
    @Override
    public boolean hasNext() {
        if (s.isEmpty()) {
            return false;
        } else {
            Iterator<MenuComponent> it = s.peek();
            if (!it.hasNext()) {
                s.pop();
                return hasNext();
            } else {
                return true;
            }
        }
    }
    @Override
    public MenuComponent next() {
        Iterator<MenuComponent> it = s.peek();
        MenuComponent mc = it.next();
        if (mc instanceof Menu) {
            s.push(((Menu) mc).getList().iterator());
        }
        return mc;
    }
}

9、观察者模式

业务场景设置:

* 一个游戏中有一角色,角色拥有HP、MP,在游戏窗口中,有一些面板用来显示该游戏角色的hp、mp
* 实现面板有:角色头顶的血条、数值显示面吧、HP和MP的显示球

 (1)简单粗暴的代码实现

class Role {
    private Integer hp;
    private Integer mp;
    private String name;
    public Integer getHp() {
        return hp;
    }
    public void setHp(Integer hp) {
        this.hp = hp;
        // 当hp发生变化时,一定要“通知”3个面板地方,在这里用输出来模拟
        System.out.println("头顶血条更新hp为:" + hp);
        System.out.println("球形血量更新hp为:" + hp);
        System.out.println("面板数值更新hp为:" + hp);
    }
    public Integer getMp() {
        return mp;
    }
    public void setMp(Integer mp) {
        this.mp = mp;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
}
// 模拟怪物
class Monster {
    // 怪物攻击
    public void attack(Role r, int attackValue) {
        r.setHp(r.getHp() - attackValue);
    }
}

这时变化来了,业务变更需要一个角色受到伤害时弹出一个伤害数值。只根据上诉代码时,那么只能修改原始代码,这样就违反了开闭原则,不应该这样设计。而且这样设计也违反了单一职责。

针对上诉问题进行代码优化:

// 角色
class Role {
    private Integer hp;
    private Integer mp;
    private String name;
    private List<Observer> observers = new ArrayList<>();
    public void addObserver(Observer obj) {
        observers.add(obj);
    }
    public void removeObserver(Observer obj) {
        observers.remove(obj);
    }
    public void notifyObservers() {
        for (Observer obj : observers) {
            obj.update(hp);
        }
    }
    public Integer getHp() {
        return hp;
    }
    public void setHp(Integer hp) {
        this.hp = hp;
        // 每当hp变化就通知面板
        notifyObservers();
    }
    public Integer getMp() {
        return mp;
    }
    public void setMp(Integer mp) {
        this.mp = mp;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
}
// 面板的接口
interface Observer {
    // 一个方法,来接收主体发送的数据
    public void update(int hp);
}
// 数值面板
class Panel implements Observer {
    @Override
    public void update(int hp) {
        System.out.println("在左上角的数值面板,更新数据" + hp);
    }
}
// 球形面板
class BallPanel implements Observer {
    @Override
    public void update(int hp) {
        System.out.println("在球形面板中,更新数据" + hp);
    }
}
// 头顶血条
class HeadPanel implements Observer {
    @Override
    public void update(int hp) {
        System.out.println("在角色头顶血条上,更新数据" + hp);
    }
}
// 模拟怪物
class Monster {
    // 怪物攻击
    public void attack(Role r, int attackValue) {
        r.setHp(r.getHp() - attackValue);
    }
}
// -----------------客户端自定义面板--------------------------
// 用于测试后续新增功能
class PopupPanel implements Observer {
    @Override
    public void update(int hp) {
        System.out.println("弹出面板,更新数据:" + hp);
    }
}

优点:

  1. 当新增一个面板显示数据时,不会违反开闭原则。
  2. 因为每一个面板的算法,都被隔离在不同的类,也不违反单一职责。

缺点:

目前主体只是将hp的修改广播给所有的观察者,但要新增一个mp时,则必须要修改原始代码,还是违反了开闭原则

再进一步优化,这里不再更新时传数据,而是在具体的面板的连接角色对象,让角色与面板双向引用。

 

interface Observer {
    // 一个方法,来接收主体发送的数据
    public void update();
}
// 数值面板
class Panel implements Observer {
    // 连接角色
    private Role r;
    public Panel(Role r) {
        this.r = r;
    }
    @Override
    public void update() {
        System.out.println("在左上角的数值面板,更新数据" + r);
    }
}
// 球形面板
class BallPanel implements Observer {
    private Role r;
    public BallPanel(Role r) {
        this.r = r;
    }
    @Override
    public void update() {
        System.out.println("在球形面板中,更新数据" + r);
    }
}
// 头顶血条
class HeadPanel implements Observer {
    private Role r;
    public HeadPanel(Role r) {
        this.r = r;
    }
    @Override
    public void update() {
        System.out.println("在角色头顶血条上,更新数据" + r);
    }
}

测试类:

    public static void main(String[] args) {
        Role r = new Role();
        r.setName("张三");
        r.setHp(100);
        r.setMp(100);

        Panel p = new Panel(r);
        BallPanel b = new BallPanel(r);
        HeadPanel h = new HeadPanel(r);
        r.addObserver(p);
        r.addObserver(b);
        r.addObserver(h);

        // 受到怪物攻击后 -3 hp
        Monster m = new Monster();
        m.attack(r, 3);
    }

分析上述优化代码可知,主体中的涉及观察者的方法可能共用,因而可以抽取出来。其代码为:

interface Subject {
    public void addObserver(Observer observer);
    public void removeObserver(Observer observer);
    public void notifyObservers();
}

然后在各个主体(本例为Role)中实现该接口。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值