You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在应用中实现NotificationsService全局实例并遵循OOP原则?

符合OOP规范的NotificationService依赖管理方案

问题背景

我正尝试从OOP角度正确实现一个NotificationService,定义了如下抽象类:

abstract class NotificationsService {
  void initNotificationsHandlers();

  int sendGeneralNotification({String? title, String? body});

  //...
}

以及它的子类:

class FirebaseNotificationService extends NotificationsService {
    // 所有实现代码...
}

现在的问题是实现阶段的依赖管理:我需要在main函数中实例化它:

NotificationsService notificationsService = new FirebaseNotificationService();

但我需要在多个类中使用这个服务,又不想在每个类里都实例化FirebaseNotificationService——这会违反依赖倒置原则(Dependency Inversion Principle),我希望其他类只知晓抽象类NotificationsService。

我曾考虑过这种实现方式:

abstract class NotificationsService {
  /// 当前使用的通知服务子类实例
  static NotificationsService? instance;

  //...
}

然后这样使用:
main函数中

NotificationsService.instance = new FirebaseNotificationService();

其他类中

NotificationsService.instance?.initNotificationsHandlers(); // 示例调用,也可以是其他方法

但这种方式不够优雅,因为我让NotificationsService这个接口承担了“存储子类实例”的职责,这显然不属于它的职责范围。

我应该创建另一个类来存储当前的实现?还是应用单例模式?符合OOP规范的正确解决方案是什么?

说明:我并非寻求个人观点(否则此问题应被关闭),而是询问符合OOP的正确解决方案。


符合OOP与DIP的正确解决方案

1. 依赖注入(Dependency Injection,DI)

这是最贴合依赖倒置原则的方案,也是OOP中推荐的核心实践:

  • 在应用启动入口(比如main函数)创建FirebaseNotificationService的实例
  • 将这个实例注入到所有需要使用通知服务的类中,而非让类自行创建或获取实例
  • 依赖通知服务的类,其构造函数只接收抽象类NotificationsService类型的参数,完全不感知具体实现类

示例代码:

// 需要使用通知服务的业务类
class OrderManager {
  final NotificationsService _notificationService;

  // 通过构造函数注入依赖
  OrderManager(this._notificationService);

  void sendOrderShippedNotification() {
    _notificationService.sendGeneralNotification(
      title: "订单更新",
      body: "你的订单已发出,请留意物流信息"
    );
  }
}

// main函数中完成初始化与注入
void main() {
  // 创建具体服务实例
  final NotificationsService notificationService = FirebaseNotificationService();
  // 注入到业务类
  final OrderManager orderManager = OrderManager(notificationService);
  // 其他需要通知服务的类,同理通过构造函数注入
}

这种方式严格遵循DIP:高层模块(业务类)依赖抽象,底层模块(具体实现类)也依赖同一抽象,彻底消除了类之间的直接耦合,同时让代码的可测试性、扩展性大幅提升——后续替换其他通知服务实现时,只需修改main中的实例化代码,业务类无需任何改动。

2. 服务定位器(Service Locator)

如果在大型项目中手动注入过于繁琐,可以使用服务定位器模式,把服务实例的管理职责剥离到专门的类中:

  • 创建独立的ServiceLocator类,负责统一注册、存储和提供所有服务实例
  • 所有需要服务的类,通过定位器获取抽象类型的实例,无需知晓具体实现

示例代码:

class ServiceLocator {
  // 定位器自身实现单例,确保全局唯一
  static final ServiceLocator _instance = ServiceLocator._();
  final Map<Type, dynamic> _services = {};

  ServiceLocator._();

  static ServiceLocator get instance => _instance;

  // 注册服务:将抽象类型与具体实例绑定
  void register<T>(T service) {
    _services[T] = service;
  }

  // 获取服务:根据抽象类型返回对应实例
  T get<T>() {
    return _services[T] as T;
  }
}

// main函数中注册服务
void main() {
  ServiceLocator.instance.register<NotificationsService>(FirebaseNotificationService());
}

// 业务类中使用服务
class OrderManager {
  void sendOrderShippedNotification() {
    final notificationService = ServiceLocator.instance.get<NotificationsService>();
    notificationService.sendGeneralNotification(
      title: "订单更新",
      body: "你的订单已发出,请留意物流信息"
    );
  }
}

这种方式既保持了类对抽象的依赖,又将服务实例的管理职责从抽象类中剥离,符合单一职责原则。缺点是服务的依赖关系不如依赖注入直观,需要注意避免过度使用导致代码难以追踪。

3. 单例模式(谨慎使用)

如果你的通知服务确实需要全局唯一的实例(比如需要维护全局状态),可以让具体实现类成为单例,而非在抽象类中添加静态属性:

class FirebaseNotificationService extends NotificationsService {
  // 具体实现类自身实现单例
  static final FirebaseNotificationService _instance = FirebaseNotificationService._();

  FirebaseNotificationService._();

  static FirebaseNotificationService get instance => _instance;

  // 实现抽象方法
  @override
  void initNotificationsHandlers() {
    // 具体初始化逻辑
  }

  @override
  int sendGeneralNotification({String? title, String? body}) {
    // 具体发送逻辑
    return 0;
  }
}

// 使用时
void main() {
  final NotificationsService notificationService = FirebaseNotificationService.instance;
}

// 业务类中使用(推荐结合依赖注入,而非直接调用单例)
class OrderManager {
  final NotificationsService _notificationService;

  OrderManager(this._notificationService);

  void sendOrderNotification() {
    _notificationService.sendGeneralNotification(
      title: "订单更新",
      body: "你的订单已发货"
    );
  }
}

注意:单例模式会导致代码测试困难(难以替换模拟实现),也容易引入全局状态的耦合问题,因此仅在确有必要时使用,优先推荐依赖注入或服务定位器方案。


为什么你之前的方案不合适?

你尝试在抽象类中添加静态实例的方式,违反了单一职责原则:抽象类的核心职责是定义通知服务的行为契约,而存储实例是服务生命周期管理的职责,两者耦合在一起会导致抽象类职责膨胀,同时也不利于后续扩展其他通知服务实现(比如新增LocalNotificationService时,抽象类的静态实例逻辑会变得混乱)。

内容的提问来源于stack exchange,提问作者Lucas Tomic

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.08 12:35:15