和田同学完成的设计模式的整理

一、Parnas原则

1. 从C语言角度说明什么是Parnas原则

  • 首先Parnas原则,被称为接口与实现分离原则,是一个函数级别的原则,只需要了解接口,不需要了解实现
  • 站在客户程序的角度,接口指方法的签名和返回值类型;而站在用户程序员的角度,接口是使用该方法的用户所需要知道的所有信息,包括方法头(方法原型)和非常重要的说明文档
  • 实现指的是:完成该接口功能的代码块,及方法体
  • 模块开发者在编写C语言程序的时候,只需要关注该模块需要承担责任或者功能所需要的全部信息;而用户或者客户程序在调用它时,只需要正确使用该模块的所有信息。
  • 良好的接口:命名的规范、简洁而有暗示性;方法签名包含各种可能的重载要求,更重要的是完备的方法说明文档。

2. 从java语言来说,Parnas原则在面向对象时的编码

parnas原则是数据抽象和封装的基础。

数据抽象是接口与实现分离原则在类型层面以及对象技术中的推广,我们只需要数据类型的接口,不需要了解数据类型的实现。

type类是一个名称,它标识了类的接口。如果一个对象能够接受X类的接口的全部操作请求(方法调用),则称对象具有X类型。正是因为Java的子类能够满足父类的接口(尽管可以改写),所以子类的对象能够同时具有类层次中的多个类型

3. 什么是封装

封装在传统概念上指的是将数据和行为包裹起来形成一个不可分割的整体,如今封装的概念指的是以以访问修饰符形成类型的接口叫作封装。前者是有局限性的,关键字在"整体",比如说在C语言中使用链表,应用的是一个结构体来描述封装后的整体,但是用我们面向对象的思想来看,此时的结构体表示的仅仅当前的节点,对于整个链表而言,是没有对应的实体(也就是我们封装后的整体),来保留外界能访问的接口,但是传统概念却认为c语言完成了封装,其实是不对。而在面向对象的过程中,如今封装的概念是自然而正确的,java可将链表形成一个”整体“对他进行维护,通过访问修饰符对类得属性、方法加以合理的控制,暴露一些接口供外界使用。

二 Parnas原则

1.什么是Parnas原则,比较一下(接口与实现分离)与(信息隐藏)

  • 首先Parnas原则,被称为接口与实现分离原则,是一个函数级别的原则,也可以在java语言通过数据抽象,完成接口与实现分离原则在类型层面上的推广,我们只需要了解接口,不需要了解实现。
  • 站在客户程序的角度,接口指方法的签名和返回值类型;而站在用户程序员的角度,接口是使用该方法的用户所需要知道的所有信息,包括方法头(方法原型)和非常重要的说明文档
  • 实现指的是:完成该接口功能的代码块,及方法体
  • 信息隐藏,指的是外界能否访问到;接口与实现的分离,指的是程序员是否了解方法的实现。程序员通常对(不被隐藏的)接口的实现视而不见,换言之,信息隐藏使程序员不能接触到某些东西(类,方法,域),接口与实现分离使程序员不需要接触某些东西(方法的实现)。

2. 什么是针对接口编程,而不针对于实现编程

针对于接口编程就是依赖于抽象类型编程,抽象类型指的是抽象类和java接口(不可实例化的类型),可以实例化的类称为具体类或实现类。针对于接口编程,而不是针对于实现编程的意思就是不要将变量声明为某个具体类的实例对象,而是让他遵守抽象类所定义的接口。这样设计意义是减少模块间的依赖数量和减轻依赖强度,降低耦合度,让程序在需求变化中能保持相应的稳定性。

3. 什么是类的接口,如何限定访问修饰符

类的接口指外界对象能够访问的、类所定义的接口的集合,使用访问修饰符修饰类的接口,我们将其成为封装,通常,类中声明的public、protected域,也作为类接口的一部分.

Parnas原则/接口与实现分离指在模块或方法层面,用户程序员有意识地忽略方法的实现。Parnas原则被推广到类层面——类的接口。

使用访问修饰符限定类的接口,这一机制称为封装。

三 高阶函数

1. 什么是高阶函数,为什么java中要将lamda表达式看成函数

