ค้นหาเว็บไซต์

มีวิธีใดบ้างที่จะหยุดไม่ให้ซับคลาส override เมธอดใน TypeScript?


ฉันกำลังหาวิธีป้องกันไม่ให้ซับคลาสสามารถโอเวอร์ไรด์เมธอดในคลาสได้ ในภาษาอื่นๆ ฉันจะใช้ final กับเมธอดเพื่อบ่งบอกว่ามันไม่สามารถถูกโอเวอร์ไรด์ได้ แต่สิ่งนั้นไม่ใช่ส่วนหนึ่งของ TypeScript/JavaScript ดังนั้นจึงไม่สามารถใช้เป็นตัวเลือกได้

ฉันกำลังพยายามที่จะติดตั้งเซ็นเซอร์หลายประเภทในตัวจำลอง โดยสิ่งสำคัญอย่างหนึ่งคือฉันสามารถเก็บตัวอย่างจากเซ็นเซอร์ในเวลาใดเวลาหนึ่งและจากนั้นเรียกค่าที่เก็บไว้ในเวลาที่ต่างออกไป

นี่คือเวอร์ชันที่เรียบง่ายของโค้ด

interface Sensor<T> {
  sample(): void; // public method to tell sensor to sample
  read(): T; // public method to get the previously sampled value
}

abstract class AbstractSensor<T> implements Sensor<T> {
  private value: T

  constructor(arg1, arg2, etc,  initialValue: T = undefined){
    this.value = initialValue
  }

  sample(): void {
    this.value = this.sensorSpecficValue()
  }

  // Concrete sub classes would need to override this to return their specific sensor values
  protected abstract sensorSpecficValue(): T // Yes, this needs a new name

  read(): T {
    return this.value
  }
}

ข้างต้นทำงานได้ดี (สมมติว่าฉันได้ลดความซับซ้อนลงจากโค้ดจริงอย่างถูกต้อง) แต่มีปัญหาคืออนุญาตให้ซับคลาสคอนกรีตใด ๆ ของ AbstractClass สามารถแทนที่เมธอด sample() หรือ read() ได้ง่าย ๆ หมายความว่าแม้ว่าพวกเขาจะสร้างเมธอด sensorSpecficValue อย่างถูกต้อง แต่พวกเขาก็อาจทำให้เกิดบั๊กโดยการเปลี่ยนพฤติกรรมของเมธอดทั้งสองตัวนั้น

มีวิธีใดที่ฉันสามารถทำที่นี่เพื่อบ่งบอกในช่วงเวลาคอมไพล์หรือไม่ว่าฟังก์ชันเหล่านั้นไม่ควรถูก override? หรือวิธีคิดที่แตกต่างไปโดยสิ้นเชิงเกี่ยวกับโค้ดนี้ซึ่งจะบรรลุสิ่งที่ฉันต้องการ หัวของฉันติดอยู่กับวิธีแก้ไขแบบ final ดังนั้นฉันอาจพลาดวิธีแก้ปัญหาอื่น ๆ ได้ง่าย

ผมก็โอเคกับวิธีแก้ปัญหาที่ทำงานได้เฉพาะตอนคอมไพล์เท่านั้น ผมทราบดีว่าถ้าผู้พัฒนาต้องการจริง ๆ ในตอนรันไทม์พวกเขาสามารถเข้าไปเปลี่ยนแปลงเมธอด sample() ในแบบที่ผมอาจไม่สามารถตรวจจับได้ตอนคอมไพล์ ผมก็โอเคกับเรื่องนั้น

คำตอบ #1

หวังว่า จะมีใครสักคนที่เก่ง TypeScript กว่าฉันมาช่วยและให้วิธีแก้ปัญหาที่เวลาแปลผลคอมไพล์

จนกว่าพวกเขาจะทำ หรือถ้าไม่ทำ นี่คือวิธีแก้ปัญหาในระหว่างรันไทม์ ซึ่งชัดเจนว่าเป็นตัวเลือกที่ดีที่สุดรองลงมาอย่างมาก:

// A couple of utility functions:

function freezeDataProperty(obj, propName) {
  Object.defineProperty(obj, propName, {
    value: obj[propName],
  }); // `writable`, `configurable`, and `enumerable` all default to `false`
}
function forceNoOverride(instance, cls, methodName) {
  if (instance[methodName] !== cls.prototype[methodName]) {
    throw new Error(`Subclasses may not override the ${cls.name} '${methodName}' method`);
  }
  freezeDataProperty(instance, methodName);
}

// Using them:

abstract class AbstractSensor<T> implements Sensor<T> {
  private value: T

  constructor(arg1, arg2, etc,  initialValue: T = undefined){
    this.value = initialValue
    // Detect overrides and throw, and then prevent overrides on the instance
    forceNoOverride(this, AbstractSensor, "sample");
    forceNoOverride(this, AbstractSensor, "read");
  }

  sample(): void {
    this.value = this.sensorSpecficValue()
  }

  // Concrete sub classes would need to override this to return their specific sensor values
  protected abstract sensorSpecficValue(): T // Yes, this needs a new name

  read(): T {
    return this.value
  }
}
// Prevent replacement on the `AbstractSensor.prototype`
freezeDataProperty(AbstractSensor.prototype, "sample");
freezeDataProperty(AbstractSensor.prototype, "read");

มันอาจจะไม่สมบูรณ์แบบ แต่ถ้ามีทางผ่านมัน ใครบางคนคงต้องพยายามอย่างจริงจังเพื่อหามัน

คำตอบ #2

สำหรับการตรวจสอบในเวลาคอมไพล์ ลอง:

declare const _: unique symbol;
type NoOverride = { [_]: typeof _; }

class A {
  baz: string & NoOverride = '' as any;

  // Note - `ReturnValue & NoOverride`
  foo(): { a: string } & NoOverride {
    return { a: '' } as any;
  }

  // if this function return nothing, use `NoOverride` only
  bar(): NoOverride {
    console.log(0);

    return null!;
  }
}

class B extends A {
  // @ts-expect-error - Type 'string' is not assignable to type 'NoOverride'.
  baz = '';

  // @ts-expect-error - Property '[_]' is missing in type '{ a: string; }' but required in type 'NoOverride'.
  foo() {
    return { a: '' };
  }

  // @ts-expect-error - Type 'void' is not assignable to type 'NoOverride'.
  bar() {
  }
}