มีวิธีใดบ้างที่จะหยุดไม่ให้ซับคลาส 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() {
}
}
