我眼中的面向对象分析

本文分享了作者多年来的面向对象编程经验,包括合理设计类的方法、遵循数据库实体设计原则、利用继承提升代码复用性等内容。

面向对象

似乎我也没学过其他的编程思维方式了,面向对象是我编程时常用的思维方式,因为我觉得它更加贴近于我们的生活,更加容易地去理解和定义程序想要表达的内容。正是因为如此,每当项目要开启的时候,我都会使用该种思维来分析和设计程序。多年下来发现它确实有着它的魅力,帮助我解决了很多设计中的问题。于是我总结了一下,在下面章节中说说面向对象使用上的一些心得。

给你的程序分“类”

面向对象的基础就是“类”的设计,其设计好坏直接影响到程序的结构。那么,如何才能设计出合理的类型呢?本人觉得应该从实际需求出发,提取需求中各种实体对象,最终形成程序中使用的对象。某些同学喜欢为自己的程序埋点,添加很多为日后作扩展的类型,其实我是不建议过度设计,我觉得可以在需求中提及的实体预留一些功能性的扩展,但是不建议为需求中没有的功能点设计类型,因为,你永远都把握不准产品日后的发展会是什么样子的,最终形态是怎么样,也有可能你埋下的点还没来得及实现,程序就需要进行重构了等等的情况都会导致你的额外工作变成无用功。

假如公司要求你做一个附近的陌生人交友的App。从这个App的需求,可以提取出附近陌生人以及交友三个关键字,附近描述的是一种相对的地理位置所以它应该是一个类的属性,而陌生人也是相对于某个人来说的一种人与人的关系,所以这里会被分解为一个的实体,还有一个与其它实体的关系。而最后一个交友关键字其实隐含了一种建立关系的行为,这里可以是聊天,也可以是其它的交友方式。我们再进一步分析,如果App里面提供了聊天功能,那么需求中还隐含了一个实体,那就是聊天的内容。所以,根据需求这些点,我们可以设计出两个类型

//用户类型
class User
{
    //地理位置
    property location : Location;

    //用户的好友
    property friends : Array<User>;

    //给某个用户发送消息
    function sendMessage(user : User,  message : Message) : void;
}

//聊天内容
class Message
{
}

这就由需求所驱动产生的类型,以这些类型为核心类围绕需求进行补充和扩展来不断满足后续的需求。

让类型也符合数据库实体设计

关系数据库设计中常会提及第三范式,其特点是去除表中直接依赖。这意思是说两个实体对象不应该被设计在同一个表中,如:评论表中有记录是某个用户发表的评论,可能会把用户名称等信息归类到评论表中,那这样是不符合第三范式要求的,正确的做法是把用户信息放入用户表,评论表中可以保留一个用户id来标识用户表中的用户记录,从而取消在评论表中直接依赖用户信息,导致用户信息的冗余。

其实我个人觉得这种范式要求很好地划分了实体之间的界线,对于类的设计也同样适用。譬如:你定义了一个人类,该类型有四肢属性和说话的行为。这样很好理解,因为现实中人就具备这些特性。但是在后续的开发过程中由于需求的变动,出现了人类因为有飞行道具可以进行飞行。而把飞行的行为直接赋予人类,这种图省事的做法其实是存在很多弊端的:

  • 首先是不容易读懂,因为常识性的认为人是不会飞的,人会飞的话那肯定是个鸟人。所以会让人摸不着头脑。

  • 再者就是这样的定义往往会限制其它跟你协作开发的同事。因为大多数情况下我们都会秉持着能尽量不动旧代码就不要去动,以免动出问题来。那么你这样的设计就让人不得不按照现有的思维去进行扩展。直到哪一天是在没有办法了,再进行彻底的重构。

所以更优的做法应该是定义出一个道具类。至于道具实现什么功能,由其子类实现:

// 道具基类
class Item
{
    //道具的行为
     function action(target : Person);
}

//飞行道具
class FlyingItemItem
{
    function action(target : Person)
    {
        //飞行的实现代码
    }
}

类似这样的实现就可以分离人类和道具类型的耦合,而且也提高了道具类型的扩展。所以,在设计类型时一定要给实体分清界线。

