作为一名常年和系统设计打交道的开发者,每次都会被 OOD(面向对象设计)的六大原则戳中 —— 这六条看似抽象的规则,其实是避开 “代码越写越烂” 陷阱的核心心法。
很多人刚接触时会觉得 “这些原则太理论,实际开发用不上”,但只要真正理解并落地,你会发现代码的扩展性、可维护性会发生质的变化。今天就用大白话拆解这六大原则,每个原则都配了 Java 代码示例,从概念到实践一步到位,新手也能看懂。
一、开闭原则:对扩展开放,对修改关闭
核心概念:这是 OOD 原则的 “老大”,核心思想是 —— 当需要给系统新增功能时,尽量通过 “扩展已有代码” 实现,而不是 “修改已有代码”。这样能避免改动旧代码时,不小心引入新 Bug,也能让系统更稳定。
举个例子:比如你开发了一个电商系统的 “订单折扣” 功能,初期只有 “会员折扣”,后来要加 “节日折扣”,如果一开始就遵循开闭原则,就不用改原来的会员折扣代码,直接加个新的折扣类就行。
下面给出一个示例代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
| public interface Discount { double calculateDiscount(double originalPrice); }
public class MemberDiscount implements Discount { @Override public double calculateDiscount(double originalPrice) { return originalPrice * 0.9; } }
public class HolidayDiscount implements Discount { @Override public double calculateDiscount(double originalPrice) { return originalPrice * 0.8; } }
public class OrderService { public double calculateFinalPrice(double originalPrice, Discount discount) { return discount.calculateDiscount(originalPrice); } }
public class Test { public static void main(String[] args) { OrderService orderService = new OrderService(); double originalPrice = 100; double memberPrice = orderService.calculateFinalPrice(originalPrice, new MemberDiscount()); System.out.println("会员价:" + memberPrice); double holidayPrice = orderService.calculateFinalPrice(originalPrice, new HolidayDiscount()); System.out.println("节日价:" + holidayPrice); } }
|
从上面的代码中我们可以看到,对于折扣这个对象我们抽象出一个接口,然后对于不同类型的折扣通过接口的实现了创建具体的类。这样一来,创建订单时,我们只要区分现在要用的折扣是哪个类型的就可以了。以后要新增一个折扣类型(比如“618折扣”)只要扩展Discount这个接口就行了。
二、里氏替换原则:子类能无缝替代父类,且不破坏程序逻辑
核心概念:简单说就是 “子类是父类的加强版,但不能颠覆父类的原有功能”。如果一个程序里用了父类对象,把它换成子类对象后,程序还能正常跑,这就符合里氏替换;反之如果换了之后程序崩了,那就是违反了这个原则。
最典型的反例就是 “正方形不是长方形”—— 如果把长方形的 “宽” 和 “长” 分开设置,子类正方形强制让宽 = 长,那用子类替换父类后,修改长或宽的逻辑就会出错。
我们看下面的代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58
| public class Bird { protected double speed; public double calculateFlyTime(double distance) { return distance / speed; } public void setSpeed(double speed) { this.speed = speed; } }
public class Sparrow extends Bird { public Sparrow() { this.speed = 50; } }
public class Eagle extends Bird { public Eagle() { this.speed = 150; } public void dive() { System.out.println("老鹰正在俯冲捕猎!"); } }
public class Test { public static void main(String[] args) { double distance = 100; Bird bird = new Bird(); bird.setSpeed(80); System.out.println("普通鸟飞行时间:" + bird.calculateFlyTime(distance)); Bird sparrow = new Sparrow(); System.out.println("麻雀飞行时间:" + sparrow.calculateFlyTime(distance)); Bird eagle = new Eagle(); System.out.println("老鹰飞行时间:" + eagle.calculateFlyTime(distance)); ((Eagle) eagle).dive(); } }
|
在上面的代码中,Sparrow和Eagle都继承自Bird这个类,是Bird的“特例”,但是本质上她们都是鸟,所以都有fly这个能力。因此就算是在另一个地方把Sparrow替换成Bird,程序也不会报错,因为它没有破坏Bird的逻辑。
三、依赖倒置原则:依赖抽象,不依赖具体实现
核心概念:这条原则其实是开闭原则的 “支撑”,核心是 “高低层模块都要依赖抽象,抽象不能依赖具体”。简单说就是 —— 不要让你的代码依赖某个具体的类,而是依赖接口或抽象类,这样高层模块(比如服务类)就不会被低层模块(比如工具类)的变动影响。
比如你开发一个 “消息通知” 功能,高层模块是 “通知服务”,低层模块是 “短信通知”“邮件通知”。如果通知服务直接依赖 “短信通知” 这个具体类,后来要加 “邮件通知”,就得改通知服务的代码;但如果依赖 “通知接口”,加新功能时只需要加个接口实现类就行。
我们还是来看代码示范:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
| public interface Notification { void send(String content); }
public class SmsNotification implements Notification { @Override public void send(String content) { System.out.println("通过短信发送:" + content); } }
public class EmailNotification implements Notification { @Override public void send(String content) { System.out.println("通过邮件发送:" + content); } }
public class NotificationService { private Notification notification; public NotificationService(Notification notification) { this.notification = notification; } public void notifyUser(String content) { notification.send(content); } }
public class Test { public static void main(String[] args) { NotificationService smsService = new NotificationService(new SmsNotification()); smsService.notifyUser("您的验证码是123456"); NotificationService emailService = new NotificationService(new EmailNotification()); emailService.notifyUser("您有一封新邮件,请查收"); } }
|
在上面的代码中:NotificationService 的notifyUser实际上是调用了Notification的send方法,但是由于它是一个抽象方法,具体的实现都在不同的通知类中,因此当服务方法调用时,只要指派不同的通知类即可。
这里的核心是NotificationService 依赖的只是 Notification 这个抽象接口,这样方便后续的扩展。假设未来如果有了新的通知类型,比如微信通知,那么创建一个WeChatNotification并实现Notification即可。
四、组合 / 聚合原则:优先用组合 / 聚合,少用继承
核心概念:继承的问题在于 “强耦合”—— 子类会依赖父类的实现,如果父类改了,子类可能跟着崩;而组合 / 聚合是 “弱耦合”—— 一个类通过 “包含另一个类的对象” 来使用其功能,双方可以独立变化。所以设计时要优先选组合 / 聚合,实在适合继承(比如子类是父类的 “is-a” 关系)再用继承。
比如 “汽车” 和 “发动机” 的关系:汽车需要发动机才能跑,但汽车不是 “继承” 发动机(因为汽车不是发动机的一种),而是 “组合” 发动机(汽车里包含一个发动机对象);而 “轿车” 和 “汽车” 是 “is-a” 关系,适合用继承。
我们来看代码的示范:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64
| public class Engine { public void start() { System.out.println("发动机启动,开始提供动力"); } public void stop() { System.out.println("发动机停止,动力中断"); } }
public class Car { private Engine engine; public Car() { this.engine = new Engine(); } public void startCar() { engine.start(); System.out.println("汽车成功启动,可以行驶"); } public void stopCar() { engine.stop(); System.out.println("汽车成功停止"); } }
public class Sedan extends Car { public void autoParking() { System.out.println("轿车正在自动泊车"); } }
public class Test { public static void main(String[] args) { Sedan sedan = new Sedan(); sedan.startCar(); sedan.autoParking(); sedan.stopCar(); } }
|
五、接口隔离原则:接口要小而专,不要大而全
核心概念:这条原则是说 —— 不要设计一个 “万能接口”,把所有功能都塞进去,而是要把接口拆成多个 “专用接口”,让类只实现自己需要的接口。 这样能避免 “类实现了接口,但被迫重写不需要的方法”(比如空实现),也能减少接口变动的影响。
比如 “用户系统” 里,普通用户只需要 “登录、注册” 功能,管理员需要 “用户管理、权限管理” 功能。如果设计一个 “UserInterface” 包含所有 4 个方法,普通用户类就得空实现 “用户管理、权限管理”,这就很不合理;拆成两个接口后,各自实现需要的方法即可。
还是来看示范代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64
| public interface NormalUserService { void login(String username, String password); void register(String username, String password); }
public interface AdminService { void manageUser(String userId); void managePermission(String roleId); }
public class NormalUser implements NormalUserService { @Override public void login(String username, String password) { System.out.println(username + "登录成功"); } @Override public void register(String username, String password) { System.out.println(username + "注册成功"); } }
public class Admin implements NormalUserService, AdminService { @Override public void login(String username, String password) { System.out.println("管理员" + username + "登录成功"); } @Override public void register(String username, String password) { System.out.println("管理员账号需通过审批注册"); } @Override public void manageUser(String userId) { System.out.println("成功管理用户:" + userId); } @Override public void managePermission(String roleId) { System.out.println("成功管理角色权限:" + roleId); } }
public class Test { public static void main(String[] args) { NormalUser user = new NormalUser(); user.login("zhangsan", "123456"); user.register("lisi", "654321"); Admin admin = new Admin(); admin.login("admin", "admin123"); admin.manageUser("1001"); admin.managePermission("admin_role"); } }
|
六、最少知识原则:一个类只和 “直接朋友” 通信,别和 “陌生人” 说话
核心概念:也叫 “迪米特法则”,核心是 “降低类之间的耦合”—— 一个类应该只和它的 “直接朋友”(比如成员变量、方法参数、返回值里的类)交互,不要主动去调用 “朋友的朋友” 的方法。这样能减少类之间的依赖,让系统更稳定。
比如 “老板要统计部门的员工数量”:老板的直接朋友是 “部门”,部门的直接朋友是 “员工”。如果老板直接去遍历部门里的员工列表,就是和 “员工”(陌生人)通信了;正确的做法是老板让部门自己统计人数,然后把结果返回给老板。
看下面的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
| public class Employee { private String name; private int age; public Employee(String name, int age) { this.name = name; this.age = age; } }
public class Department { private List<Employee> employees; public Department() { employees = new ArrayList<>(); employees.add(new Employee("张三", 25)); employees.add(new Employee("李四", 28)); employees.add(new Employee("王五", 30)); } public int getEmployeeCount() { return employees.size(); } }
public class Boss { public void countDepartmentEmployees(Department department) { int count = department.getEmployeeCount(); System.out.println("当前部门员工数量:" + count); } }
public class Test { public static void main(String[] args) { Boss boss = new Boss(); Department department = new Department(); boss.countDepartmentEmployees(department); } }
|
写在最后:六大原则不是 “教条”,而是 “工具”
很多人学完这些原则后会陷入 “过度设计” 的误区 —— 为了凑齐原则,写了一堆复杂的接口和类,反而让代码更难维护。
其实这六大原则的核心目标是一致的:降低耦合、提高内聚、让代码更易扩展和维护。实际开发中,不需要强行遵守每一条,而是要根据场景灵活取舍(比如简单的工具类,用继承可能比组合更简单)。
记住:好的设计不是 “符合多少原则”,而是 “能解决当前问题,且能应对未来的合理变化”。