如何在应用中实现NotificationsService全局实例并遵循OOP原则?
问题背景
我正尝试从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