另外一个数据库设计时使用较多的就是实体的关系描述。实体的关系不同,在设计表存储数据时也有所不同。通常有三种实体关系:

  • 一对一,即两个实体之间的关系是一一对应的。如:一个中国公民跟他的身份证是唯一绑定的,知道身份证就能够找到对应的人,同样对应的人也能够找到与其对应的唯一的身份证。类似这种关系,通常是建立两张实体数据表,然后关系可以同时放两个实体表中。

  • 一对多,即两种实体之间,第一种实体可以对应多个第二种实体,但是第二种实体只能对应唯一的第一种实体。如用户评论的例子,用户可以发多条评论,但是一条评论只能够是特定的一个用户发出来。对于这种关系,通常也是建立两张实体数据表,然后会在对应多个实体的数据表中记录关系,拿上面的例子来说,就是用户表和评论表,然后评论表会放入一个userId之类的标识来记录与用户的对应关系。

  • 多对多,即两种实体之间,第一种实体可以对应多个第二种实体,第二种实体也可以对应多个第一种实体。例如作者和书籍的关系就是一种多对多的关系,一个作者可以写多本书,同时,一本书有可能有多个作者共同写成。对于这种关系,则需要建立两种实体数据表以及一张关系表来进行描述。

说了这么多实体关系的东西,其实最想说的一点就是,在进行面向对象分析和设计时,关系的分析也是不可缺少的。但是需要怎么来评估关系的划分和归属,我觉得按照数据库的关系设计标准来进行也是一种不错的方法。

  • 一对一,在该种关系下,其实关系的描述可以同时放在两个类型上,如上面所说的中国公民与身份证的例子,用类可以描述为:
class Chinese
{
    //姓名
    property name:String;

    //对应的身份证
    property idCard:IDCard;
}

class IDCard
{
    //身份证号
    property id:String;

    //对应的人
    property person:Chinese;
}
  • 一对多,该种关系下,稍微与数据库表设计有点不同,由于数据表可以通过查询的方式来找出实体对应的多个另一种实体,这已经超出了类设计阶段的范畴。因此,对应多个类实例的类型需要定义一个集合的属性来保留对应的多个类实例。这样在阅读代码时也能够很好理解,拿用户评论的例子来说:
class User
{
    property comments:Set<Comment>;
}

class Comment
{
   property user:User;
}

类似这样能够很好的描述用户进行过多少的评论,然后哪条评论是哪个用户评论的。

  • 多对多,这是一种很复杂的关系,主要是很多情况下,我们都会忽略这样的一种关系,把它直接给变成了一对多的关系设计。其实对于种情况我们都可以将其关系的处理交由另外一个类型进行处理,与数据库表设计一样,两个实体类型,一个关系类型。拿刚才作者跟书籍的关系为例,可以设计成下面的样子:
//作者书籍关系管理类型
class AuthorBookRelationshipManager
{
   //设置书籍的作者信息
   static function setBookAuthor(book : Book, authors : Array<Author>) : void;

    //获取指定书籍的作者
    static function getAuthors(book : Book) : Array<Author>;

    //获取指定作者的书籍
    static function getBooks(author : Author) : Array<Book>;
}

//作者类型
class Author
{
    //作者名称
    property name : String;

    //获取作者出版过的书籍
    function getBooks() : Array<Book>
    {
        return AuthorBookRelationshipManager.getBooks(this);
    }
}

//书籍类型
class Book
{
    //书籍名称
    property name : String;

    //获取书籍的作者信息
    function getAuthors():Array<Author>
    {
        return AuthorBookRelationshipManager.getAuthors(this);
    }
}

上面的代码可以看出,使用了一个AuthorBookRelationshipManager类型来管理书籍和作者的关系。然后实体类型不再保存之间的关系,要获取相关的实体需要通过AuthorBookRelationshipManager类型来获取。

上面所说的实体划分和关系划分在面向对象中有着非常重要的意义。平时要多加练习才能够在面临实际项目开发时做出合理的分类,让自己开发的架构更加灵活更加强大。

以上对于类型的设计基本上是讲完了,是时候要说说继承方面的事情了,这是面向对象的一个很强大的特性。它可以改善代码结构的问题,节省代码的书写工作量。下面将会围绕它来聊聊我的看法。

万物的始祖

