研究抽象类与接口

研究背景
面试被问了。 只回答了基本的,感觉还有很多可以聊的
 

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(抽象类),而不是具体的 DogCat,这样如果以后增加 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,而抽象类可以定义 protectedprivate 方法,防止子类暴露不必要的实现细节。

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 提供默认实现,避免代码重复。
  • 子类 MySQLDatabaseMongoDBDatabase 只需实现 executeQuery(),关注自身逻辑


 

8. 💡 结论

  • 合理使用抽象类能降低耦合,提高代码复用性和扩展性
  • 滥用抽象类可能导致僵化,增加系统复杂度和耦合度
  • 如果只是定义行为,推荐使用 interface,而不是 abstract class

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值