基于Angular的高内聚、低耦合、可扩展模块设计参考

将一个工具特性模块设计得高内聚、低耦合、可扩展,是Angular架构能力的体现。

作为资深前端架构师,会从 设计原则、具体方案、示例代码和注意事项 四个方面提供建议。


一、核心设计原则

  1. 单一职责原则 (Single Responsibility Principle) : 你的工具模块只应该做它被设计来做的事情------提供可复用的工具服务,不应包含任何业务逻辑或UI组件。
  2. 依赖倒置原则 (Dependency Inversion Principle) : 工具模块不应该直接依赖具体的业务模块。它应该定义抽象的接口或令牌(Token),让业务模块来适配和注入具体的实现。
  3. 开放封闭原则 (Open/Closed Principle) : 模块应该对扩展开放,对修改封闭。当需要添加新工具时,应通过扩展(如添加新服务)来实现,而非修改现有核心代码。
  4. 提供清晰的契约 (Contract) : 对外暴露的API(服务方法、注入令牌、配置选项)必须稳定、清晰、文档完备。

二、具体设计方案

我将你的"工具特性模块"理解为 CoreModuleSharedModule 的一个更纯粹的子集,专门用于工具。我建议将其命名为 ToolkitModuleUtilsModule

1. 模块定义 (toolkit.module.ts)

这个模块的唯一目的是聚合和提供所有工具服务。它不应该有declarations(不声明组件/指令/管道),只包含providers

2. 服务设计 (*.service.ts)

每个工具功能都应该被封装在一个独立的、可注入的Service类中。这是实现"职责单一"和"与业务分离"的关键。

3. 配置机制 (Optional)

对于需要配置的工具(如日志级别、API端点),使用 forRoot() 模式或 注入令牌(Injection Token) 来提供配置,使其在根模块中可配置。

4. 接口抽象 (Abstraction)

对于可能被业务模块扩展或有不同的实现(如日志服务、HTTP客户端装饰器),定义抽象类或接口,并使用依赖注入令牌(DI Token) 来提供实现,而不是直接依赖具体类。


三、示例代码参考

让我们构建一个包含 LoggerServiceStorageServiceHttpHelperServiceToolkitModule

项目结构

text 复制代码
src/app/core/toolkit/
├── index.ts                          //  Barrel file for easy imports
├── toolkit.module.ts                 //  NgModule definition
├── services                          //  Directory for all services
│   ├── logger.service.ts             //  Logger service with injection token
│   ├── storage.service.ts            //  Abstract & concrete storage service
│   └── http-helper.service.ts        //  HTTP utility service
└── tokens                            //  Directory for injection tokens
    └── logger.config.ts              //  Logger configuration token

1. 定义配置令牌 (tokens/logger.config.ts)

typescript 复制代码
// tokens/logger.config.ts
import { InjectionToken } from '@angular/core';

export interface LoggerConfig {
  logLevel: 'debug' | 'info' | 'warn' | 'error';
  enableDebug: boolean;
}

// 创建一个注入令牌来传递配置
export const LOGGER_CONFIG = new InjectionToken<LoggerConfig>('logger.config', {
  providedIn: 'root',
  factory: () => ({
    logLevel: 'debug',
    enableDebug: true
  })
});

2. 实现可配置的日志服务 (services/logger.service.ts)

typescript 复制代码
// services/logger.service.ts
import { Injectable, Inject, Optional } from '@angular/core';
import { LOGGER_CONFIG, LoggerConfig } from '../tokens/logger.config';

// 定义一个抽象类,为可能的多种实现提供契约
export abstract class Logger {
  abstract debug(message: string, data?: any): void;
  abstract info(message: string, data?: any): void;
  abstract warn(message: string, data?: any): void;
  abstract error(message: string, data?: any): void;
}

@Injectable({
  providedIn: 'root' // 通常工具服务是应用级单例
})
export class LoggerService implements Logger {
  private readonly config: LoggerConfig;

  constructor(@Inject(LOGGER_CONFIG) config: LoggerConfig) {
    this.config = config;
  }

  debug(message: string, data?: any): void {
    if (this.config.enableDebug && this.config.logLevel === 'debug') {
      console.debug(`DEBUG: ${message}`, data || '');
    }
  }

  info(message: string, data?: any): void {
    if (['debug', 'info'].includes(this.config.logLevel)) {
      console.log(`INFO: ${message}`, data || '');
    }
  }

  warn(message: string, data?: any): void {
    if (['debug', 'info', 'warn'].includes(this.config.logLevel)) {
      console.warn(`WARN: ${message}`, data || '');
    }
  }

  error(message: string, data?: any): void {
    // Error should always be logged
    console.error(`ERROR: ${message}`, data || '');
  }
}

3. 实现抽象存储服务 (services/storage.service.ts)

typescript 复制代码
// services/storage.service.ts
import { Injectable } from '@angular/core';

// 抽象类,定义存储契约,便于未来切换实现(e.g., LocalStorage -> IndexedDB)
export abstract class StorageService {
  abstract getItem<T>(key: string): T | null;
  abstract setItem<T>(key: string, value: T): void;
  abstract removeItem(key: string): void;
  abstract clear(): void;
}

@Injectable({
  providedIn: 'root'
})
export class LocalStorageService extends StorageService {
  getItem<T>(key: string): T | null {
    const item = localStorage.getItem(key);
    return item ? JSON.parse(item) : null;
  }

  setItem<T>(key: string, value: T): void {
    localStorage.setItem(key, JSON.stringify(value));
  }

  removeItem(key: string): void {
    localStorage.removeItem(key);
  }

  clear(): void {
    localStorage.clear();
  }
}

4. 实现工具模块 (toolkit.module.ts)

typescript 复制代码
// toolkit.module.ts
import { NgModule, ModuleWithProviders } from '@angular/core';
import { CommonModule } from '@angular/common';
import { LoggerService, Logger } from './services/logger.service';
import { LocalStorageService, StorageService } from './services/storage.service';
import { HttpHelperService } from './services/http-helper.service';
import { LOGGER_CONFIG, LoggerConfig } from './tokens/logger.config';

@NgModule({
  imports: [CommonModule],
  // 没有 declarations
})
export class ToolkitModule {
  // 使用 forRoot 模式来在根模块配置工具模块
  static forRoot(config: Partial<LoggerConfig> = {}): ModuleWithProviders<ToolkitModule> {
    return {
      ngModule: ToolkitModule,
      providers: [
        // 提供具体的服务实现
        { provide: Logger, useClass: LoggerService }, // 依赖抽象,而不是具体类
        { provide: StorageService, useClass: LocalStorageService },
        HttpHelperService,
        // 覆盖默认的配置
        {
          provide: LOGGER_CONFIG,
          useValue: {
            logLevel: 'debug',
            enableDebug: true,
            ...config // 允许根模块传入配置进行覆盖
          }
        }
      ]
    };
  }
}

5. 在根模块中导入并配置 (app.module.ts)

typescript 复制代码
// app.module.ts
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppComponent } from './app.component';
import { ToolkitModule } from './core/toolkit/toolkit.module';
import { environment } from '../environments/environment';

@NgModule({
  declarations: [AppComponent],
  imports: [
    BrowserModule,
    // 导入工具模块并传入生产环境配置
    ToolkitModule.forRoot({
      logLevel: environment.production ? 'error' : 'debug',
      enableDebug: !environment.production
    })
  ],
  bootstrap: [AppComponent]
})
export class AppModule { }

6. 在业务组件中使用

typescript 复制代码
// some-business-component.component.ts
import { Component } from '@angular/core';
import { Logger, StorageService } from '../../core/toolkit'; // 通过barrel文件导入

@Component({
  selector: 'app-some-business-component',
  template: `...`
})
export class SomeBusinessComponent {
  // 依赖注入的是抽象接口,工具模块与业务完全解耦
  constructor(
    private logger: Logger,
    private storage: StorageService
  ) {
    this.logger.info('Component initialized');
    const userPrefs = this.storage.getItem('user-preferences');
  }
}

7. 创建Barrel文件 (index.ts)

typescript 复制代码
// index.ts
export * from './toolkit.module';

// Services
export * from './services/logger.service';
export * from './services/storage.service';
export * from './services/http-helper.service';

// Tokens
export * from './tokens/logger.config';

四、关键注意事项

  1. providedIn: 'root' vs Module Providers : 对于真正的全局单例服务(如Logger,HttpHelper),使用providedIn: 'root'更简单。对于需要多种实现或配置的,通过模块的providers数组提供更灵活。示例中混合使用了两种方式以作演示。
  2. 避免循环依赖 : 工具模块绝对不能导入任何业务模块。它的依赖流应该是单向的:业务模块 -> 工具模块。
  3. Tree-shaking : 确保你的服务如果没有被使用,可以被Angular的构建优化工具移除。使用providedIn: 'root'providedIn: 'platform'有助于实现这一点。
  4. 测试: 由于职责单一且与业务分离,这些工具服务非常容易进行单元测试。你可以轻松地Mock和替换这些服务来测试业务组件。
  5. 版本管理与变更日志: 如果这个工具模块被多个项目使用,考虑将其发布为私有NPM包,并严格遵守语义化版本控制(SemVer),维护清晰的变更日志。

通过以上设计,你的工具特性模块将成为一个强大、灵活且易于维护的基础设施,能够很好地支持你项目的长期演化。

相关推荐
vivi_and_qiao14 分钟前
HTML的form表单
java·前端·html
骑驴看星星a1 小时前
Vue中的scoped属性
前端·javascript·vue.js
四月_h1 小时前
在 Vue 3 + TypeScript 项目中实现主题切换功能
前端·vue.js·typescript
qq_427506081 小时前
vue3写一个简单的时间轴组件
前端·javascript·vue.js
雨枪幻。2 小时前
spring boot开发:一些基础知识
开发语言·前端·javascript
lecepin3 小时前
AI Coding 资讯 2025.8.27
前端·ai编程
TimelessHaze3 小时前
拆解字节面试题:async/await 到底是什么?底层实现 + 最佳实践全解析
前端·javascript·trae
执键行天涯4 小时前
从双重检查锁定的设计意图、锁的作用、第一次检查提升性能的原理三个角度,详细拆解单例模式的逻辑
java·前端·github
青青子衿越4 小时前
微信小程序web-view嵌套H5,小程序与H5通信
前端·微信小程序·小程序
OpenTiny社区4 小时前
TinyEngine 2.8版本正式发布:AI能力、区块管理、Docker部署一键强化,迈向智能时代!
前端·vue.js·低代码