怎样设计合适的接口-Java基础-Java-编程开发

java web接口开发(二) 接口开发具体实现 java web开发(一) 环境搭建讲解了如何搭建一个Java Web项目,如果你还没了解,建议先浏览下!今天这篇文章主要讲解的就是接口开发,打算使用比较古老的或者说比较原始方法实现的接口。 一.数据库设计。 假设要做一个简单的学生信息管理系统,数据库名为students_manage,并且先设计一张学生表,表名为student。 1.打开Sqlyong工具,如果还没创建连接... 阅读详情
<script type="text/javascript" src="http://pagead2.googlesyndication.com/pagead/show_ads.js"> </script>摘要:我们在设计系统接口时,经常会遇到这样的问题:
我们的接口应该提供多少方法才合适?
我们的接口应该提供"原子方法"还是"复合方法"?
我们的接口是否应该封装(或者,能否封装)所有的细节?
接口的设计需要考虑用户的使用习惯、使用的方便程度、使用的安全程度,根据我的编程经验,下面会详细讨论接口设计的2个需要权衡的方面:接口的单一化 & 复合化。
接口
接口提供了不同系统之间或者系统不同组件之间的界定。在软件中,接口提供了一个屏障,从而从实现中分离目标,从具体中分离抽象,从作者中分离用户。

站在用户的角度看,一个接口建立并命名了一个目标对象的使用方法。一些约束(例如:编译时的类型系统、运行时的异常机制及返回值)使得类作者的目的得以体现和加强。供给(affordances)指事物的被感知的真实的属性,这些属性可以决定事物使用的可能方法,供给提供了对事物操作的线索。

类设计者的一个职责便是在接口中减小约束与供给之间的隔阂、匹配目标以及一定程度上的自由度,尽可能减小错误使用目标对象的可能。

封装
对于封装来说,远不止数据私有那么简单。在设计中,封装往往会涉及到自我包含(self-containment)。如果一个类需要你知道如何调用它方法(e.g. 在一个线程的环境中,在一个方法调用后调用另一个方法,你必须明确地同步对象),那么它的封装性就不如将所有这些全部包含并隐藏的类(e.g. 这个类是thread-safe的)好。前一个设计存在着设计的漏洞,它的许多限定条件是模糊的,而且把部分责任推给了用户,而不是让类提供者做这些工作来完成类的设计。

在空间或者时间上分离方法的执行(例如,线程,远程方法调用,消息队列),能够对设计的正确性和效率产生意义深远的影响。这种分离带来的结果是不可忽视的:

并发引入了不确定性和环境(context)选择的开销;
分布引入了回调的开销,这些开销可能不断增加,而且会导致错误。
这些是设计的问题,修改它们可不是象修改bug那样简单。

如果一个接口主要由存取方法(set和get方法)组成,每个方法都相应的直接指向某个私有域,那么它的封装性会很差。接口中的域存取方法通常是不会提供信息的:他们在对象的使用中不能通讯、简单化和抽象化,这通常会导致代码冗长,并且容易出错。

所以,我们首先考虑接口设计的第一个原则:

命令与查询分离(Command-Query Separation)
要求:保证一个方法不是命令(Command)就是查询(Query)

定义:
  查询:当一个方法返回一个值来回应一个问题的时候,它就具有查询的性质;
  命令:当一个方法要改变对象的状态的时候,它就具有命令的性质;

通常,一个方法可能是纯的Command模式或者是纯的Query模式,或者是两者的混合体。在设计接口时,如果可能,应该尽量使接口单一化,保证方法的行为严格的是命令或者是查询,这样查询方法不会改变对象的状态,没有副作用(side effects),而会改变对象的状态的方法不可能有返回值。也就是说:如果我们要问一个问题,那么就不应该影响到它的答案。实际应用,要视具体情况而定,语义的清晰性和使用的简单性之间需要权衡。

例如,在java.util.Iterator中,hasNext可以被看作一种查询,remove是一种命令,next合并了命令和查询:
public interface Iterator{
boolean hasNext();
Object next();
void remove();
}