高阶函数是:函数作为参数或者返回值的函数。由于此定义只适合一些函数语言,比如说scheme,js等语言,Java 中lambda表达式是(函数接口的)匿名类的语法糖,lambda表达式是函数接口的一个具体匿名类的对象的引用,我们把函数接口作为参数和函数接口作为返回值的函数在java中认为是高阶函数,为了让java语言拥有高阶函数的定义,前提是我们要将lamda表示看成函数,于是我们才在java中有了高阶函数的一系列概念。

2. F函数的返回值是多少,以及利用F求解2+3

(define F
   lanbda(x)
   lanbda(y)
     (+ x y))) 

返回值是个闭包

求解函数2+3 ,((F 2) 3) 计算过程为

((F 2) 3)的计算过程,先计算(F 2) 返回一个匿名函数,然后该匿名函数以3为实参进行计算。这个匿名函数为一个重要特点:它带有一个数据2

3. 为什么scheme语言中,闭包是个不需要定义的概念?

闭包指一个外包函数的内部定义的嵌套函数,而且(外包函数的)外界能够使用该函数,它通常作为返回值,因为在scheme这种函数语言中函数嵌套函数,函数作为返回值的情况太常见了,即scheme语言天然支持闭包概念,反而让scheme语言不用专门去定义。

同时在很多场合中人们认为闭包是带数据的行为,重点是理解带数据,带的是哪里的数据?带数据指的是函数体外的数据,比如在第二题中,带的数据就是指的是x。

但是上面闭包就是带数据的行为,容易给人造成误解,并非是是引用了外界变量的函数才是闭包!!!例如,函数and()的返回值是一个闭包,它使用了and()的参数other;当然函数not()的返回值也是闭包,它不需要外包函数的局部变量。

综上强调"带数据"的唯一可接受的依据是,考虑如下情况:当外包函数执行完毕,通常它使用的局部变量会被函数调用栈弹出,而嵌套函数如何才能够使用将被弹出的局部变量呢?事实上,这是编程语言或环境需要某种机制将这些局部变量加以保存。而“某种机制”,程序员通常不需要研究

四 闭包

1. 什么是闭包?为什么在java中可以不将其作为返回值?

闭包指一个外包函数的内部定义的嵌套函数,而且(外包函数的)外界能够使用该函数,它通常作为返回值。

同时在很多场合中人们认为闭包是带数据的行为,重点是理解带数据,带的是哪里的数据?带数据指的是函数体外的数据,比如

在scheme语言中

(define (curryingF x) 
    (lambda ( y)
         (+ x y)
    )
)

它的运算过程中

((curryingF x) y)的计算过程,先计算(curryingF x) 返回一个匿名函数,然后该匿名函数以y为实参进行计算。这个匿名函数为一个重要特点:带的数据为的是x。

但是上面闭包就是带数据的行为,容易给人造误解,并非是是引用了外界变量的函数才是闭包!!!例如,函数and()的返回值是一个闭包,它使用了and()的参数other;当然函数not()的返回值也是闭包,它不需要外包函数的局部变量。

之所以java中可以不将闭包作为返回值是因为:

Java等面向对象语言中,可以将闭包赋值给外包函数外边的一个成员变量,而且一个外包函数中可以定义多个闭包

2. java代码,上机实验二,行为参数化,or 和 not的返回值是不是闭包,如何理解带数据的行为?

代码如下

@FunctionalInterface public interface Condition{
    public boolean test(int n); 
    default Condition or(Condition other){
        return (n) -> this.test(n) || other.test(n);
    }
    default Condition not(){
        return (n) -> !this.test(n);
    }
}

将or和not的返回值都认为是闭包,or函数带有了参数other,但是not函数没有带参数,但是在我们应用程序员看来都一样作用都一样,所以我们都将其返回值都看作闭包。

如果我们按照带数据的行为来理解,只有or函数的返回值才是闭包,但not函数不是闭包,我们之所以要这样强调,主要是因为java虚拟机制如何将外包函数的参数进行存储的问题,即强调带数据的行为:是因为当外包函数执行完毕,通常它使用的局部变量会被函数调用栈弹出,而嵌套函数如何在自己执行时能够使用这些被弹出的外包函数的局部变量。

因此应用程序员和语言内部看待闭包的角度是不一致的,需要分开讨论。

