Skip to content

组合优于继承:构建灵活可扩展的系统架构

在软件设计中,继承虽然强大,但过度使用往往会导致代码结构变得僵化且脆弱。'组合优于继承'这一原则主张通过将小的、独立的功能模块组合在一起来构建复杂对象,而不是通过深层的类继承体系。这种方式能让系统更加灵活,更容易适应需求的变化。

"组合优于继承"(Composition Over Inheritance)是面向对象设计中的一个重要原则。它强调通过组合多个小的、专门的对象来构建复杂功能,而不是通过深层的继承层次。这种设计哲学让系统更加灵活、可维护,并且更容易适应变化。

继承的局限性

在深入了解组合模式之前,让我们先看看传统继承方式存在的问题:

1. 脆弱的基类问题

javascript
// 脆弱的基类示例
class Animal {
  makeSound() {
    console.log("动物发出了声音");
  }

  move() {
    console.log("动物在移动");
    // 某天,基类开发者添加了新功能
    this.checkEnergyLevel();
  }

  // 新添加的方法
  checkEnergyLevel() {
    console.log("检查能量水平");
  }
}

class Bird extends Animal {
  makeSound() {
    console.log("鸟儿啾啾叫");
  }

  // 重写移动方法
  move() {
    console.log("鸟儿在飞翔");
    // 没有调用 checkEnergyLevel,可能导致基类功能失效
  }

  fly() {
    console.log("鸟儿展翅高飞");
  }
}

class Penguin extends Bird {
  makeSound() {
    console.log("企鹅嘎嘎叫");
  }

  // 企鹅不会飞,重写方法
  move() {
    console.log("企鹅在游泳");
  }

  fly() {
    console.log("企鹅不会飞");
    // 这个实现违反了里氏替换原则
    throw new Error("企鹅不会飞!");
  }
}

// 问题:基类的变化可能破坏所有子类
const bird = new Bird();
bird.move(); // "鸟儿在飞翔" - 没有检查能量水平

const penguin = new Penguin();
try {
  penguin.fly(); // 抛出异常 - 破坏了多态性
} catch (error) {
  console.log(error.message);
}

2. 过度设计的问题

javascript
// 过度设计的继承层次
class Vehicle {
  start() {
    console.log("启动交通工具");
  }
}

class MotorVehicle extends Vehicle {
  start() {
    super.start();
    console.log("启动引擎");
  }
}

class Car extends MotorVehicle {
  start() {
    super.start();
    console.log("启动汽车系统");
  }
}

class ElectricCar extends Car {
  start() {
    super.start();
    console.log("启动电动系统");
  }
}

class SelfDrivingCar extends ElectricCar {
  start() {
    super.start();
    console.log("启动自动驾驶系统");
  }
}

// 问题:每一个新功能都需要新的子类
const car = new SelfDrivingCar();
car.start();
// 输出多层嵌套的启动信息

3. 违反开闭原则

javascript
class Shape {
  draw() {
    throw new Error("子类必须实现 draw 方法");
  }
}

class Circle extends Shape {
  constructor(radius) {
    super();
    this.radius = radius;
  }

  draw() {
    console.log(`绘制半径为 ${this.radius} 的圆形`);
  }
}

class Square extends Shape {
  constructor(side) {
    super();
    this.side = side;
  }

  draw() {
    console.log(`绘制边长为 ${this.side} 的正方形`);
  }
}

// 现在需要添加颜色功能 - 必须修改基类或创建大量子类
class ColoredCircle extends Circle {
  constructor(radius, color) {
    super(radius);
    this.color = color;
  }

  draw() {
    console.log(`绘制${this.color}色的圆形`);
  }
}

class ColoredSquare extends Square {
  constructor(side, color) {
    super(side);
    this.color = color;
  }

  draw() {
    console.log(`绘制${this.color}色的正方形`);
  }
}

// 如果还需要透明度、边框等属性,组合爆炸!

组合模式的解决方案

组合模式通过将功能分解为独立的、可重用的组件来解决继承的问题。

1. 基础组合示例

javascript
// 功能组件
class Engine {
  constructor(type, power) {
    this.type = type;
    this.power = power;
  }

  start() {
    console.log(`${this.type}引擎启动,功率 ${this.power} 马力`);
  }

  stop() {
    console.log(`${this.type}引擎停止`);
  }
}

class GPS {
  constructor(version) {
    this.version = version;
  }

