研究背景
面试被问了。 只回答了基本的,感觉还有很多可以聊的
1. 什么是耦合
耦合(Coupling) 是指代码之间的依赖关系,耦合度越高,代码的修改影响范围越大,灵活性越低。
- 高耦合:模块之间强依赖,修改一个地方需要改多个地方。
- 低耦合:模块之间关系松散,更容易维护和扩展。
2. 抽象类如何降低耦合?
2.1.1. (1) 通过抽象方法定义行为,提高扩展性
抽象类定义通用行为,让子类实现具体功能,从而降低系统中模块之间的直接依赖。例如:
abstract class Animal {
abstract void makeSound();
}
class Dog extends Animal {
@Override
void makeSound() {
System.out.println("Woof!");
}
}
class Cat extends Animal {
@Override
void makeSound() {
System.out.println("喵喵叫!");
}
}
✅ 低耦合点:
- 业务代码依赖于 Animal(抽象类),而不是具体的
Dog
或Cat
,这样如果以后增加Bird
,不影响现有代码。 - 代码修改时只需要扩展
Animal
,而不需要修改现有逻辑。
2.1.2. (2) 统一行为,避免代码重复
抽象类可以包含默认实现,减少代码重复,同时提供一个可维护的结构。例如:
abstract class Vehicle {
void startEngine() {
System.out.println("启动引擎");
}
abstract void drive();
}
class Car extends Vehicle {
@Override
void drive() {
System.out.println("开车...");
}
}
✅ 低耦合点:
startEngine()
在父类中实现,子类无需重复实现,减少代码冗余,提高复用性。drive()
是抽象方法,不同的子类可以自定义行为,扩展性强。
3. 抽象类如何增加耦合?
虽然抽象类通常能降低耦合,但如果使用不当,可能反而增加系统复杂度和依赖性。
3.1. (1) 过度依赖抽象类,导致僵化
如果系统所有类都继承一个庞大的抽象类,那么一旦抽象类修改,所有子类都受影响。 💡💡🤔🤔🤔
abstract class BaseClass {
void methodA() { }
void methodB() { }
void methodC() { }
}
class SubClass extends BaseClass {
@Override
void methodA() {
System.out.println("重写 methodA() 方法");
}
}
⛔ 高耦合问题:
BaseClass
变更会影响所有子类。- 不需要
methodB()
和methodC()
的子类 也必须继承它们,代码膨胀,不灵活。
3.2. (2) 使用抽象类而不是接口
如果抽象类的目的是只定义行为,而不提供默认实现❗❗,那么使用接口(interface
)可能更好。例如:
abstract class Printer {
abstract void print();
}
class InkjetPrinter extends Printer {
@Override
void print() {
System.out.println("喷墨印刷...");
}
}
改成接口更灵活:
interface Printer {
void print();
}
class InkjetPrinter implements Printer {
@Override
public void print() {
System.out.println("喷墨印刷...");
}
}
✅ 低耦合优点:
- 接口支持多实现(一个类可以实现多个接口,但不能继承多个抽象类)。
- 不受继承结构限制,可以自由组合行为
4. 总结
使用方式 | 对耦合的影响 | 解释 |
定义通用行为,让子类扩展 | ✅ 降低耦合 | 统一规范,减少直接依赖 |
提供默认实现,减少代码重复 | ✅ 降低耦合 | 提供可复用的功能 |
强制子类继承不需要的方法 | ❌ 增加耦合 | 代码冗余,修改影响大 |
使用抽象类而非接口 | ❌ 可能增加耦合 | 接口更灵活,避免不必要的继承 |
5. 增加耦合的场景
5.1. 场景:不同类型的支付方式
假设我们有一个支付系统,支持 微信支付 和 信用卡支付,但信用卡支付不支持 扫码支付,这时如果用抽象类不合理设计,可能会导致子类必须实现一个它不需要的方法。
错误设计:在抽象类中强制所有支付方式都实现 scanQRCode()
❗😵💫😵💫
abstract class Payment {
abstract void pay(double amount);
abstract void scanQRCode(); // 强制所有子类都实现扫码支付
}
class WeChatPay extends Payment {
@Override
void pay(double amount) {
System.out.println("微信支付: " + amount);
}
@Override
void scanQRCode() {
System.out.println("微信扫描二维码...");
}
}
class CreditCardPay extends Payment {
@Override
void pay(double amount) {
System.out.println("信用卡支付: " + amount);
}
@Override
void scanQRCode() {
// 🚨 信用卡支付不支持扫码,但仍然被强制实现!
throw new UnsupportedOperationException("信用卡不支持二维码扫描");
}
}
5.2. 问题分析
CreditCardPay
被迫实现scanQRCode()
,但信用卡支付根本不需要这个方法,只能抛异常或者留空。- 不符合 "单一职责原则"(SRP),
Payment
试图包含所有支付方式的行为,导致子类继承了不适合它们的方法。 - 增加了耦合性,如果以后要加 "指纹支付",抽象类又要加新方法,所有子类都要修改。
5.3. 解决方案
用 "接口隔离原则"(ISP) 进行优化:
interface Payment {
void pay(double amount);
}
interface QRCodePayment {
void scanQRCode();
}
// 只有微信支付实现扫码支付
class WeChatPay implements Payment, QRCodePayment {
@Override
public void pay(double amount) {
System.out.println("微信支付: " + amount);
}
@Override
public void scanQRCode() {
System.out.println("微信扫描二维码...");
}
}
// 信用卡支付不需要扫码接口
class CreditCardPay implements Payment {
@Override
public void pay(double amount) {
System.out.println("信用卡支付: " + amount);
}
}
6. 降低耦合的场景
6.1. 场景1: 模板方法模式
如果多个子类共享通用行为,但又需要部分自定义行为,抽象类可以提供默认实现,避免子类重复实现相同代码。
abstract class FileProcessor {
// 模板方法,定义处理流程
public void processFile() {
openFile();
readFile();
closeFile();
}
void openFile() {
System.out.println("打开文件...");
}
abstract void readFile(); // 让子类实现不同的读取方式
void closeFile() {
System.out.println("关闭文件...");
}
}
// 子类只需关注 readFile 的实现,不需要重复写 openFile 和 closeFile
class TextFileProcessor extends FileProcessor {
@Override
void readFile() {
System.out.println("读取文本文件...");
}
}
class CSVFileProcessor extends FileProcessor {
@Override
void readFile() {
System.out.println("读取 CSV 文件...");
}
}
6.1.1. 优势
✅ 减少代码重复:openFile()
和 closeFile()
只写一次,子类只需关心 readFile()
。
✅ 子类代码更简洁:无需关心文件打开和关闭的细节,提高可维护性。
✅ 修改父类不会影响子类:如果 openFile()
逻辑变更,比如增加日志,所有子类都会自动继承,而不
6.2. 场景2: 需要维护状态(字段)时
接口不能包含实例变量,但抽象类可以。
当一个类需要共享一些状态(如配置、缓存、计数器等)时,使用抽象类可以避免所有子类重复维护状态。
abstract class TaskExecutor {
private int executionCount = 0; // 共享的状态
void execute() {
executionCount++;
System.out.println("执行任务 #" + executionCount);
runTask();
}
abstract void runTask();
}
class EmailTask extends TaskExecutor {
@Override
void runTask() {
System.out.println("发送邮件...");
}
}
如果使用接口:
interface TaskExecutor {
void execute();
}
所有实现类都要自己维护 executionCount
变量,导致代码重复、难以维护。
6.3. 场景3 :需要控制访问权限的时候
接口中的方法默认是 public,而抽象类可以定义 protected 或 private 方法,防止子类暴露不必要的实现细节。
public abstract class DataProcessor {
// 让子类调用,但不希望外部调用
protected void fetchData() {
System.out.println("获取数据...");
}
public abstract void processData();
}
public class XMLDataProcessor extends DataProcessor {
@Override
public void processData() {
fetchData(); // 子类可以调用,但外部代码不能调用
System.out.println("处理XML数据...");
}
}
如果用接口:
java
复制编辑
interface DataProcessor {
void fetchData(); // 必须是 public,不能限制访问权限
void processData();
}
🔴 问题:fetchData()
变成了 public
,外部代码也能调用,暴露了内部实现,降低了封装性。
7. 接口 + 抽象类结合使用,灵活设计架构
接口(Interface)+ 抽象类(Abstract Class)结合使用是一种常见的面向对象设计最佳实践,能够同时享受接口的灵活性和抽象类的代码复用。
7.1. 示例: 消息发送系统
业务场景:我们要实现一个消息发送系统,支持 邮件(Email)、短信(SMS) 和 推送通知(Push Notification),但它们有不同的实现方式。
7.1.1. 1️⃣ 设计接口(定义行为)
interface MessageSender {
void sendMessage(String message);
}
✅ 接口的作用:定义通用行为,支持多种实现方式。
7.1.2. 2️⃣ 设计抽象类(提供部分默认实现)
abstract class AbstractMessageSender implements MessageSender {
@Override
public void sendMessage(String message) {
logMessage(message); // 统一的日志逻辑
doSend(message);
}
private void logMessage(String message) {
System.out.println("Logging: " + message);
}
protected abstract void doSend(String message); // 子类必须实现
}
✅ 抽象类的作用:
- 统一
sendMessage()
逻辑,避免子类重复代码。 logMessage()
作为私有方法,防止子类误修改。doSend()
让子类具体实现不同的消息发送方式。
7.1.3. 3️⃣ 具体实现类
class EmailSender extends AbstractMessageSender {
@Override
protected void doSend(String message) {
System.out.println("发送邮件: " + message);
}
}
class SmsSender extends AbstractMessageSender {
@Override
protected void doSend(String message) {
System.out.println("发送短信: " + message);
}
}
✅ 子类的作用:只关注 doSend()
方法,不需要关心日志记录逻辑。
7.1.4. 4️⃣ 使用
public class Main {
public static void main(String[] args) {
MessageSender emailSender = new EmailSender();
MessageSender smsSender = new SmsSender();
emailSender.sendMessage("Hello, Email!");
smsSender.sendMessage("Hello, SMS!");
}
}
7.1.5. 效果
- 接口
Database
定义通用操作,提高灵活性。 - 抽象类
AbstractDatabase
提供默认实现,避免代码重复。 - 子类
MySQLDatabase
、MongoDBDatabase
只需实现executeQuery()
,关注自身逻辑
8. 💡 结论
- 合理使用抽象类能降低耦合,提高代码复用性和扩展性。
- 滥用抽象类可能导致僵化,增加系统复杂度和耦合度。
- 如果只是定义行为,推荐使用
interface
,而不是abstract class
。