这里,如果不将一个Iterator对象的当前值向前到下一个的话,就不能够查询一个Iterator对象。如果没有提供一个复合方法next,我们将需要定义一系列的命令方法,例如:初始化(initialization)、继续(continuation)、访问(access)和前进(advance),它们虽然清晰定义了每个动作,但是,客户代码过于复杂:
for(initialization; continuation condition; advance){
... access for use ...
}




将Command和Query功能合并入一个方法,方便了客户的使用,但是,降低了清晰性,而且,可能不便于基于断言的程序设计并且需要一个变量来保存查询结果:
Iterator iterator = collection.iterator();
while(iterator.hasNext();){
Object current = iterator.next();
... use current...
}


下面,我们考虑接口设计的第二个原则:

组合方法(Combined Method)
组合方法经常在线程和分布环境中使用,来保证正确性并改善效率。

一些接口提供大量的方法,起初,这些方法看来是最小化的,而且相关性强。然而,在使用的过程中,一些接口显现得过于原始,它们过于简单化,从而迫使类用户用更多的工作来实现普通的任务,并且,方法之间的先后顺序及依赖性比较强(即,暂时耦合)。这导致了代码重复,而且非常麻烦和容易出错。

一些需要同时执行成功的方法,在多线程、异常、和分布的情况下会遇到麻烦。如果两个动作需要同时执行,它们由两个独立的方法进行描述,必须都完全成功的执行,否则会导致所有动作的回滚。

线程的引入使这种不确定性大大增加。一系列方法同时调用一个易变的(mutable)对象,如果这个对象在线程之间共享,即使我们假设单独的方法是线程安全的,也无法确保结果是意料之中的。看下面对Event Source的接口,它允许安置句柄和对事件的查询:
interface EventSource{
Handler getHandler(Event event);
void installHandler(Event event, Handler newHandler);
}




线程之间的交叉调用可能会引起意想不到的结果。假设source域引用一个线程共享的对象,对象很可能在1、2之间被另一个线程安装了一个新的句柄:
class EventSourceExample{
public void example(Event event, Handler newHandler){
oldHandler = eventSource.getHandler(event); // 1
//对象很可能在这里被另一个线程安装了一个新的句柄
eventSource.installHandler(event, newHandler); // 2
}
private EventSource eventSource;
private Handler oldHandler;
}




为了解决问题,也需要由类的使用者而不是类的设计者来完成:
class EventSourceExample{
public void example(Event event, Handler newHandler){
synchronized(eventSource){
oldHandler = eventSource.getHandler(event);
eventSource.installHandler(event, newHandler);
}
}
private EventSource eventSource;
private Handler oldHandler;
}




我们假设:目标对象eventSource是远程的,执行每一个方法体的时间和通讯的延迟相比是很短的。在这个例子中,eventSource的方法被调用了两次,并可能在其他的实例中重复多次,因而,开销也是至少两倍。

此外还有一个问题是对外部的synchronized同步块的使用需求。对synchronized块的使用之所以会失败,主要因为我们通过代理对象来完成工作,所以,调用者的synchronized块,同步的是代理对象而不是最终的目标对象,调用者不可能对其行为做太多的保证。

Combined Method必须在分布的环境,或者,线程环境中同时执行。它反映了用户直接的应用,恢复策略和一些笨拙的方法被封装到Combined Method中,并简化了接口,减少了接口中不需要的累赘。Combined Method的效果是支持一种更像事务处理风格的设计。

在一个组合的Command-Query中提供一个单独的Query方法通常是合理的。提供分离的Command方法是不太常见的,因为Combined Method可以完成这一工作,只要调用者简单的忽略返回结果。如果返回一个结果招致一个开销的话,才可能会提供一个单独的Command方法。

回到前一个例子中,如果installHandler method返回上一次安装的句柄,则设计变得更加简单和独立:
interface EventSource{
Handler installHandler(Event event, Handler newHandler);
}




客户代码如下:
class EventSourceExample{
public void example(Event event, Handler newHandler){
oldHandler = eventSource.installHandler(event, newHandler);
}
private EventSource eventSource;
private Handler oldHandler;
}



这样,我们给调用者提供了一个更加安全的接口,并且不再需要他们解决线程的问题。从而降低了风险和代码量,将类设计的职责全部给了类设计者而不是推给用户,即使有代理对象的出现也不会影响到正确性。

