CodeGym /课程 /JAVA 25 SELF /Java 架构中的接口与设计模式

Java 架构中的接口与设计模式

JAVA 25 SELF
第 20 级 , 课程 4
可用

1. 接口即契约:架构之基

在 Java(以及其他语言)中,接口不仅仅是一组方法。这是一份契约:任何实现该接口的类都承诺支持特定的行为。接口规定的是应该实现什么,而不是如何实现

为什么这很重要?

  • 分层与职责分离。借助接口,我们可以把“做什么”和“怎么做”分离。例如,如果你有接口 PaymentService,那么不同的实现可以处理银行卡、PayPal 或加密货币的支付,但调用 pay() 的代码无需关心这些细节。
  • 灵活与可扩展性。你可以在不修改其余代码的情况下添加新的接口实现。这对大型团队和长生命周期项目尤为重要。
  • 可测试性。借助接口,可以轻松将实现替换为测试用的模拟(mock),而不触动主业务代码。

示例:服务层与 DAO

来看一个业务应用中的经典例子。假设我们有一个用于处理用户的接口:


public interface UserRepository {
    User findById(int id);
    void save(User user);
}

在不同场景下,我们可以用不同方式实现该接口:

  • DatabaseUserRepository — 将用户存储在数据库中。
  • InMemoryUserRepository — 将用户存储在内存中(便于测试)。
  • FileUserRepository — 将用户保存到文件中。

与用户交互的代码只依赖接口:

public class UserService {
    private final UserRepository userRepository;

    // 通过构造函数进行依赖注入
    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    public void registerUser(User user) {
        userRepository.save(user);
    }
}

现在我们可以在不修改服务代码的情况下轻松替换 UserRepository 的实现。

2. Dependency Injection(依赖注入)与接口的作用

Dependency InjectionDI,依赖注入)是一种架构手法:把依赖(例如接口的具体实现)从外部传入对象,通常通过构造函数或 setter。它使应用更灵活、易于测试且便于扩展。

为什么接口对 DI 很重要?

如果在代码里写死具体实现,替换就会很困难。借助接口,我们无需改动主代码就能替换为任意实现。

依赖注入示例

public interface NotificationSender {
    void send(String message);
}

public class EmailNotificationSender implements NotificationSender {
    @Override
    public void send(String message) {
        System.out.println("发送 Email: " + message);
    }
}

public class SmsNotificationSender implements NotificationSender {
    @Override
    public void send(String message) {
        System.out.println("发送 SMS: " + message);
    }
}

// 使用 NotificationSender 的类
public class NotificationService {
    private final NotificationSender sender;

    public NotificationService(NotificationSender sender) {
        this.sender = sender;
    }

    public void notifyUser(String message) {
        sender.send(message);
    }
}

现在你可以轻松测试 NotificationService,例如传入一个“桩对象”来替代真实的消息发送器。

3. 设计模式与接口

接口不仅关乎架构,也与设计模式密切相关。很多模式离开接口就无法实现。下面来看最常见的几种。

Observer(观察者)

Observer 是一种模式,使一个对象(被观察者)能够在其状态变化时通知其他对象(观察者)。

UML 图(简化):

+------------------+         +------------------------+
|   Subject        |<------->|   Observer             |
+------------------+         +------------------------+
| +addObserver()   |         | +update()              |
| +removeObserver()|         +------------------------+
| +notifyObservers()|
+------------------+

代码示例:

import java.util.ArrayList;
import java.util.List;

// 观察者接口
public interface Observer {
    void update(String event);
}

// 主体(被观察者)接口
public interface Subject {
    void addObserver(Observer observer);
    void removeObserver(Observer observer);
    void notifyObservers(String event);
}

// 主体实现
public class NewsAgency implements Subject {
    private List<Observer> observers = new ArrayList<>();

    @Override
    public void addObserver(Observer observer) {
        observers.add(observer);
    }

    @Override
    public void removeObserver(Observer observer) {
        observers.remove(observer);
    }

    @Override
    public void notifyObservers(String event) {
        for (Observer observer : observers) {
            observer.update(event);
        }
    }
}

// 观察者实现
public class NewsReader implements Observer {
    private String name;

    public NewsReader(String name) {
        this.name = name;
    }

    @Override
    public void update(String event) {
        System.out.println(name + " 收到新闻: " + event);
    }
}