  navigate(destination) {
    console.log(`GPS ${this.version} 导航到 ${destination}`);
  }
}

class AudioSystem {
  constructor(brand) {
    this.brand = brand;
    this.isPlaying = false;
  }

  play() {
    this.isPlaying = true;
    console.log(`${this.brand} 音响开始播放音乐`);
  }

  stop() {
    this.isPlaying = false;
    console.log(`${this.brand} 音响停止播放`);
  }
}

class Battery {
  constructor(capacity) {
    this.capacity = capacity; // kWh
    this.charge = capacity; // 当前电量
  }

  charge(amount) {
    this.charge = Math.min(this.capacity, this.charge + amount);
    console.log(`充电 ${amount} kWh,当前电量 ${this.charge}/${this.capacity}`);
  }

  consume(amount) {
    if (this.charge >= amount) {
      this.charge -= amount;
      console.log(
        `消耗 ${amount} kWh,剩余电量 ${this.charge}/${this.capacity}`
      );
      return true;
    }
    console.log(`电量不足!需要 ${amount} kWh,只有 ${this.charge} kWh`);
    return false;
  }
}

// 通过组合创建汽车
class Car {
  constructor(config = {}) {
    this.engine = new Engine(
      config.engineType || "汽油",
      config.enginePower || 150
    );
    this.gps = config.hasGPS ? new GPS(config.gpsVersion || "2.0") : null;
    this.audio = config.hasAudio
      ? new AudioSystem(config.audioBrand || "Sony")
      : null;
    this.battery = config.hasBattery
      ? new Battery(config.batteryCapacity || 50)
      : null;
  }

  start() {
    console.log("汽车启动流程开始");

    if (this.battery && !this.battery.consume(1)) {
      console.log("启动失败:电量不足");
      return false;
    }

    this.engine.start();

    if (this.gps) {
      this.gps.navigate("预设目的地");
    }

    console.log("汽车启动完成");
    return true;
  }

  stop() {
    this.engine.stop();
    this.audio?.stop();
    console.log("汽车停止");
  }

  playMusic() {
    if (this.audio) {
      this.audio.play();
    } else {
      console.log("这辆车没有音响系统");
    }
  }

  upgradeGPS(newVersion) {
    if (this.gps) {
      this.gps = new GPS(newVersion);
      console.log(`GPS 升级到版本 ${newVersion}`);
    } else {
      this.gps = new GPS(newVersion);
      console.log(`安装 GPS 版本 ${newVersion}`);
    }
  }
}

// 创建不同的汽车配置
const basicCar = new Car({
  engineType: "汽油",
  enginePower: 120,
});

const luxuryCar = new Car({
  engineType: "V8汽油",
  enginePower: 400,
  hasGPS: true,
  gpsVersion: "3.0",
  hasAudio: true,
  audioBrand: "Bose",
});

const electricCar = new Car({
  engineType: "电动",
  enginePower: 300,
  hasGPS: true,
  gpsVersion: "4.0",
  hasAudio: true,
  audioBrand: "B&O",
  hasBattery: true,
  batteryCapacity: 100,
});

// 使用示例
basicCar.start(); // 基础汽车
luxuryCar.start(); // 豪华汽车
luxuryCar.playMusic(); // 豪华汽车播放音乐
electricCar.start(); // 电动汽车

2. 行为组合模式

javascript
// 行为组件
class Drawable {
  constructor(shape, style = {}) {
    this.shape = shape;
    this.style = {
      color: "black",
      fillColor: "transparent",
      lineWidth: 1,
      ...style,
    };
  }

  draw(ctx, x, y) {
    ctx.save();
    ctx.strokeStyle = this.style.color;
    ctx.fillStyle = this.style.fillColor;
    ctx.lineWidth = this.style.lineWidth;

    switch (this.shape) {
      case "circle":
        this.drawCircle(ctx, x, y);
        break;
      case "rectangle":
        this.drawRectangle(ctx, x, y);
        break;
      case "triangle":
        this.drawTriangle(ctx, x, y);
        break;
    }

    ctx.restore();
  }

  drawCircle(ctx, x, y) {
    ctx.beginPath();
    ctx.arc(x, y, this.style.radius || 20, 0, Math.PI * 2);
    ctx.fill();
    ctx.stroke();
  }

  drawRectangle(ctx, x, y) {
    const width = this.style.width || 40;
    const height = this.style.height || 30;
    ctx.fillRect(x - width / 2, y - height / 2, width, height);
    ctx.strokeRect(x - width / 2, y - height / 2, width, height);
  }

  drawTriangle(ctx, x, y) {
    const size = this.style.size || 25;
    ctx.beginPath();
    ctx.moveTo(x, y - size);
    ctx.lineTo(x - size, y + size);
    ctx.lineTo(x + size, y + size);
    ctx.closePath();
    ctx.fill();
    ctx.stroke();
  }
}

class Movable {
  constructor(speed = 5) {
    this.speed = speed;
    this.x = 0;
    this.y = 0;
  }

  moveTo(x, y) {
    this.x = x;
    this.y = y;
  }

  moveBy(dx, dy) {
    this.x += dx * this.speed;
    this.y += dy * this.speed;
  }

  getPosition() {
    return { x: this.x, y: this.y };
  }
}

class Interactive {
  constructor() {
    this.onClick = null;
    this.onHover = null;
    this.isEnabled = true;
  }

  click() {
    if (this.isEnabled && this.onClick) {
      this.onClick();
    }
  }

  hover() {
    if (this.isEnabled && this.onHover) {
      this.onHover();
    }
  }

  enable() {
    this.isEnabled = true;
  }

  disable() {
    this.isEnabled = false;
  }
}

class Animated {
  constructor() {
    this.animations = [];
    this.isAnimating = false;
  }

  addAnimation(animation) {
    this.animations.push(animation);
  }

  start() {
    this.isAnimating = true;
    this.animate();
  }

  stop() {
    this.isAnimating = false;
  }

  animate() {
    if (!this.isAnimating) return;

    this.animations.forEach((animation) => {
      if (animation.update()) {
        animation.draw();
      }
    });

    requestAnimationFrame(() => this.animate());
  }
}

// 通过组合创建图形对象
class GameObject {
  constructor(x, y, config = {}) {
    this.drawable = config.drawable || null;
    this.movable = config.movable || null;
    this.interactive = config.interactive || null;
    this.animated = config.animated || null;

    if (this.movable) {
      this.movable.moveTo(x, y);
    }
  }

  update(deltaTime) {
    // 更新所有组件
  }

  render(ctx) {
    if (this.drawable && this.movable) {
      const pos = this.movable.getPosition();
      this.drawable.draw(ctx, pos.x, pos.y);
    }
  }

  handleClick() {
    this.interactive?.click();
  }

  handleHover() {
    this.interactive?.hover();
  }
}

// 使用示例
const canvas = document.createElement("canvas");
canvas.width = 800;
canvas.height = 600;
document.body.appendChild(canvas);

const ctx = canvas.getContext("2d");

// 创建一个可点击的圆形
const clickableCircle = new GameObject(100, 100, {
  drawable: new Drawable("circle", {
    radius: 30,
    fillColor: "blue",
    color: "darkblue",
  }),
  movable: new Movable(3),
  interactive: new Interactive(),
});

clickableCircle.interactive.onClick = () => {
  console.log("圆形被点击了!");
  clickableCircle.drawable.style.fillColor =
    clickableCircle.drawable.style.fillColor === "blue" ? "red" : "blue";
};

// 创建一个可移动的矩形
const movableRect = new GameObject(200, 200, {
  drawable: new Drawable("rectangle", {
    width: 60,
    height: 40,
    fillColor: "green",
  }),
  movable: new Movable(2),
});

// 动画循环
function gameLoop() {
  ctx.clearRect(0, 0, canvas.width, canvas.height);

  clickableCircle.update();
  movableRect.update();

  clickableCircle.render(ctx);
  movableRect.render(ctx);

  requestAnimationFrame(gameLoop);
}

gameLoop();

// 添加交互
canvas.addEventListener("click", (e) => {
  const rect = canvas.getBoundingClientRect();
  const x = e.clientX - rect.left;
  const y = e.clientY - rect.top;

  const circlePos = clickableCircle.movable.getPosition();
  const distance = Math.sqrt(
    Math.pow(x - circlePos.x, 2) + Math.pow(y - circlePos.y, 2)
  );

  if (distance <= 30) {
    clickableCircle.handleClick();
  }
});

// 键盘控制移动
document.addEventListener("keydown", (e) => {
  switch (e.key) {
    case "ArrowUp":
      movableRect.movable.moveBy(0, -1);
      break;
    case "ArrowDown":
      movableRect.movable.moveBy(0, 1);
      break;
    case "ArrowLeft":
      movableRect.movable.moveBy(-1, 0);
      break;
    case "ArrowRight":
      movableRect.movable.moveBy(1, 0);
      break;
  }
});

组合模式的实际应用

1. Web 组件系统

javascript
// 组件功能模块
class EventManager {
  constructor() {
    this.listeners = new Map();
  }

  on(event, handler) {
    if (!this.listeners.has(event)) {
      this.listeners.set(event, []);
    }
    this.listeners.get(event).push(handler);
  }

  off(event, handler) {
    if (this.listeners.has(event)) {
      const handlers = this.listeners.get(event);
      const index = handlers.indexOf(handler);
      if (index > -1) {
        handlers.splice(index, 1);
      }
    }
  }

  emit(event, ...args) {
    if (this.listeners.has(event)) {
      this.listeners.get(event).forEach((handler) => {
        handler(...args);
      });
    }
  }
}

class Validator {
  constructor() {
    this.rules = [];
    this.errors = [];
  }

  addRule(field, validator, message) {
    this.rules.push({ field, validator, message });
  }

  validate(data) {
    this.errors = [];
    let isValid = true;

    for (const { field, validator, message } of this.rules) {
      if (!validator(data[field])) {
        this.errors.push({ field, message, value: data[field] });
        isValid = false;
      }
    }

    return isValid;
  }

  getErrors() {
    return [...this.errors];
  }
}

class Renderer {
  constructor(template) {
    this.template = template;
  }

  render(data) {
    return this.template.replace(/\{\{(\w+)\}\}/g, (match, key) => {
      return data[key] !== undefined ? data[key] : "";
    });
  }
}

class StateManager {
  constructor(initialState = {}) {
    this.state = { ...initialState };
    this.subscribers = [];
  }

  setState(newState) {
    const oldState = { ...this.state };
    this.state = { ...this.state, ...newState };
    this.notify(oldState, this.state);
  }

  getState() {
    return { ...this.state };
  }

  subscribe(callback) {
    this.subscribers.push(callback);
    return () => {
      const index = this.subscribers.indexOf(callback);
      if (index > -1) {
        this.subscribers.splice(index, 1);
      }
    };
  }

  notify(oldState, newState) {
    this.subscribers.forEach((callback) => callback(newState, oldState));
  }
}

// 组合创建表单组件
class FormComponent {
  constructor(element, config = {}) {
    this.element = element;
    this.events = new EventManager();
    this.validator = config.hasValidation ? new Validator() : null;
    this.renderer = new Renderer(config.template || "<div>{{content}}</div>");
    this.state = new StateManager(config.initialState || {});

    this.setupValidation();
    this.bindEvents();
  }

  setupValidation() {
    if (!this.validator) return;

    // 添加通用验证规则
    this.validator.addRule(
      "email",
      (val) => typeof val === "string" && val.includes("@"),
      "请输入有效的邮箱地址"
    );

    this.validator.addRule(
      "required",
      (val) => val !== null && val !== undefined && val !== "",
      "此字段为必填项"
    );
  }

  bindEvents() {
    this.state.subscribe((newState, oldState) => {
      this.render(newState);
      this.events.emit("stateChange", newState, oldState);
    });

    this.element.addEventListener("submit", (e) => {
      e.preventDefault();
      this.handleSubmit();
    });
  }

  handleSubmit() {
    const formData = this.getFormData();

    if (this.validator && !this.validator.validate(formData)) {
      this.events.emit("validationError", this.validator.getErrors());
      return;
    }

    this.events.emit("submit", formData);
  }

  getFormData() {
    const formData = {};
    const inputs = this.element.querySelectorAll("input, select, textarea");

    inputs.forEach((input) => {
      formData[input.name] = input.value;
    });

    return formData;
  }

  render(data) {
    this.element.innerHTML = this.renderer.render(data);
  }

  validate() {
    if (!this.validator) return true;

    const formData = this.getFormData();
    return this.validator.validate(formData);
  }

  setState(newState) {
    this.state.setState(newState);
  }

  getState() {
    return this.state.getState();
  }
}

// 使用示例
const loginForm = new FormComponent(document.getElementById("loginForm"), {
  hasValidation: true,
  template: `
    <form>
      <div>
        <label>邮箱:</label>
        <input type="email" name="email" value="{{email}}">
      </div>
      <div>
        <label>密码:</label>
        <input type="password" name="password" value="{{password}}">
      </div>
      <button type="submit">登录</button>
      <div class="errors">{{errors}}</div>
    </form>
  `,
  initialState: {
    email: "",
    password: "",
    errors: "",
  },
});

// 添加自定义验证
loginForm.validator.addRule(
  "password",
  (val) => typeof val === "string" && val.length >= 6,
  "密码至少需要6个字符"
);

// 监听事件
loginForm.events.on("submit", (formData) => {
  console.log("表单提交:", formData);
  // 发送登录请求
});

loginForm.events.on("validationError", (errors) => {
  const errorMessages = errors
    .map((err) => `${err.field}: ${err.message}`)
    .join(", ");
  loginForm.setState({ errors: errorMessages });
});

2. 服务层组合

javascript
// 服务组件
class CacheService {
  constructor() {
    this.cache = new Map();
    this.ttl = new Map();
  }

  set(key, value, expireTime = 5 * 60 * 1000) {
    this.cache.set(key, value);
    this.ttl.set(key, Date.now() + expireTime);
  }

  get(key) {
    if (!this.cache.has(key)) return null;

    if (Date.now() > this.ttl.get(key)) {
      this.delete(key);
      return null;
    }

    return this.cache.get(key);
  }

  delete(key) {
    this.cache.delete(key);
    this.ttl.delete(key);
  }

  clear() {
    this.cache.clear();
    this.ttl.clear();
  }
}

class LoggerService {
  constructor(level = "info") {
    this.level = level;
  }

  log(message, level = "info") {
    const timestamp = new Date().toISOString();
    console.log(`[${timestamp}] [${level.toUpperCase()}] ${message}`);
  }

  error(message) {
    this.log(message, "error");
  }

  warn(message) {
    this.log(message, "warn");
  }

  info(message) {
    this.log(message, "info");
  }
}

class HttpService {
  constructor(baseURL = "") {
    this.baseURL = baseURL;
  }

  async get(url, options = {}) {
    try {
      const response = await fetch(`${this.baseURL}${url}`, {
        method: "GET",
        ...options,
      });

      if (!response.ok) {
        throw new Error(`HTTP ${response.status}: ${response.statusText}`);
      }

      return await response.json();
    } catch (error) {
      throw new Error(`请求失败: ${error.message}`);
    }
  }

  async post(url, data, options = {}) {
    try {
      const response = await fetch(`${this.baseURL}${url}`, {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
          ...options.headers,
        },
        body: JSON.stringify(data),
        ...options,
      });

      if (!response.ok) {
        throw new Error(`HTTP ${response.status}: ${response.statusText}`);
      }

      return await response.json();
    } catch (error) {
      throw new Error(`请求失败: ${error.message}`);
    }
  }
}

class AuthService {
  constructor() {
    this.http = new HttpService("https://api.example.com");
    this.cache = new CacheService();
    this.logger = new LoggerService("auth");
  }

  async login(email, password) {
    this.logger.info(`用户登录尝试: ${email}`);

    try {
      const response = await this.http.post("/auth/login", {
        email,
        password,
      });

      // 缓存用户信息
      this.cache.set("user", response.user, 30 * 60 * 1000); // 30分钟
      this.cache.set("token", response.token, 30 * 60 * 1000);

      this.logger.info(`用户登录成功: ${email}`);
      return response;
    } catch (error) {
      this.logger.error(`登录失败: ${error.message}`);
      throw error;
    }
  }

  async logout() {
    try {
      const token = this.cache.get("token");
      if (token) {
        await this.http.post("/auth/logout", { token });
      }

      this.cache.delete("user");
      this.cache.delete("token");

      this.logger.info("用户登出成功");
    } catch (error) {
      this.logger.error(`登出失败: ${error.message}`);
    }
  }

  getCurrentUser() {
    return this.cache.get("user");
  }

  isAuthenticated() {
    return this.cache.get("token") !== null;
  }
}

class UserService {
  constructor() {
    this.http = new HttpService("https://api.example.com");
    this.cache = new CacheService();
    this.logger = new LoggerService("user");
    this.authService = new AuthService();
  }

  async getUserProfile(userId) {
    const cacheKey = `profile:${userId}`;

    // 检查缓存
    let profile = this.cache.get(cacheKey);
    if (profile) {
      this.logger.info(`从缓存获取用户资料: ${userId}`);
      return profile;
    }

    try {
      profile = await this.http.get(`/users/${userId}`);

      // 缓存结果
      this.cache.set(cacheKey, profile, 10 * 60 * 1000); // 10分钟

      this.logger.info(`获取用户资料成功: ${userId}`);
      return profile;
    } catch (error) {
      this.logger.error(`获取用户资料失败: ${error.message}`);
      throw error;
    }
  }

  async updateProfile(userId, updates) {
    this.logger.info(`更新用户资料: ${userId}`);

    try {
      const updatedProfile = await this.http.put(`/users/${userId}`, updates);

      // 更新缓存
      const cacheKey = `profile:${userId}`;
      this.cache.set(cacheKey, updatedProfile, 10 * 60 * 1000);

      this.logger.info(`用户资料更新成功: ${userId}`);
      return updatedProfile;
    } catch (error) {
      this.logger.error(`更新用户资料失败: ${error.message}`);
      throw error;
    }
  }
}

// 使用示例
const authService = new AuthService();
const userService = new UserService();

// 用户登录
authService
  .login("[email protected]", "password123")
  .then(() => {
    console.log("登录成功");

    // 获取用户资料
    const user = authService.getCurrentUser();
    return userService.getUserProfile(user.id);
  })
  .then((profile) => {
    console.log("用户资料:", profile);
  })
  .catch((error) => {
    console.error("操作失败:", error);
  });

组合模式的优势

1. 灵活性

javascript
// 动态组合功能
class TaskManager {
  constructor() {
    this.features = {
      logging: null,
      priority: null,
      deadline: null,
      notification: null,
      persistence: null,
    };
  }

  addFeature(name, feature) {
    if (this.features.hasOwnProperty(name)) {
      this.features[name] = feature;
      console.log(`功能 ${name} 已添加`);
    }
  }

  removeFeature(name) {
    if (this.features[name]) {
      this.features[name] = null;
      console.log(`功能 ${name} 已移除`);
    }
  }

  executeTask(task) {
    console.log(`执行任务: ${task.name}`);

    // 按顺序执行功能
    this.features.logging?.log(task);
    this.features.priority?.process(task);
    this.features.deadline?.check(task);

    const result = this.performTask(task);

    this.features.notification?.send(result);
    this.features.persistence?.save(result);

    return result;
  }

  performTask(task) {
    console.log(`任务 ${task.name} 执行完成`);
    return { task, success: true, timestamp: Date.now() };
  }
}

// 功能组件
class LoggingFeature {
  log(task) {
    console.log(`[LOG] 开始执行任务: ${task.name}`);
  }
}

class PriorityFeature {
  process(task) {
    if (task.priority === "high") {
      console.log(`[PRIORITY] 高优先级任务,立即执行`);
    }
  }
}

class DeadlineFeature {
  check(task) {
    if (task.deadline && Date.now() > task.deadline) {
      console.log(`[DEADLINE] 警告:任务已过期`);
    }
  }
}

class NotificationFeature {
  send(result) {
    console.log(`[NOTIFY] 任务完成通知: ${result.task.name}`);
  }
}

class PersistenceFeature {
  save(result) {
    console.log(`[PERSIST] 保存任务结果到数据库`);
  }
}

// 动态配置任务管理器
const taskManager = new TaskManager();

// 根据需求添加功能
taskManager.addFeature("logging", new LoggingFeature());
taskManager.addFeature("priority", new PriorityFeature());
taskManager.addFeature("deadline", new DeadlineFeature());

// 运行时添加或移除功能
taskManager.addFeature("notification", new NotificationFeature());

const task1 = {
  name: "发送邮件",
  priority: "high",
  deadline: Date.now() + 3600000, // 1小时后过期
};

taskManager.executeTask(task1);

// 移除通知功能
taskManager.removeFeature("notification");

const task2 = {
  name: "生成报告",
  priority: "normal",
};

taskManager.executeTask(task2);

2. 测试友好性

javascript
// 可测试的组合设计
class EmailService {
  constructor(emailProvider, logger, validator) {
    this.emailProvider = emailProvider;
    this.logger = logger;
    this.validator = validator;
  }

  async sendEmail(to, subject, body) {
    this.logger.info(`准备发送邮件到: ${to}`);

    if (!this.validator.isValidEmail(to)) {
      throw new Error("无效的邮箱地址");
    }

    try {
      const result = await this.emailProvider.send(to, subject, body);
      this.logger.info(`邮件发送成功: ${result.messageId}`);
      return result;
    } catch (error) {
      this.logger.error(`邮件发送失败: ${error.message}`);
      throw error;
    }
  }
}

// 测试用的模拟组件
class MockEmailProvider {
  async send(to, subject, body) {
    console.log(`[MOCK] 发送邮件: ${to} - ${subject}`);
    return { messageId: "mock-id-123", status: "sent" };
  }
}

class MockLogger {
  info(message) {
    console.log(`[MOCK INFO] ${message}`);
  }

  error(message) {
    console.log(`[MOCK ERROR] ${message}`);
  }
}

class MockValidator {
  isValidEmail(email) {
    return email.includes("@");
  }
}

// 测试
async function testEmailService() {
  const mockProvider = new MockEmailProvider();
  const mockLogger = new MockLogger();
  const mockValidator = new MockValidator();

  const emailService = new EmailService(
    mockProvider,
    mockLogger,
    mockValidator
  );

  try {
    await emailService.sendEmail(
      "[email protected]",
      "测试邮件",
      "这是一封测试邮件"
    );
    console.log("测试通过");
  } catch (error) {
    console.error("测试失败:", error.message);
  }
}

testEmailService();

组合模式的最佳实践

1. 接口设计

javascript
// 定义清晰的接口
class IRenderable {
  render(context) {
    throw new Error("子类必须实现 render 方法");
  }
}

class IUpdatable {
  update(deltaTime) {
    throw new Error("子类必须实现 update 方法");
  }
}

class IDestroyable {
  destroy() {
    throw new Error("子类必须实现 destroy 方法");
  }
}

// 实现接口的组件
class SpriteRenderer extends IRenderable {
  constructor(image, x, y) {
    super();
    this.image = image;
    this.x = x;
    this.y = y;
  }

  render(context) {
    context.drawImage(this.image, this.x, this.y);
  }
}

class PhysicsComponent extends IUpdatable {
  constructor(gravity = 9.8) {
    super();
    this.velocity = { x: 0, y: 0 };
    this.gravity = gravity;
  }

  update(deltaTime) {
    this.velocity.y += this.gravity * deltaTime;
    return this.velocity;
  }
}

class LifecycleManager extends IDestroyable {
  constructor() {
    super();
    this.resources = [];
  }

  addResource(resource) {
    this.resources.push(resource);
  }

  destroy() {
    this.resources.forEach((resource) => {
      if (resource.destroy) {
        resource.destroy();
      }
    });
    this.resources = [];
  }
}

2. 依赖注入

javascript
// 依赖注入容器
class DIContainer {
  constructor() {
    this.services = new Map();
    this.singletons = new Map();
  }

  register(name, factory, options = {}) {
    this.services.set(name, { factory, options });
  }

  resolve(name) {
    const service = this.services.get(name);
    if (!service) {
      throw new Error(`服务 ${name} 未注册`);
    }

    if (service.options.singleton) {
      if (!this.singletons.has(name)) {
        this.singletons.set(name, service.factory(this));
      }
      return this.singletons.get(name);
    }

    return service.factory(this);
  }
}

// 使用依赖注入
const container = new DIContainer();

container.register("logger", () => new LoggerService(), { singleton: true });
container.register("cache", () => new CacheService(), { singleton: true });
container.register("http", () => new HttpService(), { singleton: true });

container.register("authService", (container) => {
  return new AuthService(
    container.resolve("http"),
    container.resolve("cache"),
    container.resolve("logger")
  );
});

// 使用服务
const authService = container.resolve("authService");

总结

"组合优于继承"是现代软件设计的核心原则之一。通过组合模式,你可以:

  1. 构建灵活的系统:通过组合不同的功能模块,创建多样化的对象
  2. 提高代码复用性:独立的功能组件可以在多个地方重用
  3. 增强可维护性:修改一个组件不会影响其他组件
  4. 改善测试性:可以独立测试每个组件
  5. 支持运行时变化:可以动态添加、移除或替换功能

组合模式的核心思想是将复杂系统分解为小的、专门的、可组合的组件。这种设计哲学不仅适用于面向对象编程,也适用于函数式编程和其他编程范式。

记住,好的设计不是关于继承层次有多深,而是关于组件之间如何优雅地协作。通过组合,你可以构建出更加灵活、可维护和可扩展的软件系统。