3. 为什么说java语言存在最终值陷阱

在java语言中带数据的行为的局部变量,我们在写lamda表达式的时候,有个要求就是要用final修饰,即不可能在修改。在java8以后编写局部变量,可以省略final,局部变量默认不可修改。但是问题依然存在,当我们java使用闭包时,若是使用了成员变量,并且成员变量是可变的,我们在调用函数的过程中,将此值进行了修改,我们闭包再请求值,与我们原先传入的参数的值不相同了,这就是java语言存在的最终值陷阱。

避免最终值陷阱的方法(必答)

解决方法:如果实在要用成员变量,在函数体内创建一个临时变量,将成员变量的值赋值给临时变量

五 依赖注入

1.使用java代码编写依赖注入的两种方式,构造注入和Setter注入

构造注入:Client提供构造器public Client (IServer s),等待外界创建IServer的(实现类的)对象后将其引用传递进来/注入,代码如下

public class Client{ 
    private IServer s; 
    public Client(IServer s){         this.s = s;    }
}

Setter注入:Client提供设置方法如setIServer (IServers),等待注入

public class Client{ 
    private IServer s; 
    public void setIServer(IServer s){        this.s = s;    }    
}

Spring可以按照XML配置文件或源代码中的Annotation自动装配,xml文件如下

<?xml version="1.0" encoding="UTF-8" ?> 
<beans > 
    <!--public void setIServer(IServer s) -->
    <bean id="cli" class="com.Client"> 
        <property name="IServer" ref="sv" /> 
    </bean> 
    <bean id="sv" class="com.Server"> 
    </bean> 
</beans>

配置文件中有两个键值对,分别是cli与com.Client,sv和com.Serrver,键"sv"在本xml文件中用于自动装配,由于 ,则要求com.Client方法内有一个public void setIServer(IServer s)方法,来实现setter注入。

2.为什么说God和依赖注入的容器都是抽象依赖原则的使能工具

为了应对需求的变化,我们在类的设计的时候我们要依赖于抽象类型,达到开放封闭,比如c->animal(animal为抽象类型),如果要依赖于抽象编程,那么就需要获得animal对象,同时使c又不依赖于具体类型(如cat,dog),那我们就需要这样的一个工具,我们称其为抽象依赖原则的使能工具,

组件之间依赖关系由God在运行期决定,形象的说,即由god动态的将某个依赖关系注入到组件之中。依赖注入的目的并非为软件系统带来更多功能,而是为了提升组件重用的频率,并为系统搭建一个灵活、可扩展的平台。

通过god,我们只需要写好配置文件,就可以创建抽象类型的子类。

God就是我们自己编写的这样的使能工具,而spring则是由厂商构建的这样的工具。

3.God的特点

God的优点

  • 使用了最简单的代码,最朴素的代码告诉了学习者什么使能工具,相较于大型spring而言,更为清晰的展示了使能工具的作用
  • 代码轻量级,学习容易,对学习者更为友好
  • 代码结构清晰,便于初学者,进行代码的重构
  • God工具响应速度快

God的不足

  • 仅能能够按照默认构造器创建对象
  • 仅支持属性配置文件properties文件,键值对的形式配置信息。
  • 不支持标注

/要是需要介绍spring的话,下面也带上/

Spring可以带参数。
1.	如图God可以由类A创建子类B,但是无法从类A创建子类C或D。
而Spring则可以。Spring通过反射机制对构造器进行反射。
Spring简介:
1.Spring是一个轻量级的控制反转(IOC)和面向切面的容器框架。
2.IOC控制反转,将对象的创建权反转给Spring,使用IOC可以解决程序耦合性高的问题。
3.DI:依赖注入,在Spring负责创建bean对象时,动态的将依赖对象注入到bean组件中。
Spring框架优点
(1).轻量级的容器框架,没有侵入性。
(2).IOC更加容易组合对象之间的关系,通过面向接口进行编程,可以低耦合进行开发。
(3).易与本地测试。
(4).Aop可以更加容易的进行功能扩展,遵循OCP开发原则。
(5).Spring默认对象的创建为单例的,我们不需要使用单例的设计模式来开发单体类。
(6).Spring的集成很强大,另外还可以对其他框架进行一元化管理。
(7).Spring的声明式事务的方便使用。

六 工厂方法模式

public class AFactory{
    public static Animal getObject(String typeName)	{//int ID
        if(typeName.equals("dog")){
            return new Dog();
        }else if(typeName.equals("cat")){
            return new Cat();
        }else{
            return null;
        }
    }
}

1.参数化工厂的意义,缺点是什么

参数化工厂的意义:参数化工厂和God是等价的,都是抽象依赖原则的使能工具

他的缺点非常明显:分支结构!当我们想要创建新的类型对象的时候,我们不得不添加新的分支,不符合开放封闭原则(对扩展开放,对修改封闭)。开放封闭的封闭意味着你 发布一个已经完成的类,那就不要轻易去修改它的 接口。当我们具体到一个方法的时候,注意,方法的接口,是使用该方法的用户所需要知道的所有信息,包括方法头(方法原型)、和 非常重要的说明文档——用户怎样使用该方法

增加新的分支,不会涉及 方法头的修改——但这不意味着方法的接口没有受到影响,打个比方,原来在分支中是Dog"和“Cat”字符串是合法的,其他为非法参数,而如今我添加了“Fish”,那么此时意味着现在有三个合法串,这就意味着方法的前置条件被修改了,也就是接口被修改了。

2. 通过多态来取消分支(也就是工厂方法模式,可以写代码,也可以画类图)

//将Animal定义成抽象类
public abstract class Animal {
	public abstract void eat();
}

//Animal的实现类Cat
public class Cat extends Animal {
    @Override
    public void eat() {
        System.out.println("我吃猫粮");
    }
}
//Animal的实现类Dog
public class Gog extends Animal {
    @Override
    public void eat() {
        System.out.println("我吃狗粮");
    }
}

public abstract class AnimalFacroty {
    Animal animal = null;
    
    public AnimalFacroty() {
       animal = createAnimal(); 
    }
    //定义一个抽象方法,让各个工厂子类自己实现
    abstract Animal createAnimal();
}


public class S1F extends AnimalFacroty {
    @Override
    Amimal createAnimal() {
        Animal animal = new Dog();
        return animal;
    }
}

public class S2F extends AnimalFacroty {
    @Override
    Amimal createAnimal() {
        Animal animal = new Dog();
        return animal;
    }
}



3.工厂方法模式是抽象依赖原则的使能工具吗?

工厂方法模式不是抽象依赖原则的使能工具,如果以demo依赖抽象类型Animal为目的而使用 工厂方法模式,这是典型的为了模式而模式的应用。因为demo依赖抽象类型Animal的代价,是demo现在依赖依赖具体的工厂类,那为什么我不直接依赖于Cat或者Dog类型呢?这样看来工厂方法模式,作为抽象依赖原则的使能工具,本就不合适。

同时在使用工厂方法模式时,Client并不介意创建具体的工厂对象如new Car(),这种用法再次暗示工厂方法模式不是Client依赖抽象类型的工具

工厂方法模式的目态其实解决类型匹配的问题,例如链表和迭代器的匹配问题,我可以创建一个链表对象和一个迭代器,但是不知道这个迭代器对应匹配的链表是谁,于是我们可以利用工厂方法模式,在对应的链表的中定义一个方法来获取迭代器,完成类型的匹配问题。

七 好莱坞法则

1 .当上层模块向下层模块发送通知时,我们既可以采用通知机制,也可以采用轮询机制,介绍两者,并且比较两者优缺点

轮询机制:不停地查询数据状态。。将进度数据保存在一个成员变量x中,并提供getX()。这样Client就可以时时刻刻地查询该数据

通知机制:当下层模块状态发生某些变化时——通常由操作系统或JVM捕捉这种状态变化并调用回调函数。。

轮询方式的优点:

  • 上层模块能够随时随地的了解所需的数据(不会被下级欺瞒),依赖关系和方法调用关系都很明确;
  • 上层模块获得的数据是实时数据,如9%、72%,而不是10%、20%这种满足通知条件的数据。
  • 下层模块代码简洁。

缺点:

  • 获得数据时执行太多的调用(特别是在网络编程如远程方法调用时,这是不能够容忍的)

通知机制的优点:

  • 没有不断的请求查询,减少了资源的消耗

