什么是面向对象
面向对象的含义
面向对象编程就是将事物抽象成对象,针对对象所持有的数据和与之相关联的行为进行编程。
将现实中的事物对应程序中的对象,让程序的运行变成对象与对象之间的交互。对象成为程序中的基本单元,将这一类对象相应的数据和数据对应的操作封装到一起作为类,而对象则是该类的一个具体实例。
面向对象的三大特征
- 封装
- 继承
- 多态
封装是面向对象最基本的表现,继承是面向对象最核心的行为,多态是面向对象最重要的能力。
封装
将不需要外部看到的数据和行为放到类内,外部不可见,只暴露外部需要看到的数据和方法
封装对象有一个重要的条件:依据特征去分类。
这是面向对象的初衷和最基本的表现,将数据被保护在类的内部,尽可能地隐藏内部的实现细节,只保留一些对外接口使之与外部发生联系,实现了高内聚;其他类只能通过提供的行为来进行交互,当类内部行为的逻辑发生改变时,无需通知调用者,实现了低耦合。
封装不只是“隐藏数据”,也包括隐藏实现细节并提供抽象接口,让调用方只需关心“做什么”,而不必关心“怎么做”。
例如:用户
| 属性 | 行为 |
|---|---|
| 用户ID、昵称、状态…… | ? |
public class UserInfoRpcDTO implements Serializable {
private Long userId;
private String brand;
private String thdPartId;
private String thdPartName;
private String thdPartType;
private String entranceCode;
private String password;
private Integer status;
private String nickName;
private String faceUrl;
/* ...... */
}
例如用户常见行为:
-
用户是否绑定微信
-
目前实现
userInfo == null || !WECHAT_REGISTER_TYPE.equals(userInfo.getThdPartType()) || StringUtils.isEmpty(userInfo.getThdPartId()) -
封装到用户类内部
public boolean isWechatLinked() { return WECHAT_REGISTER_TYPE.equals(this.getThdPartType()) && StringUtils.isEmpty(this.getThdPartId()); }
-
-
用户状态是否正常
-
用户是否实名认证
-
……
继承
依据相关类的共有特征进行层级分类,具体类包括抽象类的特征,二者是一种“is-a”的关系。
这是面向对象最核心的行为与标志。子类继承父类,表示子类“is-a”父类,子类从父类得到子类共有的方法,并进行个性化实现与拓展,是一种父类类别下的具体类别,有着父类包含的特征,也可以拥有自己独有的特征。而父类是一组相关子类共同特征的集合,可以从抽象层面代指子类。主要目的是减少代码量,使代码结构更加清晰可见
多态
多态就是同一个类的对象,在不同的情况下,有不同的表现。通俗讲就是,Java 在运行时,能根据对象类型的不同,对于同一个行为产生不同的结果。主要目的是提高代码的灵活性和可拓展性,使得代码更容易维护和拓展。
这是面向对象最重要的能力,也是它灵活、易拓展和复用的原因。
常见的多态实现方式:继承 + 重载。
例如:会员
-
会员
public class Member { /** 会员下单,没有折扣,原价下单 */ public void buy(int goodsPrice) { System.out.println("下单成功,下单金额 :" + goodsPrice); } } -
VIP
public class VipMember extends Member { /** Vip会员下单,9折下单 */ public void buy(int goodsPrice) { System.out.println("下单成功,下单金额 :" + (goodsPrice * 0.9)); } } -
SVIP
public class SVipMember extends Member { /** SVip会员下单,8折下单 */ public void buy(int goodsPrice) { System.out.println("下单成功,下单金额 :" + (goodsPrice * 0.8)); } }
面向对象的思想
- 封装是基础,将一切看作成对象,从代码层面,定义生成类和对象的基本单元;
- 继承从核心行为层面,基于类的共有特征进行层级分类,抽象为多层;
- 多态从表现和结果层面,描述了基于这种分类所带来的好处,即可拓展性和可复用性。
那么,面向对象的思想到底是什么?
面向对象是一种语言无关、技术无关的一种编程范式,强调编程时思考问题的角度与方法。
面向对象编程,就是将事物抽象成对象,针对对象所持有的数据和与之相关的行为进行编程。这里的关键点就对事物的抽象与分类,需要描述好事物的变化规律。
面向“变化”编程
良好的设计最重要,也是最主要的就是面向“变化”编程。
处理好了变化,不光拥有了良好的拓展性,更提升了复用性、可读性、稳定性、可测试性、兼容性等诸多指标,甚至性能也会有所提升。
什么是变化?
变化,就是一组可以独立改变的事物。
- 衍生细节:从抽象类衍生出各种有着特征的具体类
- 拓展种类:产生新的事物进而需要拓展新的类
- 封装差异:在不同的情况下,事物需要有不同的表现
衍生细节
用户实名认证最初可能只需要身份证和姓名认证,随着业务的发展,增加了三级(手机号)和四级实名认证(人脸)。
初期,由于没有或不需要那么多细节,大家不关心与区分具体种类,所以用统称来描述。
后期,发生了变化, 由于业务细化,内容变得丰富起来,细节变多,这个统称无法去描述与区分那些不同的细节,需要用更具体的类别来描述所需,而具体的类别自然也被包括在笼统的类别中。当然,也存在一些不关心这些细节的场景与需要,而直接用统称去描述它们。
这就是对细节的衍生。从一开始简单的业务需要,到后来逐渐细化业务,区分细节。对应的,就是一开始的抽象类,到后来从抽象类衍生的,可以区分细节的具体类。
而这些从抽象类衍生具体类,非常自然的应对了这些细节衍生的变化。不但解决了新的要区分细节的需要,还不破坏原本对抽象事物的使用。
拓展种类
最初系统的订单,可能只有方案订单,后面加入了工具、VIP等多种业务的订单。
初期,业务场景单一,只有一个种类。
后期,发生了变化,由于业务拓展,新的类似业务产生,有了新的种类。同时,由于二者的相似性,拓展时如果完全重新拓展,成本较高。由此,将这类业务的共同特征提取,作为公共的抽象业务(抽象类),基于此抽象业务去拓展新的业务。
这就是对种类的拓展。从一开始单一的业务需要,到后来逐渐拓展业务,添加业务种类。对应的,就是一开始只有一个具体类别,到后来需要高效拓展,提取公共业务封装到抽象类中,并基于抽象类去拓展新的具体类别。
这样提取抽象类并拓展其他具体类,非常统一、高效的应对了这种种类拓展的变化。不但解决了创建新类别的要求,还支持原有公共逻辑与接口的复用。
衍生和拓展的区别:衍生在初期没有或者不需要那么多细节,而拓展,需要抽象类是是需要提取公共逻辑与接口,实现复用。
封装差异
针对系统会员,VIP和SVIP,既有相同点,又有不同点。
当前,存在两个相似业务(两人的快餐店),虽然存在许多实现上的不同(一个是小明的,一个是小红的,菜单也不同),但大体业务形式是相似的(都支持顾客点餐)。
既然是相似的业务,外部就希望可以统一的看待它们,不希望自己处理这些不同。而这些不同,就是差异,就是变化。
为了统一对待相似业务,可以提取它们的宏观公共业务逻辑与表现接口,封装为总体业务的类。而实现上的不同,则交由实现接口(抽象类)的具体类去实现。
这就是对差异的封装。从一开始有多个相似的业务,到后来需要统一对待,隐藏差异。对应的,就是一开始有多个业务逻辑类,到后来提取相同的宏观公共业务逻辑与接口作为总体业务类,而变化的具体实现则延迟到实现对应接口的具体类中去实现。
这样提取提取相似的业务逻辑与表现、封装变化的具体实现的方式,对外透明且清晰的应对了这种存在差异而导致的变化。不但让差异对外透明,而且精简了重复的逻辑,同时支持具体实现的复用。
怎么面向变化编程
面向变化编程的过程,也是封装变化、调整结构支持变化的过程。
封装变化
面向变化编程的第一步就是封装变化。
封装变化有三个重点:
- 让变化对外界透明: 将变化的实现封装到具体中类,减少外界对变化的了解。不但减轻了外界处理变化、了解变化细节的复杂度与工作量,降低了代码复杂度,同时也将变化的影响控制在了内部范围,便于查找与修改。
- 让变化缩小为它本质的样子: 找出变化的本质,并将其封装,不要将没有变化的部分也封装其内。可以最大程度复用代码,减少了每次变化的代码体积,同时逻辑也将更加清晰。
- 将变化的影响范围缩到最小: 减少对外暴露的表现形式,让行为与职责更加单一明确。而在变化的类的内部,变化的部分影响的范围也应尽可能的缩小。最好变化的类就是变化本身。减少变化的影响范围,本身也反映了对变化的理解程度以及变化的抽象能力。
支持变化
面向变化编程的核心步骤,就是调整变化的封装结构,支持更多的变化。
当已经通过封装变化将变化尽可能集中,接下来,就可以让变化在此处支持拓展,并在不同的情形下选择不同的具体实现。而支持变化的方法,无外乎还是提取公共特征与相同的抽象业务形式(接口),并将不同的实现延迟的具体类。困难的就是如何对这些特征及接口进行抽象,让其足够清晰且灵活。
在面向对象中面向变化
用面向对象衍生细节
通过类的继承与多态,来完成个性化的实现与拓展的能力。
用面向对象拓展种类
面向对象通过继承实现从抽象类拓展新类,通过继承表现出来的多态作为拓展的子类变化的部分的不同表现。
用面向对象封装差异
在面向对象中想要实现多态,就要使用继承机制,继承是面对对象实现多态的基础。
面向对象的六大原则
- 里氏替换原则
- 单一职责原则
- 开闭原则
- 迪米特法则
- 接口隔离原则
- 依赖倒置原则
里氏替换原则
子类永远可以替换父类,且不会造成错误、异常。
这是一个用来规范继承的原则,该原则包含四层含义:
- 子类必须完全实现父类的方法;
- 子类可以有自己的实现;
- 重写或实现父类的方法时,输入参数可以放大;
- 覆盖或实现父类的方法时,返回值可以缩小。
通过该原则,继承标准的实现了一个“is-a”语义,让父类是子类的公共特征与接口的集合,可以从抽象层面代指子类。也是通过这样一个标准的抽象与实现的语义,才可以让我们将变化的抽象公共特征提取,并交由子类去实现变化的细节。
单一职责原则
一个接口或一个类只能有一个原因引起变化,也就是一个接口或者类只能有一个职责,它就负责一件事情。
让变化本身最小,也就是每次对变化的封装只包含一个变化,且不包含与变化无关的逻辑部分。
让变化对外暴露的接口最小,则是在说抽象变化的宏观表现,将每个完整而独立的功能当成一个整体。简单来说,就是多个相同层面的功能不能放在一起,尽可能暴露更抽象层面的表现接口。
开闭原则
类应该对拓展开放, 对修改关闭
通过提取抽象的公共特征与接口作为对外表现接口来屏蔽外部对内部变化的感知,通过继承实现包含不同细节的具体类进行拓展,通过尽可能的自描述与其他优化方法降低外部对具体类的引用。
迪米特法则
一个类应该对其他对象有最少的了解
最少知道原则让我们尽可能少的允许直接引用某个对象,同时尽可能少的给外部暴露自己的细节。暴露变化本身最宏观的表现,让外部了解变化最少的细节。
依赖倒置原则
要依赖抽象,不要依赖具体的类
类之间的依赖关系也是变化之间的依赖关系,该原则要求面向接口编程,而不是面向实现。
接口隔离原则
不应该依赖它不需要的接口,类之间的依赖关系应该建立在最小的接口上
解读设计模式
设计模式是面向对象编程中应对变化的重要方法论,它将前任总结的可复用经验沉淀为固定的解决方案。常见的设计模式可分为三类:
-
创建型模式:关注对象的创建过程,屏蔽创建细节
- 代表模式:单例模式、工厂方法模式、抽象工厂模式、建造者模式、原型模式
- 示例:使用工厂方法统一创建不同类型的订单,避免直接调用方直接
new
-
结构型模式:关注对象与类直接的组合关系,优化结构。
- 代表模式:适配器模式、装饰器模式、代理模式、组合模式、外观模式
- 示例:使用装饰器模式给订单增加促销功能,而无需修改原有类
-
行为型模式:关注对象之间的通信与职责分配
- 代表模式:策略模式、观察者模式、模板方法模式、责任链模式、状态模式
- 示例:用策略模式封装不同的会员折扣算法,实现运行时动态切换
设计模式的核心是封装变化与解耦依赖,本质上是面向对象思想的落地实践。
切忌生搬硬套设计模式,应从问题出发选择模式,而不是为了使用模式而使用
面向对象的沼泽
面向对象虽然强大,但如果使用不当,也容易陷入设计的沼泽:
-
过度设计
- 为了“可拓展”预留过多抽象层和接口,导致代码复杂、理解成本高,而这些拓展点可能永远不会用到。
-
滥用继承
- 继承层级过深导致父类修改影响巨大,出现“脆弱基类”问题。很多情况下,组合优于继承
-
接口膨胀
- 过于追求抽象,定义了大而全的接口,让实现类被迫实现无关方法,违背接口隔离原则。
-
模式绑架
- 强行套用设计模式,让代码可读性变差,业务逻辑被掩盖。
-
命名与职责混乱
- 类名和方法名无法体现职责,职责边界不清晰,导致封装失效。
不要为暂时用不到的功能设计复杂结构;多用组合和委托,少用继承;经常做代码审查,保持设计与实际需求一致。
面向对象实践指南
总体思路:把业务用例拆成若干“对象职责”和“对象协作”。
-
明确需求与用例(Why/What)
- 要做什么?核心行为是什么(用户场景、边界条件、失败场景)
-
领域建模(识别对象与职责)
-
确定交互/流程(责任与协作)
-
定义接口与边界(模块化与依赖倒置)
-
设计类与聚合(实体聚合与不变式)
-
选择和适的设计模式(按需)
-
先写测试再实现
-
实现最小可行版本
实践
在线课程学习系统
1. 明确需求与用例(Why/What)
业务背景:构建一个在线课程学习系统,支持用户购买课程、观看视频、完成作业等功能。
核心用例:
- 用户可以浏览和购买不同类型的课程(视频课程、直播课程、专栏课程)
- 不同会员等级享有不同的购买折扣和观看权限
- 课程学习进度需要被记录和统计
- 完成课程后可以获得证书
边界条件:
- 未登录用户只能浏览课程信息,不能购买或观看
- 课程一旦购买成功不可退款
- 学习进度达到80%才能申请证书
失败场景:
- 余额不足导致购买失败
- 网络异常导致学习进度保存失败
- 课程已下架但用户仍可以继续学习已购买的内容
2. 领域建模(识别对象与职责)
通过分析需求,可以识别处以下核心对象:
实体对象:
-
用户(User) :持有个人信息、会员等级、账户余额
-
课程(Course) :课程基本信息、价格、状态
- 视频课程(VideoCourse)
- 直播课程(LiveCourse)
- 专栏课程(ColumnCourse)
-
订单(Order) :记录购买信息、支付状态
-
学习记录(LearningRecord) :记录学习进度
-
证书(Certificate) :学习完成凭证
值对象:
- 价格(Price) :金额、货币类型
- 学习进度(Progress) :完成百分比、已学时长
领域服务:
- 折扣计算服务(DiscountService) :根据会员等级计算折扣
- 证书颁发服务(CertificateService) :验证资格并颁发证书
3. 确定交互/流程(责任与协作)
用例:用户购买课程
1. 用户选择课程 → Course
2. 系统计算折扣价格 → User.getMemberLevel() + DiscountService.calculate()
3. 检查用户余额 → User.getBalance()
4. 创建订单 → Order. create()
5. 扣除余额 → User. deductBalance()
6. 订单支付成功 → Order.markAsPaid()
7. 创建学习记录 → LearningRecord.create()
对象协作关系:
- User 依赖 MemberLevel(会员等级)
- Order 依赖 User 和 Course
- LearningRecord 依赖 User 和 Course
- DiscountService 依赖 MemberLevel 和 Price
4. 定义接口与边界(模块化与依赖倒置)
-
课程接口:定义课程的公共行为
public interface Course { Long getCourseId(); String getTitle(); Price getPrice(); CourseType getType(); boolean isAvailable(); /** * 验证用户是否有权限访问该课程 */ boolean canAccess(User user); /** * 获取课程时长(分钟) */ int getDurationInMinutes(); } -
会员等级接口:定义会员行为
public interface MemberLevel { int getLevel(); String getLevelName(); /** * 获取折扣系数(0.8表示8折) */ BigDecimal getDiscountRate(); /** * 是否可以观看指定类型的课程 */ boolean canWatchCourseType(CourseType courseType); } -
折扣策略接口
public interface DiscountStrategy { Price calculate(Price originalPrice, MemberLevel memberLevel); } -
学习进度追踪接口
public interface ProgressTracker { void recordProgress(Long userId, Long courseId, int watchedMinutes); Progress getProgress(Long userId, Long courseId); boolean isQualifiedForCertificate(Long userId, Long courseId); }
5. 设计类与聚合
用户
-
用户:管理用户信息、余额、会员等级
@Data public class User { private Long userId; private String userName; private String email; private MemberLevel memberLevel; // 会员等级 private Price balance; // 账户余额 public User(Long userId, String userName, String email, MemberLevel memberLevel, Price balance) { this.userId = userId; this.userName = userName; this.email = email; this.memberLevel = memberLevel; this.balance = balance; } /** * 封装变化:判断用户是否为 VIP 会员 */ public boolean isVip() { return memberLevel != null && !(memberLevel instanceof OrdinaryMember); } /** * 封装行为:扣减余额 不变式:余额不能为负 */ public void deductBalance(Price amount) { if (balance.lessThan(amount)) { throw new InsufficientBalanceException("余额不足,当前余额:" + balance + ",需要:" + amount); } this.balance = balance.subtract(amount); } /** * 封装行为:升级会员 */ public void upgradeMembership(MemberLevel newLevel) { if (this.memberLevel.getLevel() >= newLevel.getLevel()) { throw new IllegalStateException("不能降级或平级"); } this.memberLevel = newLevel; } /** * 封装行为:检查是否可以购买课程 */ public boolean canPurchase(Course course, Price finalPrice) { return course.isAvailable() && balance.greaterThanOrEqual(finalPrice); } }
会员
-
会员等级抽象类:应用里氏替换原则,所有子类都可以替换父类
@Data public abstract class AbstractMemberLevel implements MemberLevel { protected int level; // 等级数值,用于比较 protected String levelName; /** * 模板方法:定义通用的权限检查流程 */ public boolean canWatchCourseType(CourseType courseType) { if (courseType == null) { return false; } return checkCourseTypePermission(courseType); } /** * 留给子类实现具体的权限逻辑 */ protected abstract boolean checkCourseTypePermission(CourseType courseType); } -
普通会员
@Data public class OrdinaryMember extends AbstractMemberLevel { public OrdinaryMember() { this.level = 0; this.levelName = "普通会员"; } @Override protected boolean checkCourseTypePermission(CourseType courseType) { // 普通会员只能观看免费课程 return courseType == CourseType.FREE; } @Override public BigDecimal getDiscountRate() { return BigDecimal.ONE; // 无折扣 } } -
VIP会员
@Data public class VipMember extends AbstractMemberLevel { public VipMember() { this.level = 1; this.levelName = "VIP会员"; } @Override public BigDecimal getDiscountRate() { return new BigDecimal("0.9"); // 9折 } @Override protected boolean checkCourseTypePermission(CourseType courseType) { // VIP可以观看免费和付费课程,但不包括专属课程 return courseType != CourseType.EXCLUSIVE; } } -
SVIP会员
@Data public class SVipMember extends AbstractMemberLevel { public SVipMember() { this.levelName = "SVIP会员"; this.level = 2; } @Override public BigDecimal getDiscountRate() { return new BigDecimal("0.8"); // 8折 } @Override protected boolean checkCourseTypePermission(CourseType courseType) { // SVIP可以观看所有类型课程 return true; } }
课程
-
课程抽象类:封装课程的公共属性和行为
@Getter @ToString public abstract class AbstractCourse implements Course { protected Long courseId; protected String title; protected String description; protected Price price; protected CourseStatus status; protected LocalDateTime createTime; @Override public boolean isAvailable() { return status == CourseStatus.PUBLISHED; } /** * 模板方法:访问权限检查 */ @Override public boolean canAccess(User user) { if (! isAvailable()) { return false; } return checkAccessPermission(user); } /** * 由子类实现具体的权限检查逻辑 */ protected abstract boolean checkAccessPermission(User user); public void publish() { this.status = CourseStatus.PUBLISHED; } } -
视频课程
@Data public class VideoCourse extends AbstractCourse { private List<Video> videos; // 视频列表 private int totalDuration; // 总时长(分钟) public VideoCourse(Long courseId, String title, Price price) { this.courseId = courseId; this.title = title; this.price = price; this.status = CourseStatus.DRAFT; this.videos = new ArrayList<>(); this.createTime = LocalDateTime.now(); } @Override public CourseType getType() { return CourseType.VIDEO; } @Override public int getDurationInMinutes() { return totalDuration; } @Override protected boolean checkAccessPermission(User user) { // 视频课程:VIP及以上可以观看 return user.getMemberLevel().canWatchCourseType(CourseType.VIDEO); } /** * 添加视频 */ public void addVideo(Video video) { if (status == CourseStatus.PUBLISHED) { throw new IllegalStateException("已发布的课程不能添加视频"); } videos.add(video); totalDuration += video.getDuration(); } /** * 发布课程 */ public void publish() { if (videos.isEmpty()) { throw new IllegalStateException("课程至少需要一个视频才能发布"); } super.publish(); } } -
直播课程
@Data public class LiveCourse extends AbstractCourse { private LocalDateTime liveStartTime; private LocalDateTime liveEndTime; private String liveRoomUrl; private int estimatedDuration; public LiveCourse(Long courseId, String title, Price price, LocalDateTime liveStartTime, int estimatedDuration) { this.courseId = courseId; this.title = title; this.price = price; this.liveStartTime = liveStartTime; this.estimatedDuration = estimatedDuration; this.status = CourseStatus.DRAFT; this.createTime = LocalDateTime.now(); } @Override public CourseType getType() { return CourseType.LIVE; } @Override public int getDurationInMinutes() { return estimatedDuration; } @Override protected boolean checkAccessPermission(User user) { return user.getMemberLevel().canWatchCourseType(CourseType.LIVE); } /** * 检查直播是否进行中 */ public boolean isLive() { LocalDateTime now = LocalDateTime.now(); return now.isAfter(liveStartTime) && (liveEndTime == null || now.isBefore(liveEndTime)); } /** * 结束直播 */ public void endLive() { if (!isLive()) { throw new IllegalStateException("直播未开始或已结束"); } this.liveEndTime = LocalDateTime.now(); } /** * 发布课程 */ public void publish() { if (liveEndTime == null) { throw new IllegalStateException("直播未开始或未结束"); } super.publish(); } } -
专栏课程:仅SVIP可以访问
@Data public class ColumnCourse extends AbstractCourse { private List<Article> articles; public ColumnCourse(Long courseId, String title, Price price) { this.courseId = courseId; this.title = title; this.price = price; this.status = CourseStatus.DRAFT; this.articles = new ArrayList<>(); this.createTime = LocalDateTime.now(); } @Override public CourseType getType() { return CourseType.EXCLUSIVE; } @Override public int getDurationInMinutes() { // 专栏按文章数量 * 平均阅读时间估算 return articles.size() * 15; } @Override protected boolean checkAccessPermission(User user) { // 专栏课程仅SVIP可访问 return user.getMemberLevel(). canWatchCourseType(CourseType.EXCLUSIVE); } public void addArticle(Article article) { if (status == CourseStatus.PUBLISHED) { throw new IllegalStateException("已发布的专栏不能添加文章"); } articles. add(article); } /** * 发布课程 */ public void publish() { if (articles.isEmpty()) { throw new IllegalStateException("课程至少需要一篇才能发布"); } super.publish(); } }
订单
-
订单:管理订单生命周期
@Data public class Order { private Long orderId; private Long userId; private Long courseId; private Price originalPrice; // 原价 private Price finalPrice; // 实付价格 private OrderStatus status; private LocalDateTime createTime; private LocalDateTime paidTime; /** * 工厂方法:创建订单 */ public static Order create(User user, Course course, DiscountStrategy discountStrategy) { if (!course.isAvailable()) { throw new CourseNotAvailableException("课程不可购买"); } Order order = new Order(); order.orderId = generateOrderId(); order.userId = user.getUserId(); order.courseId = course.getCourseId(); order.originalPrice = course.getPrice(); // 应用折扣策略 order.finalPrice = discountStrategy.calculate( course.getPrice(), user.getMemberLevel() ); order.status = OrderStatus.PENDING; order.createTime = LocalDateTime.now(); return order; } /** * 支付订单 */ public void pay(User user) { if (status != OrderStatus.PENDING) { throw new IllegalStateException("订单状态不正确,当前状态:" + status); } if (!user.getUserId().equals(this.userId)) { throw new IllegalStateException("只能支付自己的订单"); } // 扣除用户余额 user.deductBalance(finalPrice); this.status = OrderStatus.PAID; this.paidTime = LocalDateTime.now(); } /** * 取消订单 */ public void cancel() { if (status == OrderStatus.PAID) { throw new IllegalStateException("已支付订单不能取消"); } this.status = OrderStatus.CANCELLED; } public boolean isPaid() { return status == OrderStatus.PAID; } private static Long generateOrderId() { // 简化实现,实际应使用分布式ID生成器 return System.currentTimeMillis(); } }
学习记录
-
学习记录
@Data public class LearningRecord { private Long recordId; private Long userId; private Long courseId; private int totalMinutes; // 课程总时长 private int watchedMinutes; // 已学习时长 private LocalDateTime lastWatchTime; private LocalDateTime createTime; /** * 工厂方法:创建学习记录 */ public static LearningRecord create(User user, Course course) { LearningRecord record = new LearningRecord(); record.recordId = generateRecordId(); record.userId = user.getUserId(); record.courseId = course.getCourseId(); record.totalMinutes = course.getDurationInMinutes(); record.watchedMinutes = 0; record.createTime = LocalDateTime.now(); return record; } /** * 更新学习进度 */ public void updateProgress(int newWatchedMinutes) { if (newWatchedMinutes < 0) { throw new IllegalArgumentException("学习时长不能为负数"); } if (newWatchedMinutes > this.watchedMinutes) { this.watchedMinutes = newWatchedMinutes; this.lastWatchTime = LocalDateTime.now(); } } /** * 获取学习进度对象(值对象) */ public Progress getProgress() { return new Progress(watchedMinutes, totalMinutes); } /** * 是否有资格获得证书(完成度>=80%) */ public boolean isQualifiedForCertificate() { return getProgress().getPercentage() >= 80; } private static Long generateRecordId() { return System.currentTimeMillis(); } }
值对象
-
价格
@Data public class Price { private final BigDecimal amount; private final String currency; public Price(BigDecimal amount, String currency) { if (amount == null || amount.compareTo(BigDecimal.ZERO) < 0) { throw new IllegalArgumentException("金额不能为空或负数"); } this.amount = amount.setScale(2, RoundingMode.HALF_UP); this.currency = currency == null ? "CNY" : currency; } public Price(String amount) { this(new BigDecimal(amount), "CNY"); } /** * 减法操作,返回新对象 */ public Price subtract(Price other) { checkCurrency(other); return new Price(this.amount.subtract(other.amount), this.currency); } /** * 乘法操作(用于折扣计算) */ public Price multiply(BigDecimal rate) { return new Price(this.amount.multiply(rate), this.currency); } public boolean lessThan(Price other) { checkCurrency(other); return this.amount.compareTo(other.amount) < 0; } public boolean greaterThanOrEqual(Price other) { checkCurrency(other); return this.amount.compareTo(other.amount) >= 0; } private void checkCurrency(Price other) { if (!this.currency.equals(other.currency)) { throw new IllegalArgumentException("货币类型不一致"); } } } -
学习进度
@Data public class Progress { private final int watchedMinutes; private final int totalMinutes; public Progress(int watchedMinutes, int totalMinutes) { if (watchedMinutes < 0 || totalMinutes <= 0) { throw new IllegalArgumentException("时长参数不合法"); } this.watchedMinutes = Math.min(watchedMinutes, totalMinutes); this.totalMinutes = totalMinutes; } /** * 获取完成百分比 */ public int getPercentage() { return (int) ((watchedMinutes * 100.0) / totalMinutes); } public boolean isCompleted() { return watchedMinutes >= totalMinutes; } }
6. 选择合适的设计模式(按需)
策略模式 - 折扣计算
-
折扣策略接口
public interface DiscountStrategy { Price calculate(Price originalPrice, MemberLevel memberLevel); } -
会员折扣策略
public class MemberDiscountStrategy implements DiscountStrategy { @Override public Price calculate(Price originalPrice, MemberLevel memberLevel) { BigDecimal discountRate = memberLevel.getDiscountRate(); return originalPrice.multiply(discountRate); } } -
促销折扣策略(可与会员折扣叠加)
public class PromotionDiscountStrategy implements DiscountStrategy { private final BigDecimal promotionRate; private final MemberDiscountStrategy memberDiscountStrategy; public PromotionDiscountStrategy(BigDecimal promotionRate) { this.promotionRate = promotionRate; this.memberDiscountStrategy = new MemberDiscountStrategy(); } @Override public Price calculate(Price originalPrice, MemberLevel memberLevel) { // 先应用会员折扣 Price memberPrice = memberDiscountStrategy.calculate(originalPrice, memberLevel); // 再应用促销折扣 return memberPrice.multiply(promotionRate); } } -
无折扣
public class NoDiscountStrategy implements DiscountStrategy { @Override public Price calculate(Price originalPrice, MemberLevel memberLevel) { return originalPrice; } }
工厂模式 - 课程创建
-
课程工厂接口
public interface CourseFactory { Course createCourse(CourseCreateDTO dto); } -
视频课程工厂
public class VideoCourseFactory implements CourseFactory { @Override public Course createCourse(CourseCreateDTO dto) { VideoCourse course = new VideoCourse( dto.getCourseId(), dto.getTitle(), dto.getPrice() ); // 添加视频 if (dto.getVideos() != null) { for (VideoDTO videoDTO : dto.getVideos()) { course.addVideo(convertToVideo(videoDTO)); } } return course; } private Video convertToVideo(VideoDTO dto) { return new Video(dto.getVideoId(), dto.getTitle(), dto.getDuration(), dto.getUrl()); } } -
直播课程工厂
public class LiveCourseFactory implements CourseFactory { @Override public Course createCourse(CourseCreateDTO dto) { return new LiveCourse( dto.getCourseId(), dto.getTitle(), dto.getPrice(), dto.getLiveStartTime(), dto.getEstimatedDuration() ); } } -
专栏课程工厂
public class ColumnCourseFactory implements CourseFactory { @Override public Course createCourse(CourseCreateDTO dto) { return new ColumnCourse(dto.getCourseId(), dto.getTitle(), dto.getPrice()); } } -
课程工厂管理器
public class CourseFactoryManager { private static final Map<CourseType, CourseFactory> factories = new HashMap<>(); static { factories.put(CourseType.VIDEO, new VideoCourseFactory()); factories.put(CourseType.LIVE, new LiveCourseFactory()); factories.put(CourseType.EXCLUSIVE, new ColumnCourseFactory()); } public static Course createCourse(CourseType type, CourseCreateDTO dto) { CourseFactory factory = factories.get(type); if (factory == null) { throw new IllegalArgumentException("不支持的课程类型:" + type); } return factory.createCourse(dto); } }
观察者模式 - 学习进度事件
-
学习进度监听器
public interface ProgressListener { void onProgressUpdated(ProgressEvent event); } -
学习进度事件
@Data public class ProgressEvent { private final Long userId; private final Long courseId; private final Progress progress; private final LocalDateTime eventTime; public ProgressEvent(Long userId, Long courseId, Progress progress) { this.userId = userId; this.courseId = courseId; this.progress = progress; this.eventTime = LocalDateTime.now(); } } -
证书颁发监听器
public class CertificateListener implements ProgressListener { private final CertificateService certificateService; public CertificateListener(CertificateService certificateService) { this.certificateService = certificateService; } @Override public void onProgressUpdated(ProgressEvent event) { // 当学习进度达到80%时自动颁发证书 if (event.getProgress().getPercentage() >= 80) { certificateService.issueCertificate( event.getUserId(), event.getCourseId() ); } } } -
学习统计监听器
public class StatisticsListener implements ProgressListener { @Override public void onProgressUpdated(ProgressEvent event) { // 记录学习统计数据 System.out.println("用户 " + event.getUserId() + " 的课程 " + event.getCourseId() + " 学习进度更新为:" + event.getProgress()); } } -
进度追踪器
public class ObservableProgressTracker implements ProgressTracker { private final Map<Long, LearningRecord> records = new ConcurrentHashMap<>(); private final List<ProgressListener> listeners = new CopyOnWriteArrayList<>(); public void addListener(ProgressListener listener) { listeners.add(listener); } public void removeListener(ProgressListener listener) { listeners.remove(listener); } @Override public void recordProgress(Long userId, Long courseId, int watchedMinutes) { Long key = generateKey(userId, courseId); LearningRecord record = records.get(key); if (record != null) { record.updateProgress(watchedMinutes); // 通知所有监听器 ProgressEvent event = new ProgressEvent(userId, courseId, record. getProgress()); notifyListeners(event); } } @Override public Progress getProgress(Long userId, Long courseId) { Long key = generateKey(userId, courseId); LearningRecord record = records.get(key); return record != null ? record.getProgress() : null; } @Override public boolean isQualifiedForCertificate(Long userId, Long courseId) { Long key = generateKey(userId, courseId); LearningRecord record = records.get(key); return record != null && record.isQualifiedForCertificate(); } public void addRecord(LearningRecord record) { Long key = generateKey(record.getUserId(), record.getCourseId()); records.put(key, record); } private void notifyListeners(ProgressEvent event) { for (ProgressListener listener : listeners) { try { listener.onProgressUpdated(event); } catch (Exception e) { // 记录异常,但不影响其他监听器 System.err.println("监听器执行异常:" + e.getMessage()); } } } private Long generateKey(Long userId, Long courseId) { return userId * 1000000L + courseId; } }
7. 先写测试再实现
-
用户购买课程的测试用例
public class CoursePurchaseTest { private User vipUser; private User svipUser; private Course videoCourse; private DiscountStrategy discountStrategy; @BeforeEach public void setUp() { // 准备测试数据 vipUser = new User(1L, "张三", "zhangsan@example.com", new VipMember(), new Price("1000")); svipUser = new User(2L, "李四", "lisi@example. com", new SVipMember(), new Price("1000")); videoCourse = new VideoCourse(100L, "Java进阶课程", new Price("100")); ((VideoCourse) videoCourse).addVideo(new Video(3L, "Java进阶视频", 3523, "https://www.google.com")); ((VideoCourse) videoCourse).publish(); discountStrategy = new MemberDiscountStrategy(); } @Test public void testVipUserPurchaseCourse() { // VIP用户购买课程,应享受9折优惠 Order order = Order.create(vipUser, videoCourse, discountStrategy); assertEquals(new Price("90"), order.getFinalPrice()); // 100 * 0.9 Assertions.assertEquals(OrderStatus.PENDING, order.getStatus()); } @Test public void testSVipUserPurchaseCourse() { // SVIP用户购买课程,应享受8折优惠 Order order = Order.create(svipUser, videoCourse, discountStrategy); assertEquals(new Price("80"), order.getFinalPrice()); // 100 * 0.8 } @Test public void testPayOrder_Success() { // 测试支付成功场景 Order order = Order.create(vipUser, videoCourse, discountStrategy); Price balanceBefore = vipUser.getBalance(); order.pay(vipUser); assertEquals(OrderStatus.PAID, order.getStatus()); assertEquals(balanceBefore.subtract(order.getFinalPrice()), vipUser.getBalance()); assertNotNull(order.getPaidTime()); } @Test public void testPayOrder_InsufficientBalance() { // 测试余额不足场景 User poorUser = new User(3L, "王五", "wangwu@example.com", new VipMember(), new Price("50")); Order order = Order.create(poorUser, videoCourse, discountStrategy); assertThrowsExactly(InsufficientBalanceException.class, () -> order.pay(poorUser)); // 应抛出余额不足异常 } @Test public void testPayOrder_AlreadyPaid() { // 测试重复支付场景 Order order = Order.create(vipUser, videoCourse, discountStrategy); order.pay(vipUser); assertThrowsExactly(IllegalStateException.class, () -> order.pay(vipUser)); } } -
学习进度测试
public class LearningProgressTest { private User user; private Course course; private ObservableProgressTracker tracker; private CertificateService certificateService; @BeforeEach public void setUp() { user = new User(1L, "张三", "test@example.com", new VipMember(), new Price("1000")); course = new VideoCourse(100L, "测试课程", new Price("100")); ((VideoCourse) course).addVideo(new Video(100L, "测试视频", 100, "https://www.google.com")); certificateService = mock(CertificateService.class); tracker = new ObservableProgressTracker(); tracker.addListener(new CertificateListener(certificateService)); } @Test public void testRecordProgress() { LearningRecord record = LearningRecord.create(user, course); tracker.addRecord(record); tracker.recordProgress(user.getUserId(), course.getCourseId(), 30); Progress progress = tracker.getProgress(user.getUserId(), course.getCourseId()); assertEquals(30, progress.getWatchedMinutes()); } @Test public void testCertificateIssuedWhenQualified() { // 测试当学习进度达到80%时自动颁发证书 VideoCourse videoCourse = (VideoCourse) course; LearningRecord record = LearningRecord.create(user, videoCourse); tracker.addRecord(record); // 学习80分钟(总时长100分钟) tracker.recordProgress(user.getUserId(), course.getCourseId(), 80); // 验证证书服务被调用 verify(certificateService, times(1)).issueCertificate( user.getUserId(), course.getCourseId() ); } }
8. 实现最小可行版本
-
课程购买服务
public class CoursePurchaseService { private final DiscountStrategy discountStrategy; private final ProgressTracker progressTracker; public CoursePurchaseService(DiscountStrategy discountStrategy, ProgressTracker progressTracker) { this.discountStrategy = discountStrategy; this.progressTracker = progressTracker; } /** * 用户购买课程的完整流程 */ public Order purchaseCourse(User user, Course course) { // 1. 验证课程访问权限 if (!course.canAccess(user)) { throw new AccessDeniedException("您的会员等级无法购买此课程"); } // 2. 创建订单 Order order = Order.create(user, course, discountStrategy); // 3. 检查余额并支付 if (!user.canPurchase(course, order.getFinalPrice())) { throw new InsufficientBalanceException("余额不足,无法完成购买"); } order.pay(user); // 4. 创建学习记录 LearningRecord record = LearningRecord.create(user, course); ((ObservableProgressTracker) progressTracker).addRecord(record); System.out.println("购买成功!用户:" + user.getUserName() + ",课程:" + course.getTitle() + ",实付:" + order.getFinalPrice()); return order; } } -
购买服务完整测试
public class OnlineLearningSystemDemo { public static void main(String[] args) { // 1. 准备数据 User ordinaryUser = new User(1L, "普通用户", "ordinary@example.com", new OrdinaryMember(), new Price("500")); User vipUser = new User(2L, "VIP用户", "vip@example.com", new VipMember(), new Price("1000")); User svipUser = new User(3L, "SVIP用户", "svip@example.com", new SVipMember(), new Price("2000")); // 创建课程 VideoCourse javaCourse = new VideoCourse(101L, "Java从入门到精通", new Price("299")); javaCourse.addVideo(new Video(1L, "第1章:Java基础", 120, "url1")); javaCourse.addVideo(new Video(2L, "第2章:面向对象", 150, "url2")); javaCourse.publish(); LiveCourse algorithmCourse = new LiveCourse(102L, "算法训练营", new Price("599"), LocalDateTime.now().minusHours(1), 300); algorithmCourse.endLive(); algorithmCourse.publish(); ColumnCourse architectureCourse = new ColumnCourse(103L, "架构师成长之路", new Price("999")); architectureCourse.addArticle(new Article(1L, "微服务架构设计")); architectureCourse.addArticle(new Article(2L, "分布式系统实践")); architectureCourse.publish(); // 2. 初始化服务 ObservableProgressTracker tracker = new ObservableProgressTracker(); CertificateService certService = new CertificateService(); tracker.addListener(new CertificateListener(certService)); tracker.addListener(new StatisticsListener()); CoursePurchaseService purchaseService = new CoursePurchaseService( new MemberDiscountStrategy(), tracker ); // 3. 场景一:VIP用户购买视频课程 System.out.println("\n========== 场景一:VIP用户购买课程 =========="); try { Order order1 = purchaseService.purchaseCourse(vipUser, javaCourse); System.out.println("原价:" + javaCourse.getPrice()); System.out.println("实付:" + order1.getFinalPrice() + " (享受9折优惠)"); System.out.println("剩余余额:" + vipUser.getBalance()); } catch (Exception e) { System.out.println("购买失败:" + e.getMessage()); } // 4. 场景二:普通用户尝试购买专属课程(应失败) System.out.println("\n========== 场景二:普通用户购买专属课程 =========="); try { purchaseService.purchaseCourse(ordinaryUser, architectureCourse); } catch (AccessDeniedException e) { System.out.println("购买失败:" + e.getMessage()); System.out.println("提示:升级为SVIP即可访问专属课程"); } // 5. 场景三:SVIP用户购买专属课程 System.out.println("\n========== 场景三:SVIP用户购买专属课程 =========="); try { Order order3 = purchaseService.purchaseCourse(svipUser, architectureCourse); System.out.println("原价:" + architectureCourse.getPrice()); System.out.println("实付:" + order3.getFinalPrice() + " (享受8折优惠)"); } catch (Exception e) { System.out.println("购买失败:" + e.getMessage()); } // 6. 场景四:学习进度追踪与证书颁发 System.out.println("\n========== 场景四:学习进度追踪 =========="); Long userId = vipUser.getUserId(); Long courseId = javaCourse.getCourseId(); tracker.recordProgress(userId, courseId, 50); // 学习50分钟 System.out.println("当前进度:" + tracker.getProgress(userId, courseId)); tracker.recordProgress(userId, courseId, 150); // 累计学习150分钟 System.out.println("当前进度:" + tracker.getProgress(userId, courseId)); tracker.recordProgress(userId, courseId, 220); // 累计学习220分钟(超过80%) System.out.println("当前进度:" + tracker.getProgress(userId, courseId)); System.out.println("是否获得证书:" + tracker.isQualifiedForCertificate(userId, courseId)); // 7. 场景五:使用促销折扣策略 System.out.println("\n========== 场景五:双十一促销 =========="); CoursePurchaseService promotionService = new CoursePurchaseService( new PromotionDiscountStrategy(new BigDecimal("0.8")), // 促销期间额外8折 tracker ); User newVip = new User(4L, "新VIP", "newvip@example.com", new VipMember(), new Price("1000")); try { Order order5 = promotionService.purchaseCourse(newVip, algorithmCourse); System.out.println("原价:" + algorithmCourse.getPrice()); System.out.println("VIP折扣后:" + algorithmCourse.getPrice().multiply(new BigDecimal("0.9"))); System.out.println("促销折扣后:" + order5.getFinalPrice() + " (VIP 9折 + 促销 8折)"); } catch (Exception e) { System.out.println("购买失败:" + e.getMessage()); } } }
实践总结
通过这个在线课程学习系统的实例,我们完整地应用了面向对象的设计思想:
1. 封装变化
- 将会员等级抽象为接口,不同等级的实现封装在具体类中
- 将课程类型抽象为基类,不同课程的特性封装在子类中
- 将折扣计算封装为策略模式,支持灵活切换
2. 支持拓展
- 通过继承机制轻松添加新的会员等级(如企业会员)
- 通过工厂模式轻松添加新的课程类型(如题库课程)
- 通过观察者模式轻松添加新的事件监听器
3. 遵循原则
- 单一职责:User管理用户信息,Order管理订单,LearningRecord管理学习进度
- 开闭原则:添加新的折扣策略无需修改现有代码
- 里氏替换:所有子类(VIP、SVIP)都可以替换父类(MemberLevel)
- 依赖倒置:业务逻辑依赖DiscountStrategy接口,而非具体实现
- 接口隔离:Course接口只定义课程必需的方法
- 迪米特法则:Service层只与聚合根交互,不直接操作内部对象
4. 应用模式
- 策略模式:折扣计算
- 工厂模式:课程创建
- 观察者模式:学习进度事件
- 模板方法:课程权限检查
这个实例展示了如何将面向对象的理论知识转化为实际的代码实践,帮助我们构建出高内聚、低耦合、易拓展、易维护的系统。