使用 TypeScript 实现此 OO 设计的函数式方法

问题描述 投票:0回答:1

假设我用面向对象语言(例如Python)实现了以下设计。我知道 OO 方式有点不自然,有时在使用 TypeScript 时不是首选。那么在 TypeScript 中实现相同设计的功能(和惯用)方法是什么?

想法是:

  • 我有一个
    Car
    类型来描述通用汽车。
  • 对于所有手动变速箱汽车,我可以提供一个抽象类:
    • 按照启动手动变速箱汽车的经典步骤实施
      Car
      类型,并且
    • 提供一些hook抽象方法供子类实现,并且
    • 提供一些默认impl的方法,以便子类可以选择重用或重写。
  • 对于其他类型的汽车,我可以自由地以不同的方式实现
    Car
    类型。

这将是 Python 代码:

from abc import ABC, abstractmethod
import json

class Car(ABC):
    @abstractmethod
    def start(self):
        raise NotImplemented

class ManualGearboxCar(Car):
    def start(self):
        self.engage_clutch()
        self.start_engine()
        
        dashboard_info = self.read_dashboard()
        print("Dashboard:", json.dumps(dashboard_info))
        # ... Check dashboard_info and raise if anything goes wrong ...
        if not dashboard_info["ok"]:
            raise Exception("Something is not OK")

        self.shift_gear(1)
        self.release_clutch()
        print("Car started!")

    def read_dashboard(self) -> dict:
        return {
            "foo": "bar",
            "default": "values",
            "ok": True,
        }

    def engage_clutch(self):
        print("[DefaultClutch] engaged!")

    def release_clutch(self):
        print("[DefaultClutch] released!")

    @abstractmethod
    def start_engine(self):
        raise NotImplemented

    @abstractmethod
    def shift_gear(self, level: int):
        raise NotImplemented

class Picasso(ManualGearboxCar):
    def read_dashboard(self) -> dict:
        info = super().read_dashboard()
        return info | {
            "brand": "Citroen",
        }

    def start_engine(self):
        print("[Picasso] engine started!")

    def shift_gear(self, level: int):
        print("[Picasso] gear shifted to:", level)


class Tesla(Car):
    def start(self):
        print("[Tesla] Wow it just starts!")

if __name__ == "__main__":
    picasso = Picasso()
    picasso.start()

    tesla = Tesla()
    tesla.start()

这就是我在 TypeScript 中实现相同功能的想法,仍然采用 OO 风格,基本上是将相同的代码从 Python 移植到 TS:

type Car = {
  start(): void;
}

abstract class ManualGearCar implements Car {
  start(): void {
    this.engageClutch();
    this.startEngine();
    const dashboardInfo = this.readDashboard();
    // ... Check dashboard_info and raise if anything goes wrong ...
    console.log(`Dashboard: ${JSON.stringify(dashboardInfo)}`);
    if (!dashboardInfo.ok) {
      throw new Error("Something is not OK");
    }
    this.shiftGear(1);
    this.releaseClutch();
    console.log("Car started!");
  }

  readDashboard(): Record<string, any> & { ok: boolean } {
    return {
      foo: "bar",
      default: "values",
      ok: true,
    };
  }

  engageClutch(): void {
    console.log("[DefaultClutch] engaged!");
  }

  releaseClutch(): void {
    console.log("[DefaultClutch] released!");
  }

  abstract startEngine(): void;

  abstract shiftGear(level: number): void;
}


class Picasso extends ManualGearCar {
  readDashboard(): Record<string, any> & { ok: boolean; } {
    const info = super.readDashboard();
    return { ...info, brand: "Citroen" };
  }

  startEngine(): void {
    console.log("[Picasso] engine started!");
  }

  shiftGear(level: number): void {
    console.log(`[Picasso] gear shifted to: ${level}`);
  }
}

const createPicasso = (): Car => {
  return new Picasso();
}

const createTesla = (): Car => {
  return {
    start() {
      console.log("[Tesla] Wow it just starts!");
    }
  }
}

function main() {
  const picasso = createPicasso();
  picasso.start();

  const tesla = createTesla();
  tesla.start()
}

main();

如果采用函数式风格,你会做什么?

谢谢!

typescript oop idioms
1个回答
0
投票

看起来您正在描述模板方法模式

如果您喜欢更实用的风格,就像许多其他设计模式一样,这可以使用高阶函数轻松实现。然后,您可以将函数作为参数传递来代替抽象方法。

您的示例目前有些人为,因为该类没有状态,因此可以简单地用简单的函数替换。假设您需要状态,您可以将方法替换为接受状态作为参数并返回更新后的状态的函数。我在下面添加了一个示例。为了保留在 ManualGearCar 的“子类”中扩展状态的能力,我将 create 函数(替换构造函数)设为通用。

namespace ManualGearCar {
   export interface Car {
    wheels: number;
    gears: number;
    currentGear: number;
    engineStarted: boolean;
   }
   
  function readDashboard(): Record<string, any> & { ok: boolean } {
    return {
      foo: "bar",
      default: "values",
      ok: true,
    };
  }

  function engageClutch(): void {
    console.log("[DefaultClutch] engaged!");
  }

  function releaseClutch(): void {
    console.log("[DefaultClutch] released!");
  }
   
   export function create<T extends Car>(state: T, startEngine: (state: T) => T, shiftGear: (state: T, gear: number) => T) : T {
    engageClutch();
    const state2 = startEngine(state);
    const dashboardInfo = readDashboard();
    // ... Check dashboard_info and raise if anything goes wrong ...
    console.log(`Dashboard: ${JSON.stringify(dashboardInfo)}`);
    if (!dashboardInfo.ok) {
      throw new Error("Something is not OK");
    }
    const state3 = shiftGear(state2, 1);
    releaseClutch();
    console.log("Car started!");
    return state3;
   } 
}


function createPicasso() {

  const state = {
    wheels: 4,
    gears: 5,
    currentGear: 0,
    engineStarted: false
  };  

  return ManualGearCar.create(
    state, 
    (s: ManualGearCar.Car) => ({...s, engineStarted: true}), 
    (s: ManualGearCar.Car, gear: number) => ({...s, gear: gear}));
}

interface RaceCar extends ManualGearCar.Car {
  maxSpeed: number
}

function createFormulaOneCar() {
  const state = {
    wheels: 4,
    gears: 5,
    currentGear: 0,
    engineStarted: false,
    maxSpeed: 400
  };  

  return ManualGearCar.create(
    state, 
    (s: RaceCar) => ({...s, engineStarted: true}), 
    (s: RaceCar, gear: number) => ({...s, gear: gear, maxSpeed: s.maxSpeed + 10}));
}
© www.soinside.com 2019 - 2024. All rights reserved.