2. 什么是好莱坞法则,注册的过程和通知的过程简述一下。

好莱坞法则:你不要轮询我,我通知你。例如,我是个导演,我不希望所有的演员都不断给我打电话,而是将电话号码给我,放在电话簿里面,等我觉得你合适了,打电话通知你。

注册:当只有一个观察者的时候,注册就是将本身的引用传递出来,如果多个观察者,则需要创建一个数组,将多个观察者的引用,放在数组里面。相当于例子中,导演收集电话号码的过程。

通知:就是指在满足某一条件的时候,下层调用上层的回调函数,将数据往上传递。

3.模板方法模式,模板方法导致了一个反向控制结构,被称为好莱坞法则,指的是父类调用子类的操作,而不是相反,怎么理解这句话

我觉得在模板方法模式关于好莱坞法则的论调是错误的,画类层次时,可以说父类是上层模块,但是从依赖关系上看,按照分层和依赖的单向性,子类是上层模块,父类是下层模块(不可能将父类放在上层,让下层模块的子类依赖于上层)。

按照他的说法,好莱坞原则“应该”意味着“上层调用下层而下层不得调用上层”;但是,而这一“上层调用下层而下层不得调用上层”,我们称之为单向依赖,此时的“好莱坞原则”作为单向依赖的同义词,就没啥意思了。

再来看反向控制,不管模板方法的细节 ,函数动态绑定,完全不需要”父类调用子类的操作“的说法。通常,父类不可能知道子类的附加的操作。同时这样子也很别扭,我调用了父类的某个方法,是父类调用子类的某一个方法,我也不用管下层是谁。

值得注意的是好莱坞法则在分层结构中也有应用,合理的运用的场景应该是框架,而不是用通知机制去比较。总体而言是:在框架之中,你们上层不要将我当成库函数去用,我调用你。这也仅仅说得是框架与库函数之间的区别,你们不要将我当作库函数,而是你们上层为我提供某些支持,来让我调用你,并没有考虑模块之间依赖的关系。

八 命令模式

1. 用类图来介绍一下命令模式,并且说明他的意图以及角色

意图

将一个请求封装成一个对象,从而使你可用不同的请求对客户进行参数化,对请求排队或记录请求日志,以及支持可撤销的操作。

角色

①Command。命令(即接口Command)封装一个普适方法如execute()。

②具体命令(如X),将调用消息接受者的相应操作。

③Invoker,在通常的演示程序中,它发布命令并要求它执行。它可以下达的一系列具体命令,也可以将系列具体命令组合成一个队列、可以组合成一个批命令;

④接受者/ Receiver。具体命令如X,通过依赖注入X(Receiver)可以绑定任意子类型的接受者。各种具体命令的接受者可以不同,接受者可以具有自己的类层次

2.命令模式和行为参数化的关系

使用方法而言取别明显,命令模式的使用场景为:Invoker不知道应该依赖谁

命令模式指的是当我Invoker不知道要依赖于谁时,便依赖于一个command,此时将command的子类普适方法exe ()作为适配目标,我们将任意一个函数进行适配,所以我们将命令模式作为适配器的特例。

行为参数化指的是:是指将(模板)方法中可变部分设计成该方法的参数,使得该方法具有通用性,以应对需求变换。

比如,demo从0-x之间符合某种关系(Conditoin)的数,我们也可以将其看成命令模式,但是这样子就不符合命令模式的意图和意图不符合,但其是是行为参数化,我们一般将行为参数化认为是策略模式。

但有时候,命令模式和行为参数化是不好区分的,需要自己仔细甄别。

3. 为什么命令模式看作适配器的特例(疑问,不知道这题题目,感觉)

命令模式指的是当我Invoker不知道要依赖于谁时,便依赖于一个command,此时将command的子类普适方法exe ()作为适配目标,我们将任意一个函数进行适配,所以我们将命令模式作为适配器的特例。

九 装饰者模式

1.封闭性操作

封闭性操作:在一个数据类型类型中进行操作,操作后的结果还是属于这个数据类型,Java8引入的流即java.util.stream.Stream,被称为具有函数式编程风格。其流水线操作就属于主题:封闭性操作。

2.实现类图(代码,这个地方是给了类图,要你写代码)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QkhPHNjG-1608812935510)(C:\Users\86133\AppData\Roaming\Typora\typora-user-images\image-20201223205240832.png)]

