ਬਹੁਤ ਸਾਰੀਆਂ TypeScript ਟੀਮਾਂ abstract class ਅਤੇ interface ਨੂੰ ਇੱਕੋ ਜਿਹੇ ਵਜੋਂ ਮੰਨਦੀਆਂ ਹਨ। ਪਰ ਅਜਿਹਾ ਨਹੀਂ ਹੈ। ਗਲਤ ਚੋਣ ਕਰਨ ਦੇ ਗੰਭੀਰ ਨਤੀਜੇ ਹੋ ਸਕਦੇ ਹਨ: ਡੁਪਲੀਕੇਟ ਬਿਜ਼ਨਸ ਲੌਜਿਕ, ਅਜਿਹੇ ਰਿਜਿਡ ਕਲਾਸ ਟ੍ਰੀਜ਼ (rigid class trees) ਜੋ ਕਿਸੇ ਵੀ ਰੀਫੈਕਟਰ (refactor) ਦਾ ਵਿਰੋਧ ਕਰਦੇ ਹਨ, ਅਤੇ ਸੂਖਮ ਰਨਟਾਈਮ ਗਲਤੀਆਂ (runtime errors) ਜੋ ਕਿਸੇ ਅਜਿਹੇ ਬੇਸ ਕਲਾਸ ਤੱਕ ਪਹੁੰਚਦੀਆਂ ਹਨ ਜਿਸ ਨੂੰ ਕਿਸੇ ਨੇ ਸੁਰੱਖਿਅਤ ਮੰਨ ਲਿਆ ਸੀ। ਇਸ ਦਾ ਹੱਲ ਕਿਸੇ ਨਿਯਮਾਂ ਦੀ ਕਿਤਾਬ ਨੂੰ ਯਾਦ ਕਰਨਾ ਨਹੀਂ ਹੈ। ਇਸ ਦਾ ਹੱਲ ਆਪਣੀਆਂ ਅਸਲ ਲੋੜਾਂ ਨੂੰ ਸਮਝਣਾ ਅਤੇ ਉਹਨਾਂ ਦੇ ਅਨੁਸਾਰ ਸਹੀ ਟੂਲ ਦੀ ਚੋਣ ਕਰਨਾ ਹੈ।
ਇੰਟਰਫੇਸ (Interfaces) ਸ਼ੁੱਧ ਇਕਰਾਰਨਾਮੇ (Contracts) ਹਨ
ਇੱਕ interface ਕੰਪਾਈਲ-ਟਾਈਮ ਦੀ ਇੱਕ ਸੀਮਾ ਹੈ। ਇਹ ਦੱਸਦਾ ਹੈ ਕਿ ਇੱਕ ਆਬਜੈਕਟ ਕਿਹੋ ਜਿਹਾ ਹੋਣਾ ਚਾਹੀਦਾ ਹੈ ਅਤੇ ਉਹ ਕੀ ਕਰਨ ਦੇ ਯੋਗ ਹੋਣਾ ਚਾਹੀਦਾ ਹੈ, ਪਰ ਇਹ ਕੋਈ ਇੰਪਲੀਮੈਂਟੇਸ਼ਨ (implementation) ਨਹੀਂ ਦਿੰਦਾ। ਜਦੋਂ TypeScript ਕੰਪਾਈਲ ਹੋ ਕੇ JavaScript ਬਣ ਜਾਂਦਾ ਹੈ, ਤਾਂ ਇੰਟਰਫੇਸ ਪੂਰੀ ਤਰ੍ਹਾਂ ਖਤਮ ਹੋ ਜਾਂਦਾ ਹੈ। ਇਹ ਕੋਈ ਕੰਸਟ੍ਰਕਟਰ (constructor), ਕੋਈ ਪ੍ਰੋਟੋਟਾਈਪ ਚੇਨ (prototype chain), ਜਾਂ ਤੁਹਾਡੇ ਬੰਡਲ ਵਿੱਚ ਕੋਈ ਵਾਧੂ ਬਾਈਟ ਨਹੀਂ ਛੱਡਦਾ।
ਕਲਪਨਾ ਕਰੋ ਕਿ ਤੁਸੀਂ ਇੱਕ ਨੋਟੀਫਿਕੇਸ਼ਨ ਸਿਸਟਮ ਬਣਾ ਰਹੇ ਹੋ। ਤੁਸੀਂ ਇੱਕ ਇੰਟਰਫੇਸ ਪਰਿਭਾਸ਼ਿਤ ਕਰਦੇ ਹੋ:
interface Notifier {
send(message: string): void;
readonly channel: string;
}
ਕੋਈ ਵੀ ਕਲਾਸ, ਜਾਂ ਇੱਕ ਸਾਧਾਰਨ ਆਬਜੈਕਟ ਲਿਟਰਲ (object literal), ਜੋ ਉਸ ਸ਼ੇਪ (shape) ਨੂੰ ਪੂਰਾ ਕਰਦਾ ਹੈ, ਉਹ ਇੱਕ ਵੈਧ Notifier ਹੈ। ਇੱਕ EmailNotifier ਇਸ ਨੂੰ ਇੰਪਲੀਮੈਂਟ ਕਰ ਸਕਦਾ ਹੈ। ਇੱਕ SmsNotifier, SlackNotifier, ਜਾਂ ਇੱਕ ਮੌਕ ਆਬਜੈਕਟ (mock object) ਜੋ ਤੁਸੀਂ ਯੂਨਿਟ ਟੈਸਟ ਲਈ ਦਿੰਦੇ ਹੋ, ਉਹ ਵੀ ਕਰ ਸਕਦਾ ਹੈ। ਕਿਉਂਕਿ ਇੰਟਰਫੇਸ ਵਿੱਚ ਕੋਈ ਕੋਡ ਨਹੀਂ ਹੁੰਦਾ, ਇਸ ਲਈ ਹਰ ਇੰਪਲੀਮੈਂਟੇਸ਼ਨ ਆਪਣਾ send ਮੈਥਡ ਸ਼ੁਰੂ ਤੋਂ ਲਿਖਦੀ ਹੈ। ਇਹ ਉਹੀ ਹੈ ਜੋ ਤੁਸੀਂ ਉਦੋਂ ਚਾਹੁੰਦੇ ਹੋ ਜਦੋਂ ਇੰਪਲੀਮੈਂਟੇਸ਼ਨਾਂ ਸਿਰਫ਼ ਆਪਣੇ ਪਬਲਿਕ ਫੇਸ (public face) ਤੋਂ ਇਲਾਵਾ ਹੋਰ ਕੁਝ ਵੀ ਸਾਂਝਾ ਨਹੀਂ ਕਰਦੀਆਂ।
ਐਬਸਟਰੈਕਟ ਕਲਾਸਾਂ (Abstract Classes) ਵਿੱਚ ਅਸਲ ਕੋਡ ਹੁੰਦਾ ਹੈ
ਇੱਕ ਐਬਸਟਰੈਕਟ ਕਲਾਸ ਇੱਕ ਪੂਰੀ ਕਲਾਸ ਹੁੰਦੀ ਹੈ ਜੋ ਸਿੱਧੀ ਇੰਸਟੈਂਸ਼ੀਏਸ਼ਨ (instantiation) ਦੀ ਮਨਾਹੀ ਕਰਦੀ ਹੈ। ਇਹ ਫੀਲਡਸ, ਲੌਜਿਕ ਵਾਲੇ ਕੰਕਰੀਟ ਮੈਥਡਸ (concrete methods), ਅਤੇ ਕੰਸਟ੍ਰਕਟਰ ਲੌਜਿਕ ਨੂੰ ਪਰਿਭਾਸ਼ਿਤ ਕਰ ਸਕਦੀ ਹੈ। ਇਹ ਸਬ-ਕਲਾਸਾਂ ਨੂੰ ਐਬਸਟਰੈਕਟ ਮੈਥਡਸ ਰਾਹੀਂ ਖਾਲੀ ਥਾਵਾਂ ਭਰਨ ਲਈ ਮਜਬੂਰ ਕਰਦੀ ਹੈ, ਪਰ ਇਹ ਉਹਨਾਂ ਨੂੰ ਉਹ ਵਿਵਹਾਰ (behavior) ਵੀ ਵਿਰਾਸਤ ਵਿੱਚ ਦਿੰਦੀ ਹੈ ਜੋ ਉਹਨਾਂ ਨੂੰ ਖੁਦ ਲਿਖਣ ਦੀ ਲੋੜ ਨਹੀਂ ਹੁੰਦੀ।
ਇੱਕ ਡਾਟਾ ਐਕਸੈਸ ਲੇਅਰ (data access layer) ਬਾਰੇ ਸੋਚੋ। ਤੁਹਾਡੀ ਐਪਲੀਕੇਸ਼ਨ ਦੇ ਹਰ ਰਿਪੋਜ਼ਟਰੀ (repository) ਨੂੰ ਇੱਕ ਆਈਡੈਂਟੀਫਾਇਰ ਨੂੰ ਪਾਰਸ ਕਰਨ, ਸਕੀਮਾ ਦੇ ਵਿਰੁੱਧ ਇਸ ਨੂੰ ਵੈਲੀਡੇਟ ਕਰਨ, ਅਤੇ ਫਿਰ ਹੀ ਸਟੋਰੇਜ-ਵਿਸ਼ੇਸ਼ ਕੁਐਰੀ ਚਲਾਉਣ ਦੀ ਲੋੜ ਹੁੰਦੀ ਹੈ। ਇੱਕ ਇੰਟਰਫੇਸ ਉਸ ਸਾਂਝੀ ਪ੍ਰਕਿਰਿਆ ਨੂੰ ਕੈਪਚਰ ਨਹੀਂ ਕਰ ਸਕਦਾ ਕਿਉਂਕਿ ਇੰਟਰਫੇਸ ਵਿੱਚ ਐਗਜ਼ੀਕਿਊਟੇਬਲ ਕੋਡ ਨਹੀਂ ਹੋ ਸਕਦਾ। ਇੱਕ ਐਬਸਟਰੈਕਟ ਕਲਾਸ ਕਰ ਸਕਦੀ ਹੈ:
abstract class BaseRepository<T> {
protected validateId(id: string): boolean {
return /^[a-z0-9\-]+$/.test(id);
}
abstract fetchById(id: string): Promise<T | null>;
async findById(id: string): Promise<T | null> {
if (!this.validateId(id)) throw new Error("Invalid ID format");
return this.fetchById(id);
}
}
BaseRepository ਢਾਂਚੇ ਨੂੰ ਲਾਗੂ ਕਰਦਾ ਹੈ। ਇਹ ਮੰਗ ਕਰਦਾ ਹੈ ਕਿ ਸਬ-ਕਲਾਸਾਂ fetchById ਨੂੰ ਇੰਪਲੀਮੈਂਟ ਕਰਨ। ਪਰ ਇਹ ਕੰਮ ਕਰਨ ਵਾਲਾ ਲੌਜਿਕ ਵੀ ਪ੍ਰਦਾਨ ਕਰਦਾ ਹੈ। ਸਬ-ਕਲਾਸਾਂ ਨੂੰ ਗਾਰਡਰੇਲਜ਼ (guardrails) ਅਤੇ ਬੁਆਇਲਰਪਲੇਟ (boilerplate) ਮੁਫ਼ਤ ਵਿੱਚ ਮਿਲ ਜਾਂਦੇ ਹਨ। ਜੇਕਰ ਤੁਸੀਂ ਇਸ ਨੂੰ ਇੰਟਰਫੇਸ ਨਾਲ ਬਦਲਣ ਦੀ ਕੋਸ਼ਿਸ਼ ਕੀਤੀ, ਤਾਂ