其实不管是写C++、C#还是写Java等等这些高级语言,我们都会发现只要是创建的类它们都会直接或者间接地继承自Object这个类。而这个Object类就是我这要说的万物的始祖,其被所有的事物所继承是一个根类型。Object这个名字也起得相当好,泛指了所有的物体,给予了一种无形态的抽象的定义。意味着我们作为程序中的造物主,需要将这种无形态的物体,变化成各种具体形态的事物,这就需要继承

那么,Object中所做的功能其实并不多,因为它不涉及具体的功能,因此它很多时候在实现上面仅仅能区分是否是同一个对象isEqual,又或者是告诉我们它的描述toString。但是这样已经是有很大的引导意义,因为只要继承了Object类型,那么你的类型将会得到这样功能,阐述了继承功能的强大;同时,你还可以把你的子类型对象赋予给一个Object的实例,因为Object是根类,它可以保留任意的子类类型对象,这是面向对象多态的基本体现。

有了Object这样的一个例子,更加表明了我们在开发项目过程中也应该为项目的代码设计基类。而这里的基类要比Object的功能稍微具体一点。因为它涉及到具体要实现的功能需求,而且还应该不止是一个基类。下面来说一下我是怎么样设计基类的。

和基类来个约定

要怎么设计基类,其实还是要根据实际的项目需求而定。假设你所开发的项目包含很多的后台服务,那么就应该定义一个叫BaseService的基类,然后通过这个基类来分化成多种服务类型。如果你的项目涉及到多种UI的处理,那么你就应该考虑可能需要一个基础的UI管理类型(通常UI组件有自己的基类,这里需要的是对UI进行管理和维护的基础类型),如:UIController。然后根据这个基类分化出各种的UI展现。

基本上是根据如果程序存在多种特性相近或相似的功能模块,那么就应该为这些模块建立基类,以便日后更好的进行扩展。这里不建议要给未来有可能会有多种近似功能的模块定义基类,还是老话,你的假设不一定成立。而且等到出现这样的需求在进行定义也不迟,毕竟要改造的也只是一个类型而已。

上面我们知道如何去抽象出基类,那么基类应该要如何封装属性和方法,其实完全可以视其子类而定,因为这样是最贴近需求的。如前面所说的多种后台服务,假设有如下服务类型:

class ServiceA
{
    //服务名称
    property name:String;

   //调用服务
    function exec(config:Dicationary):void
    {
          //执行服务
    }
}

class ServiceB
{
    //服务名称
    property name:String;

    //调用服务
     function call():void
     {
          //执行服务
     }
}

上面所描述的服务从代码结构上面其实不大相同,特别是在执行服务的行为上接收参数也不一样。如果要从它们身上抽象出基类,确实会让人有所困惑。但是,我们既然要抽象基类,那么就肯定会改造子类。我自己总结一些抽象的规则:

  • 子类中相同的属性和方法,要封装到基类中。
  • 对于大多数子类存在的属性和方法(如5个子类中有3个以上类型存在相同的属性和方法),可以考虑封装到基类中,没用到类型可以对基类的属性和方法进行忽略。
  • 对于意义相近的属性,可以考虑将属性合并或替换(如:子类中分别用id或者name属性来表示对象的唯一,那么基类可以考虑只取其中一项属性或者创建一个集合两者的属性定义)。
  • 对于子类相同行为的方法,如果声明的方法参数的数量或类型不同时,可以考虑基类的方法集合子类该方法中的所有参数(即取方法参数的并集)或者考虑定义共有参数,特殊参数则由子类转换为属性来实现(一般可以持续持有的参数可以这样设计,如系统的配置)。

基于上面所说的,我们可以把ServiceAServiceB,通过抽象基类改成下面的样子:

class BaseService
{
    //服务名称
    property name:String;

    //调用服务
    function exec():void;
}

class ServiceA extends BaseService
{
    //这里将参数设定为属性
    property config:Dictionary;

    function exec():void
    {
          //执行服务
    }
}

class ServiceB extends BaseService
{
    function exec():void
    {
         //执行服务
     }
}

类似这样,我们就可以把基类给抽象出来了。

让类型进化

在开发和维护代码的过程里面,难免会碰到由于产品的迭代,导致需求发生重大的变更。那么可能会出现某些功能的废弃或者功能的合并或演进。这时候,之前定义的类型就要面临着重构或者调整的命运。