// 运行示例的主类
public class ObserverExample {
    public static void main(String[] args) {
        // 创建“新闻社”(Subject)
        NewsAgency agency = new NewsAgency();

        // 创建观察者
        Observer alice = new NewsReader("Alice");
        Observer bob = new NewsReader("Bob");

        // 订阅观察者
        agency.addObserver(alice);
        agency.addObserver(bob);

        // 发送新闻
        agency.notifyObservers("Java 新版本发布!");

        // 移除一位观察者并再发送一条新闻
        agency.removeObserver(bob);
        agency.notifyObservers("给订阅者的下一条新闻");
    }
}

输出:

Alice 收到新闻: Java 新版本发布!
Bob 收到新闻: Java 新版本发布!

Strategy(策略)

Strategy 是一种模式,允许在运行时选择行为算法,而不修改客户端代码。

UML 图(简化):

+------------------+
|   Context        |
+------------------+
| -strategy: Strat.|
| +setStrategy()   |
| +execute()       |
+------------------+
         |
         v
+------------------+
|   Strategy       |<-------------------------+
+------------------+                          |
| +execute()       |                          |
+------------------+                          |
         ^                                    |
         |                                    |
+------------------+   +------------------+   |
|   ConcreteA      |   |   ConcreteB      |---+
+------------------+   +------------------+
| +execute()       |   | +execute()       |
+------------------+   +------------------+

代码示例:

public interface PaymentStrategy {
    void pay(int amount);
}

public class CreditCardPayment implements PaymentStrategy {
    @Override
    public void pay(int amount) {
        System.out.println("支付 " + amount + " CNY,使用银行卡");
    }
}

public class PaypalPayment implements PaymentStrategy {
    @Override
    public void pay(int amount) {
        System.out.println("支付 " + amount + " CNY,通过 PayPal");
    }
}

public class OnlineStore {
    private PaymentStrategy paymentStrategy;

    public void setPaymentStrategy(PaymentStrategy strategy) {
        this.paymentStrategy = strategy;
    }

    public void checkout(int amount) {
        paymentStrategy.pay(amount);
    }
}

// 用法:
OnlineStore store = new OnlineStore();
store.setPaymentStrategy(new CreditCardPayment());
store.checkout(1000);

store.setPaymentStrategy(new PaypalPayment());
store.checkout(500);

输出:

支付 1000 CNY,使用银行卡
支付 500 CNY,通过 PayPal

Command(命令)

Command 是一种将请求封装为对象的模式,从而可以把动作作为参数传递。

代码示例:

public interface Command {
    void execute();
}

public class LightOnCommand implements Command {
    @Override
    public void execute() {
        System.out.println("灯已打开!");
    }
}

public class LightOffCommand implements Command {
    @Override
    public void execute() {
        System.out.println("灯已关闭!");
    }
}

public class RemoteControl {
    private Command command;

    public void setCommand(Command command) {
        this.command = command;
    }

    public void pressButton() {
        command.execute();
    }
}

// 用法:
RemoteControl remote = new RemoteControl();
remote.setCommand(new LightOnCommand());
remote.pressButton(); // 灯已打开!

remote.setCommand(new LightOffCommand());
remote.pressButton(); // 灯已关闭!

4. 在架构中使用接口的优势

  • 低耦合(Low Coupling)。代码只依赖接口而非具体实现,这有利于替换、测试与扩展。
  • 可测试性。编写单元测试时,可以轻松把真实实现替换为测试实现(mock/stub)。
  • 可扩展性。可以在不修改现有代码的情况下添加新的接口实现——开放/封闭原则(OCP)。
  • 并行开发。只要有共同的接口,不同团队即可独立实现系统的不同部分。
  • 架构灵活性。易于引入新的模式与方法。

5. 在架构中使用接口的常见错误

错误 № 1:对具体实现的硬编码绑定。
如果你到处直接使用具体类而不是接口,那么任何实现的变更都需要在许多地方重写代码。务必尽量“面向接口编程”。

错误 № 2:过于臃肿的接口(God Interface)。
接口应当精练,并只对单一职责负责。不要把所有东西都塞进一个接口,否则实现会变得笨重且难以维护。

错误 № 3:忽视可测试性的优势。
如果不通过接口在测试中替换依赖,你的测试可能会变慢且不可靠,尤其是在涉及真实数据库或网络时。

错误 № 4:有多种实现,却没有使用 DI。
如果你做了多个接口实现,却在代码里写死了其中一个,你就失去了架构的全部灵活性。请使用依赖注入(DI)!

1
调查/小测验
接口第 20 级,课程 4
不可用
接口
接口的概念
评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION