混合類型(Mixin Types)是 TypeScript 中一種強大的類型系統特性,它允許一個類型既可以是函數又可以是對象。這種類型同時具備函數調用和對象屬性訪問的能力,為設計模式如裝飾器模式等提供了優雅的實現方式。

核心概念

混合類型通過將函數簽名與對象類型進行交叉(intersection)定義:

// 定義一個混合類型
interface MixedFunction {
  (): void;                    // 函數調用簽名
  property: string;            // 對象屬性
  method(): void;              // 對象方法
}

// 使用示例
const myMixed: MixedFunction = (() => {
  const func = (() => {
    console.log("函數被調用");
  }) as MixedFunction;

  func.property = "混合屬性";
  func.method = () => {
    console.log("方法被調用");
  };

  return func;
})();

myMixed();        // 作為函數調用
console.log(myMixed.property);  // 訪問屬性
myMixed.method(); // 調用方法

實現方式

1. 類型斷言方式

interface Counter {
  (): number;
  count: number;
  reset(): void;
}

function createCounter(): Counter {
  const counter = (() => {
    counter.count++;
    return counter.count;
  }) as Counter;

  counter.count = 0;
  counter.reset = () => {
    counter.count = 0;
  };

  return counter;
}

// 使用
const counter = createCounter();
console.log(counter()); // 1
console.log(counter()); // 2
counter.reset();
console.log(counter()); // 1
console.log(counter.count); // 1

2. 類實現方式

interface LoggerFunction {
  (message: string): void;
  level: string;
  setLevel(level: string): void;
}

class Logger implements LoggerFunction {
  public level: string = "info";

  constructor() {
    // 將實例作為函數調用時的處理
    const instance = this as unknown as LoggerFunction;
    const func = ((message: string) => {
      console.log(`[${instance.level}] ${message}`);
    }) as LoggerFunction;
  
    // 複製屬性
    Object.setPrototypeOf(func, this);
    Object.assign(func, this);
  
    return func as LoggerFunction;
  }

  setLevel(level: string): void {
    this.level = level;
  }
}

// 使用
const logger = new Logger() as LoggerFunction;
logger("這是一條日誌"); // [info] 這是一條日誌
logger.setLevel("error");
logger("這是一條錯誤日誌"); // [error] 這是一條錯誤日誌

實用的混合類型應用

1. 帶狀態的工具函數

interface CachedFunction<T extends (...args: any[]) => any> {
  (...args: Parameters<T>): ReturnType<T>;
  cache: Map<string, ReturnType<T>>;
  clearCache(): void;
}

function createCachedFunction<T extends (...args: any[]) => any>(
  fn: T
): CachedFunction<T> {
  const cachedFn = ((...args: Parameters<T>) => {
    const key = JSON.stringify(args);
  
    if (cachedFn.cache.has(key)) {
      console.log("使用緩存結果");
      return cachedFn.cache.get(key);
    }
  
    const result = fn(...args);
    cachedFn.cache.set(key, result);
    return result;
  }) as CachedFunction<T>;

  cachedFn.cache = new Map();
  cachedFn.clearCache = () => {
    cachedFn.cache.clear();
  };

  return cachedFn;
}

// 使用
const expensiveCalculation = (a: number, b: number): number => {
  console.log("執行復雜計算...");
  return a * b;
};

const cachedCalc = createCachedFunction(expensiveCalculation);
console.log(cachedCalc(2, 3)); // 執行計算並緩存
console.log(cachedCalc(2, 3)); // 使用緩存
cachedCalc.clearCache();

2. 配置化的函數構造器

interface ConfigurableGreeter {
  (name: string): string;
  greeting: string;
  setGreeting(greeting: string): void;
}

function createGreeter(initialGreeting: string = "Hello"): ConfigurableGreeter {
  const greeter = ((name: string) => {
    return `${greeter.greeting}, ${name}!`;
  }) as ConfigurableGreeter;

  greeter.greeting = initialGreeting;
  greeter.setGreeting = (newGreeting: string) => {
    greeter.greeting = newGreeting;
  };

  return greeter;
}

// 使用
const greeter = createGreeter();
console.log(greeter("Alice")); // Hello, Alice!
greeter.setGreeting("Hi");
console.log(greeter("Bob")); // Hi, Bob!

高級模式:多重混合

// 多重混合類型
interface AdvancedFunction {
  (input: string): string;
  // 配置功能
  prefix: string;
  setPrefix(prefix: string): void;
  // 統計功能
  callCount: number;
  getCallCount(): number;
  // 驗證功能
  validator: (input: string) => boolean;
  setValidator(validator: (input: string) => boolean): void;
}

function createAdvancedFunction(): AdvancedFunction {
  const advancedFn = ((input: string) => {
    advancedFn.callCount++;
  
    if (advancedFn.validator && !advancedFn.validator(input)) {
      throw new Error("輸入驗證失敗");
    }
  
    return `${advancedFn.prefix}${input}`;
  }) as AdvancedFunction;

  // 初始化屬性
  advancedFn.prefix = ">> ";
  advancedFn.callCount = 0;
  advancedFn.validator = (input) => input.length > 0;

  // 初始化方法
  advancedFn.setPrefix = (prefix: string) => {
    advancedFn.prefix = prefix;
  };

  advancedFn.getCallCount = () => advancedFn.callCount;

  advancedFn.setValidator = (validator: (input: string) => boolean) => {
    advancedFn.validator = validator;
  };

  return advancedFn;
}

// 使用
const processor = createAdvancedFunction();
console.log(processor("TypeScript")); // >> TypeScript
processor.setPrefix("處理: ");
console.log(processor("混合類型")); // 處理: 混合類型
console.log(processor.getCallCount()); // 2

最佳實踐

  1. 明確類型定義:使用接口明確定義混合類型的結構和約束
  2. 封裝構造邏輯:提供工廠函數來創建混合類型實例
  3. 保持單一職責:每個混合類型應該有明確的單一目的
  4. 文檔和註釋:為混合類型的使用提供清晰的文檔説明

混合類型是 TypeScript 類型系統靈活性的體現,合理使用可以創建出既功能強大又類型安全的API設計。