一个Combined Method可以是许多Query的集合,许多Command的集合,或者两者兼有。这样,它可能补充Command、Query方法,也可能与之相抵触。当冲突发生的时候,优先选择Combined Method会产生一个不同的正确性和适用性。

在另一个例子中,我们考虑获得资源的情况。假设,在下面的接口中,方法acquire在资源可用前阻塞:
interface Resource{
boolean isAcquired();
void acquire();
void release();
}



类似于下面的代码会在一个线程系统中推荐使用:
class ResourceExample{
public void example(){
boolean acquired = false;
synchronized(resource){
if(!resource.isAcquired())
resource.acquire();
else
acquired = true;
}
if(!acquired)
...
}
private Resource resource;
}



然而,即使我们放弃可读性和易用性,这样的设计也不是一个Command-Query分离的设计。如果引入了代理,它就会失败:
class ActualResource implements Resource {...}
class ResourceProxy implements Resource {...}




如果用户既可以通过ActualResource来完成工作,也可以通过ResourceProxy来完成工作,而且,ActualResource和ResourceProxy都没有处理同步,则synchronized块可能会失败。因为,既然我们可以通过代理对象ResourceProxy来完成工作,那么,调用者的synchronized块,同步的就是代理对象ResourceProxy而不是最终的目标对象ActualResource。

一个Combined Method解决了这个问题,它使并发和间接性更加透明。
interface Resource{
boolean tryAcquire();
}

下面的代码清晰、简单并且正确:
class ResourceExample{
public void example(){
if(!resource.tryAcquire())
...
}
private Resource resource;
}


Combined Method带来的一个结果是使一些测试和基于断言的程序设计变得十分笨拙,然而,它适合解决线程和分布问题。

实际应用中,接口应该单一化还是复合化,要视具体情况而定。

Java接口:从契约设计到现代编程实践 在Java的面向对象体系中,接口(Interface)如同数字世界的交通规则,既定义了行为规范,又为系统扩展提供了无限可能。从早期的纯抽象方法集合到如今支持默认方法和静态方法的现代接口,其演变历程折射出Java语言与时俱进的哲学。本文将以工程视角深度解析接口的核心价值与应用实践。 阅读详情

相关推荐

【Java编程进阶】Java抽象类与接口详解

这篇文章浅谈了抽象类和接口的实现以及两者的共同点和不同之处,关于Java面向对象基础教程系列就这么多内容,下一篇文章再谈一谈 Object 类,Java中 Object 是所有类的祖先,其重要性不言而喻!

zhangxia_的博客 1341

基于Java的通用图表接口设计与实现

现如今,互联网上充斥着各式各样的图表产品,有基于Java实现的(如JfreeChart)、有基于Javascript实现的(如highcharts和国内的ichartjs)、还有基于Flash实现的(如fusioncharts)。那么不同公司根据自己的需求,会选择不同类型的图表产品,甚至有的公司感觉当前的图表产品已经不适合公司长远发展,就会考虑选择一套新的图表组件,但是期间最大的问题不是挑选新的...

上善若水任方圆 2436

Java编程案例开发实战指南

自定义异常类是在标准异常类无法满足需求时,我们通过继承Exception或其子类来创建的。自定义异常类需要指定父类,可以加入额外的构造方法,以及可能的成员变量和方法来传递异常详情。

weixin_42605397的博客 1200

嵌入式矩阵按键多键扫描:从状态矩阵到差分检测的实战指南

在嵌入式系统开发中,按键扫描是人机交互的基础功能,其核心原理是通过扫描矩阵的行列线来检测按键状态。传统的单键扫描方法在面对多键并发操作时,常因“幽灵键”和去抖动问题导致识别错误,影响用户体验。从技术价值看,可靠的多键扫描能显著提升产品的交互质感,是消费电子、工业面板和智能家居等应用场景的刚需。本文聚焦于矩阵按键之多按键扫描,深入探讨了状态矩阵法和差分检测法两种核心算法。状态矩阵法通过为每个按键维护独立的状态机,构建全局视图以实现稳定识别;而差分检测法则关注状态变化,计算量更小。文中还涵盖了硬件层面的二极管隔

459

Java面向接口编程的实践指南

接口是一种引用类型,它定义了一组方法规范,这些方法由实现该接口的类来具体实现。在Java中,接口是使用关键字interface定义的。接口可以包含常量、方法签名、默认方法、静态方法和嵌套类型。但是,它不能包含实例字段。从Java 8开始,接口还可以包含抽象方法,这些方法可以有默认实现。在上述代码中,Drivable接口定义了两个方法:drive()和start()。其中start()方法有一个默认实现,而stop()方法是一个静态方法。

weixin_35755562的博客 817

用Seurat和ComplexHeatmap绘制单细胞marker基因热图:从数据处理到可视化全流程

本文详细介绍了使用Seurat和ComplexHeatmap绘制单细胞marker基因热图的全流程,涵盖从数据处理、基因筛选、平均表达矩阵计算到高级可视化与注释添加。重点解析了如何利用ComplexHeatmap生成发表级热图,并整合热词信息,解决单细胞数据可视化中的常见问题,提升科研图表质量。

weixin_29048775的博客 531

JAVA接口设计篇:这些都不知道,别说你懂接口设计

1、接口清晰,调用端看得懂,后面自己人修改也不麻烦; 2、外面的坏人很多,最好的办法是自己保护好自己,也就是说鲁棒性要杠杠的; 3、捡芝麻,也不要丢西瓜。修改接口不是重建,而是糊裱匠,顶级的糊裱匠,那是李合肥那种级别的,劝你就别想了,一般人也做不来。

沛哥儿的专栏 6559

WRF后处理总结:wrf-python与NCL在WRF后处理中的基本应用——变量提取、计算与可视化

判断需要提取的变量与诊断量确定后,使用getvar函数进行提取。可使用ncdump-h命令查看wrfout中的变量,wrf中的诊断量分类也可从官方文档处查阅。判断绘图类型所需的可视化绘图类型,决定了你在WRF后处理中的计算步骤。1D点线图:ll_to_xy interpline2D填色图:interplevel3D剖面图:vinterp vertcross绘图、美化色标、XY轴、标签的设置等等。

weixin_43750300的博客 1万+

设计一个高质量的API接口

参考网址:http://url.cn/5UaTeyv 前言 在设计接口时,有很多因素要考虑,如接口的业务定位,接口的安全性,接口的可扩展性、接口的稳定性、接口的跨域性、接口的协议规则、接口的路径规则、接口单一原则、接口过滤和接口组合等诸多因素,本篇文章将简要分析这些因素。 一、规范性建议 1、职责原则 在设计接口时,必须明确接口的职责,即接口类型,接口应解决什么业务问题等...

weixin_30478619的博客 1913

华为非AI方向0603笔试真题-爆破小游戏(详细思路+多语言题解)

华为非AI方向0603笔试第二题【爆破小游戏】,提供详细思路讲解以及代码实现。

qq_45776114的博客 358

Git下载与安装

一、Git下载 二、Git安装

欢迎订阅我的“JAVA全栈开发笔记(全)”专栏,价格优惠,仅需19.9元。 6万+

如何设计类和接口

本文是我们名为“ 高级Java ”的学院课程的一部分。 本课程旨在帮助您最有效地使用Java。 它讨论了高级主题,包括对象创建,并发,序列化,反射等。 它将指导您完成Java掌握的过程! 在这里查看 ! 目录 1.简介 2.接口 3.标记接口 4.功能接口,默认方法和静态方法 5.抽象类 6.不可变的类 7.匿名类 8.可见度 9.继承 ...

最佳 Java 编程 1188

发那科机器人karel程序

备份和查看发那科机器人中的 Karel 程序的过程主要包括从示教器中备份程序,并通过 RoboGuide 等仿真软件查看和运行程序。步骤相对简单,但需要确保你的设备和软件已经设置好,可以通过 FTP 或直接从示教器的外部存储设备进行操作。如果你在操作过程中遇到问题,建议查阅相关的用户手册或联系发那科的技术支持,确保设备设置正确。如果你有更具体的问题或需要进一步帮助,欢迎继续提问!希望如上措施及解决方案能够帮到有需要的你。

**My Coding Family** 1603

一起学JAVA 接口 面向接口开发

接口 1 接口的概念 接口(英文:Interface),在JAVA编程语言中是一个抽象类型,是抽象方法的集合,接口通常以interface来声明。一个类通过继承接口的方式,从而来继承接口的抽象方法。 接口并不是类,编写接口的方式和类很相似,但是它们属于不同的概念。类描述对象的属性和方法。接口则包含类要实现的方法。 除非实现接口的类是抽象类,否则该类要定义接口中的所有方法。 接口无法被实例化,但是可以被实现。一个实现接口的类,必须实现接口内所描述的所有方法,否则就必须声明为抽象类。另外,在 Java 中,接口

程序媛泡泡的博客 3万+

LoveSQL

LoveSQL(SQL注入详解) 1、根据题目,显然想到的是sql注入漏洞,直接尝试万能密码 2、此时url构造,查看字段数 /check.php?username=admin' order by 3%23&password=1 # 存在 /check.php?username=admin' order by 4%23&password=1 # 报错 因此,得到总共有三个字段数 注意:url中不能用#,应该用url编码%23 3、用union来查询测试注入点,查看回显点

lunan的博客 1562

实战封神!YOLOv7 轻量化部署全攻略:ONNX格式转换+TensorRT极致加速(速度直接提升3-5倍,精度无损)

部署核心链路,这是YOLO系列部署的最优解,速度提升3~5倍,精度无损;ONNX是桥梁:解决模型兼容性问题,简化后能去掉冗余节点,是TensorRT加速的前提;TensorRT是核心:NVIDIA显卡的极致加速引擎,FP16量化是速度与精度的最优平衡,无需手动配置,一键开启;轻量化终极组合YOLOv7-tiny + 416尺寸 + TensorRT FP16,速度提升5~8倍,满足所有实时检测场景;避坑核心:转换参数必加,推理尺寸必须一致,engine文件显卡专属。

专注于Python爬虫开发,分享爬虫技巧、项目实战与反爬经验,使用Scrapy、BeautifulSoup等工具,解决数据抓取难题。 308

Java编程基础

Java基础编程知识汇总

小S的博客 4165

基于Python的FAO农业数据高效爬取与分析实战:从Scrapy到异步爬虫的进阶之路

本文将详细介绍如何使用Python最新技术爬取联合国粮农组织(FAO)的农业数据,涵盖从基础爬虫到高级异步爬虫的完整实现过程。文章包含5000余字的技术解析,提供完整的代码示例,并探讨数据处理与存储的最佳实践。

2201_76125261的博客 242

[java基础] --- java开发,service层是不是一定要写接口

估计很多java开发的同学都遇到过,service层要写一个接口,然后再写接口的实现类,但这个接口从项目开始到项目倒闭,都不会有第二个实现,那为什么不直接写个service类呢?如果你还没想过这个问题,那要好好想想了。 网友支持接口模式的,大概原因如下: 1、现在大部分工程都是基于spring框架开发的,我们知道,spring的开发风格就是面向接口的,所以很多人照着搬过来了。 2、接口就是多态,加...

zhangsir的博客 1万+

android 蓝牙通讯demo

android 下蓝牙通讯demo,实现了发现蓝牙,使能蓝牙,通过蓝牙发送消息。

SAP B1中文培訓教材_SDK

公司内部培训资料,对开发工具 ,DIAPI, UIAPI,Add-ON有详细解说

【Java基础】面向抽象编程与面向接口编程

前言 本文将通过一个案例,实现面向抽象编程与面向接口编程,在举例之前先简单说明下抽象与接口这两个概念。 1、面向抽象:抽象类的设计就是只关心操作,不关心具体实现,这样可以使得程序设计者在 将主要精力放在程序设计上,而不必拘泥于细节的实现。如人类分中国人、美国人、韩国人,他们都说早上好,设计者只需要设计一个speak()抽象方法,不用管具体哪国人说什么语言,他们具体说的语言给其他具体实现的程序员去做。抽象编程常常与继承、多态结合,面向抽象编程可以更适应需求变化,如我们又加一个日本人,使得代码可维护。 2、面向

小样yx的博客 1710

pscad IEEE标准系统搭建说明

pscad IEEE标准系统搭建说明,包含典型的IEEE 9节点、14节点、30节点、39节点和118节点系统。内含标准测试系统的原始数据和潮流结果。

如何运用JAVA开发API接口?

本文详细介绍了如何使用Java开发API接口,包括创建接口、实现接口、测试接口、以及部署接口等关键步骤。同时,还讨论了接口的安全性设计和设计原则。通过遵循这些步骤和原则,可以开发出高效、安全、易于维护的API接口。Java作为一种强大的编程语言,在开发API接口方面有着广泛的应用前景。无论是前后端分离的开发模式,还是微服务架构,Java都能提供灵活、可靠的解决方案。希望本文能帮助读者更好地理解和应用Java API接口开发,为实际的开发工作提供有益的参考。如遇任何疑问或有进一步的需求,请随时私信或者评论。

API_Zevin的博客 1969

Data-Structures-and-Algorithm-Analysis-in-Java.pdf

作者: Weiss, Mark A. 出版社: Addison Wesley Data Structures and Algorithm Analysis in Java is an “advanced algorithms” book that fits between traditional CS2 and Algorithms Analysis courses. In the old ACM Curriculum Guidelines, this course was known as CS7. This text is for readers who want to learn good programming and algorithm analysis skills simultaneously so that they can develop such programs with the maximum amo...

Java编程需要必须掌握哪些基础知识?

Java编程基础知识学习是每一个程序猿进入编程领域的人所必须经历的学习过程,只有基本功修炼好,才能有更深远更好的的发展,那Java编程基础知识有哪些,需要必须掌握的呢? 1掌握静态方法和属性 静态方法和属性用于描述某一类对象群体的特征,而不是单个对象的特征。Java中大量应用了静态方法和属性,这是一个通常的技巧。但是这种技巧在很多语言中不被频繁地使用。 理解静态方法和属性对于理解类与对象的关系是...

IT知堂 3881

Java基础,接口

接口(Interface)是Java中的一种引用类型,它可以看作是一个规范,规定了实现它的类必须实现的一组方法。接口中的方法默认是抽象的,即没有方法体。此外,接口也可以包含常量。一个类可以实现(implements)多个接口,设计接口的目的之一就是要实现java类的多继承// 一个简单的接口示例void eat();在上面的代码中,Animal接口定义了两个方法eat()和sleep(),任何实现该接口的类都必须提供这些方法的实现。

qq_62048141的博客 1064

Java 面向接口编程

面向接口编程 接口介绍 接口 (interface) ,用来描述一个类应该做什么,而不指定它们的具体逻辑。 举一个生活中的例子,比如说一部手机。我们可以定义一个"手机"接口标准,例如充电器要使用TYPE-C,安卓系统以及大小尺寸等,然后各大手机厂商就按照这个标准来生产手机,有一天手机坏掉了,但是充电器还是好的,那么我们把换个手机就可以还使用原来的充电器充电了。 在Java中的接口也是一样,接口不是类,而是对希望符合这个接口的类的一组需求。在代码设计中,我们事先约定好项目的各个功能的接口(接口的签名和方法),

TheAnnoyingDog的博客 3928

0基础学习开发模块的话怎么选择合适

🔹。

王景程的博客 1555

为什么我们在程序开发设计中要基于接口而非实现编程?

不过,如果你觉得这种思考方式更加顺畅,那也没问题,只是将实现类的方法搬移到接口定义中的时候,要有选择性的搬移,不要将跟具体实现相关的方法搬移到接口中,比如 AliyunImageStore 中的 generateAccessToken() 方法。前面我们也提到,这条原则的设计初衷是,将接口和实现相分离,封装不稳定的实现,暴露稳定的接口。上游系统面向接口而非实现编程,不依赖不稳定的实现细节,这样当实现发生变化的时候,上游系统的代码基本上不需要做改动,以此来降低代码间的耦合性,提高代码的扩展性。

1270
上一篇: JAVA程序员面试32问,你能回答多少题?-Java基础-Java-编程开发
下一篇: classpath详解(谨献给那些找不到北的朋友)-Java基础-Java-编程开发
java169
博客等级 码龄18年 27粉丝 2568原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值