Suppose that I have the following design implemented in an OO language (e.g. Python). I know that the OO way is a bit unnatural and sometimes not preferred when using TypeScript. So what would be the functional (and idiomatic) way to implement the same design in TypeScript?
The ideas are:
- I have a
Cartype to describe a generic car. - For all manual gearbox cars, I can provide an abstract class to:
- implement the
Cartype following the classic steps to start a manual gearbox car, and - provide some hook abstract methods for subclass to implement, and
- provide some methods with default impl so that subclasses can choose to reuse or override.
- implement the
- For other types of cars, I am free to implement the
Cartype in a different way.
This would be the Python code:
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()
This is what I come up with to implement the same thing in TypeScript, still in an OO style which is basically porting the same code from Python to 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();
What would you do in a functional style?
Disclaimer: I am not asking for a subjective judgement of a "better" functional solution. I'm asking for an objective answer of whether there also exist a functional solution to this concrete problem.
Thanks!
Teslaa regular class that implementsCar. This feels like it might be an opinion question because it's hard to know who decides whether something is "idiomatic". Do you have some objective criterion by which we'd be able to verify an answer's correctness?