遇到这样的问题我们先别急着把之前的东西全盘否定,然后彻底重写。而是先考虑之前的定义中是否还存在可用的东西。要把可以重用的模块给提取出来,那么,模块的取舍就是我们需要评估的事情。

如果新的需求中已经废弃的功能,那么模块所涉及的类型都可以进行废弃。对于这些模块的处理,我个人比较喜欢对相关的类型进行直接删除,然后进行编译,报错后直接修改引用到该类型的代码,直到编译成功为止。对于缺失该类型后需要改造的方法,我会暂时不进行改造,而是打上标记(如TODO或者warning宏),等待正式开发新功能时再寻找会这些标记一一进行解决。

如果新需求中包含该类功能,但是又融合了一些新的元素,那么,我们的类型还不能直接拿来使用。要评估新的元素是否作为类型的一部分,还是作一种新的类型。例如:一款聊天应用,刚开始的时候只有私聊(一对一聊天),那么,发送消息就在User类型中:

//用户类型
class User
{
    //给某个用户发送消息
    function sendMessage(user : User,  message : Message) : void;
}

到了第二个版本的时候,可能支持用户给好友群发消息。那么,明显上面的代码不能满足需求,这时候需要对类型进行改造:

//用户类型
class User
{
    //给某个用户发送消息
    function sendMessage(user : User,  message : Message) : void;

    //给多个用户发送消息
    function sendMessage(users : Arrray<User>,  message : Message) : void
    {
        for (User user in users)
        {
            this.sendMessage(user, message);
        }
    }
}

增加了一个sendMessage多态版本的方法用于群发消息。这样既能保证方法的出口统一,又能保证之前的方法不被修改,就可以轻松地解决群发问题。

那么,到了第三个版本的时候出现了聊天室的概念,这时候需求中多了一个聊天室的关键字,运用之前提到的方法,这里应该需要新增一个类型,不能直接在User类型中进行扩展。那么,可以设计为:

//聊天室
class ChatRoom
{
    //聊天室名称
    property name:String;

    //聊天室用户
    property users:Array<User>;

    //发送消息
    function sendMessage(user:User, message:Message):void;
}

这里增加了一个ChatRoom的类型,主要是用来记录那些人在哪个聊天室中。该类型有一个sendMessage的方法,用于表示哪个用户要在聊天室中发言,发言的内容是什么。

由上面的例子可以看出来,产品迭代有时候需要让类型进行,有时候也需要诞生新的类型。要做那种选择则需要根据产品需求来决定。

只要这种多态

多态在我的理解中就是多种状态。就好比我们用手拿东西的时候,如果拿的是球,那么我们可能是想要打球,如果拿的是苹果,那么我们可能要把它吃掉。根据不同东西我们可能会作出不同的反映。在面向对象中多态就是用于解决这类的问题。我们来假设一下没有多态的时候,我们的代码看起来会是多挫:

function doSomething(obj:Object):void
{
    if (obj instanceof ObjectA)
    {
        //do something by ObjectA
    }
    else if (obj instanceof ObjectB)
    {
        //do something by ObjectB
    }
}

要判断的类型越多,if的语句就越长。那么有了多态后,我们可以很优雅地设计:

function doSomething(obj:ObjectA):void
{
    //do something by ObjectA
}

function doSomething(obj:ObjectB):void
{
    //do something by ObjectB
}

多态可以保证代码设计的出口名字统一,不需要外部调用的人要根据不同的类型调用不同名字的方法,对于外部调用只需要传入的类型不同即可决定要调用哪个方法。

如果不是解决上面所说的问题,我不建议使用多态。因为多态会使到类型变得复杂,如果这个使用多态的类型被继承,然后继承的子类进行了多态处理,那么会影响到程序的质量,并且一旦出现问题,排查的难度会有所增加。

后话

以上说的都是我个人在这些年的开发中所理解的东西,面向对象这东西已经存在我脑海里面许多个日夜了,总想着写些什么,今天总算把它给完成了。后续我会继续写下其他的一些关于程序思维的文章,希望大家支持。

写这篇文章的时候没有参考其他资料,可能存在错漏的地方。如果你是一位好心的猿/媛,麻烦给我指正一下。

下载代码方式:https://pan.quark.cn/s/28492da20c79 依据所提供的文件资料,本资源将系统地探讨FPGA(即现场可编程门阵列)的核心概念、其在视频图像技术领域的入门及进阶知识要点,以及图像处理算法的实现方法。此外,还将对VIPBoardBig这一特定FPGA开发板的详细资料和使用途径进行深入剖析。 FPGA的入门与进阶学习主要涉及以下核心内容: 1. FPGA的基础概念:FPGA是一种能够通过编程进行配置的集成电路,主要目的是达成硬件逻辑的可重构特性。该类芯片由大量的可配置逻辑模块(CLB)、输入输出模块(IOB)以及可编程互连资源共同构成。 2. FPGA开发板与相关套件:FPGA开发板是一种用于FPGA芯片学习和测试的硬件平台,通常配备有基础的外设设备,例如LED指示灯、按键开关、LCD显示屏、串口通信接口等。套件则通常包含硬件板卡、技术文档、相关资源,以及可能的软件工具和示例代码集。VIPBoardBig即为本教程选用的FPGA开发板,拥有特定的硬件配置和功能特性。 3. FPGA的开发流程:FPGA开发一般涉及硬件描述语言(HDL)的设计与仿真阶段,常用语言为Verilog或VHDL。随后,借助综合工具将设计蓝图转化为FPGA内部的逻辑网络,最终通过编程设备将配置文件传输至FPGA芯片中,从而实现设计的预期功能。 4. 外设开发与设计工作:涵盖LED显示控制、键盘驱动、LCD显示驱动、UART串口设计等基础外设的开发任务。这部分知识将引导学习者掌握如何在FPGA平台上管理和运用这些基础外设。 5. VGA驱动显示与字符显示测试:VGA(Video Graphics Array)是一种视频传输接口标准,能够支持640x480...
内容概要:本文系统阐述了企业在搭建官方知识库后如何通过“7步锚定法”实现GEO(生成式引擎优化)的落地,重点在于从知识库走向内容矩阵的战略升级。文章指出知识库仅为起点,真正的核心是让大模型“信任并推荐”企业内容。为此提出“一个主战场+多个品牌布局”的策略,强调需根据行业特性选择高商业流量的大模型(如豆包、文心一言、通义千问等),而非工具性模型(如ChatGPT、Claude)。通过业务场景画像、大模型流量测绘、采信逻辑拆解、内容架构设计、语义关键词埋点、信源建设与效果迭代七步法,构建高质量、高可信度的内容体系,并警惕“全模型覆盖、内容堆砌、一套内容通用、忽视第三方平台”四大误区。最终指出GEO本质是一场认知战,比拼的是对大模型逻辑与客户需求的理解深度及长期主义投入。; 适合人群:已完成官方知识库搭建、希望提升AI引用率与获客效率的企业市场负责人、品牌运营、数字营销从业者及SEO/GEO优化相关人员。; 使用场景及目标:①指导企业科学选择主攻大模型并制定差异化内容策略;②构建符合大模型采信逻辑的高质量内容矩阵;③避免常见GEO落地误区,提升AI搜索下的品牌曝光与转化效果;④建立可持续优化的数据反馈闭环。; 阅读建议:建议结合自身行业特征与客户决策路径,逐步实践“7步法”,优先聚焦单一主战场打透,注重内容质量与第三方权威信源建设,坚持3-6个月持续投入以观察真实效果。
内容概要:本文针对考虑需求响应的微电网优化调度问题,提出了一种基于改进多目标灰狼算法(GWO)的优化方法,并通过Matlab代码实现了完整的仿真验证。研究在传统灰狼算法基础上引入改进机制,有效提升了算法的收敛速度、全局搜索能力和Pareto前沿分布质量,用于求解包含经济运行成本、碳排放水平、可再生能源利用率等多重目标的微电网调度模型。模型充分融合用户侧需求响应机制,利用分时电价等激励手段引导负荷转移与削峰填谷,从而增强系统对光伏、风电等间歇性能源的消纳能力,降低综合运行成本与环境影响。文中系统阐述了多目标优化建模过程、算法改进策略、约束处理方法及仿真结果对比分析,验证了该方法在获取高质量非劣解集和辅助决策方面的优越性。; 适合人群:适用于电力系统、能源互联网、自动化控制、智能优化算法等相关领域的硕士/博士研究生、科研人员,以及从事微电网能量管理、综合能源系统优化、低碳调度等工作的工程技术人员。; 使用场景及目标:①应用于微电网能量管理系统(EMS)中实现多目标协同优化调度;②为基于电价激励的需求响应项目提供负荷调控策略与量化分析工具;③作为智能计算算法在能源系统优化中应用的教学案例与科研参考,支持进一步拓展至多能互补、多微网互联等复杂场景的研究。; 阅读建议:建议读者结合提供的Matlab代码深入理解算法实现细节,重点关注目标函数构造、约束条件处理、多目标适应度评估及决策者偏好选择机制;可尝试将该框架迁移至含氢能储能、电动汽车集群等新型设备的综合能源系统中进行性能测试与算法改进。
内容概要:本文深入分析了洞察时空在2026年世界人工智能大会上提出的“数算一体AI星座”项目,该星座由576颗低轨及超低轨卫星构成,旨在实现“一天一次全球扫描”的高频对地观测能力,为AI Agent提供标准化的“地球真值”数据,弥补大模型在物理世界认知中的预测偏差。项目创新性地提出“数算一体”范式,通过天地一体算力协同、星上边缘计算与多模态数据融合,构建以“地球状态变量”为核心的智能认知系统,推动天基基础设施从数据采集向智能服务跃迁。报告系统梳理了当前研究现状,指出现有遥感系统在时效性、一致性与AI适配性上的不足,提出涵盖星座组网、星上AI推理、数据标准化等关键技术路径,并剖析了星上算力限制、数据一致性保障、物理可解释性等核心挑战,给出了芯片研发、开放标准、跨学科协作等未来发展方向。洞察时空作为主导企业,具备航天与AI复合背景,已获政策与资本支持,计划2030年完成全星座部署。; 适合人群:从事商业航天、人工智能、遥感技术、地球系统科学及相关交叉领域的科研人员、技术研发人员、政策制定者与产业投资者。; 使用场景及目标:①理解AI与天基系统融合的前沿趋势与技术架构;②探索“数算一体”在星地协同计算、多模态数据产品标准化中的实现路径;③评估高频地球观测数据对AI Agent、气候建模、灾害预警等应用的支撑潜力; 阅读建议:本报告兼具战略高度与技术深度,建议结合商业航天发展动态与AI在科学发现中的应用案例进行延伸阅读,重点关注天地算力调度机制与“地球状态变量”的定义演化,以把握下一代天基智能基础设施的发展方向。
内容概要:本文围绕综合能源系统与模型预测控制(MPC)滚动优化展开深入研究,重点利用Matlab代码实现对包含光伏、储能、风电等多种能源形式的综合能源系统进行建模与多时间尺度优化调度。通过MPC滚动优化方法,结合系统的动态数学模型与对未来负荷、可再生能源出力的预测信息,实现对能源生产、存储、转换与消费的协同优化控制,旨在提升系统运行的经济性、能源利用效率、低碳水平及供电可靠性。研究详细阐述了MPC的核心原理、预测模型构建、目标函数设计(如运行成本最小化)、系统约束(如功率平衡、设备容量、储能荷电状态)处理以及优化求解过程,并提供了完整的Matlab仿真代码框架,便于读者复现和二次开发。; 适合人群:具备一定电力系统、自动化、能源系统工程或控制理论基础,熟悉Matlab编程环境,从事相关领域科研、工程应用的研发人员、高校研究生及高年级本科生。; 使用场景及目标:①掌握模型预测控制(MPC)在综合能源系统、微电网、智慧园区等场景中的优化调度应用方法;②学习如何构建多能互补系统的精细化数学模型并实现滚动优化求解;③为能源互联网、新型电力系统背景下的能量管理与决策提供技术参考、算法支持与代码实例。; 阅读建议:建议读者结合文中提供的Matlab代码进行动手实践,重点关注MPC控制器的设计逻辑、预测模型与优化器的耦合机制,以及约束条件的代码实现方式。同时,鼓励在现有模型基础上,拓展至不同的能源设备配置、负荷场景或优化目标(如碳排放最小化),以深化对MPC在能源领域应用的理解。
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值