public interface Num {
    abstract public void say( );
}
 
public class Zero implements Num{ 
    @Override 
    public void say(){
        System.out.println(“我是零”) 
    }
}
public abstract class NextOne implements Num{
    protected Num base;
 	public NextOne(Num base) { this.base = base; }
}

3.实现意图、类图、角色

意图:动态的给一个对象添加一些额外的职责。就增加功能来说,Decoratoor模式想比生成了子类更加灵活

类图画蓝色的部分就可以了!!!!别把笔记也画进去了

角色:

4 .博客上教师才艺的代码实现

package chap5.decoratorP;//其他包语句略
public interface IProfession {
    abstract public String say(String s);
}
 
 
import static yqj2065.util.Print.pln;
public class Teacher implements IProfession{ 
    @Override public String say(String s){   pln(teach(s)) ;    }
    public String teach(String s){
        String myStyle="讲解["+s+"]";
        return myStyle;
    }
}
 
public abstract class ITalent implements IProfession{
    protected IProfession base;
    public ITalent(IProfession base) {   this.base = base;    }
    @Override public final String say(String s) { //模板方法
        s = strengthen(s);
        return base.say(s);
    }
    public abstract String strengthen(String s);
    /*public final String say(String s) {        
        return "";
    }*/
} 
 
 
public class TSong extends ITalent{
    public TSong(IProfession base) {        super(base);    }
    @Override  public String strengthen(String s) { 
        return "旋律(" + s + ")"; 
    }
}

public class TEnglish extends ITalent{
    public TEnglish(IProfession base) {        super(base);    }
    @Override  public String strengthen(String s) { 
        return "讲英语(" + s + ")"; 
    }
}

十 函数接口

1.什么是行为参数化,说明其意义

行为参数化是指将一个(模板)方法中可变部分设计成该方法/函数的参数,使得该函数具有通用性

  • 以函数作为参数
  • 高阶函数的行为被函数参数化,随着传入的条件不同,执行的代码不同。

意义:

  • 在函数级别遵循开放封闭

  • 在模板方法模式中使用行为参数化可以避免类型爆炸

2.介绍函数接口目标类型的概念

函数接口是具有唯一抽象方法的接口被称为函数接口,之所以要保证是唯一是因为我们的lamda表达式时省略了函数名的简写,λ表达式对使用场景有一些要求,一个λ表达式只能够用于能确定其父类型——称为该λ表达式的目标类型(Target typing),即lamda表达式的父类型。故lam表达式必须是函数接口。

3. 编写APP实现类,匿名类,lamda表达式,实现加法、乘法、l

interface OP{
	int op(int x,int y);
}
public class Add implements OP{
    public int op(int m,int n){
        return m+n;   
    }
}

public class Mul implements OP{
    public int op(int m,int n){
        return m*n;   
    }
}

public class APP{
    public static int runFramework(OP op, int m, int n) {
        return op.op(m, n);
    }
    
    public static void main(String[] args){
       //实现类  
       int d = runFramework(new Add(),1,3);
       System.out.println(d);
       
       d = runFramework(new Mul(),1,3);
       System.out.println(d);
       
       //匿名类
       OP s=new OP(){
            public int op(int m,int n){
               return m+n;
            }
        };
    
    	OP m=new OP(){
            public int op(int m,int n){
               return m*n;
            }
        };
    
        int k = runFramework(s,1,3);
        System.out.println(k);
    	k = runFramework(m,1,3);
        System.out.println(k);
        
         //λ表达式 
        Op f = ( m, n)->{return m + n ;};
        d = runFramework(f,1,3);
        System.out.println(d);
        
        f = ( m, n)->{return m * n ;};
        d = runFramework(f,1,3);
        System.out.println(d);
}

与题目无关的补充内容

何如对扩展开放

(1)类层面。

比如说,对于Java中有类Animal,我们可以方便的创建A的新的子类型Dog,而且子类型Dog可以定义新的(A中没有的)操作。这是OOP中很容易做到的事情,也是A对扩展开放的主要方面。即

类Animal的派生

Dog的扩展继承

(2)函数层面。

对函数的扩展,我们可以归结到override,和参数化。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值