പല TypeScript ടീമുകളും abstract class-ഉം interface-ഉം ഒരേ കാര്യത്തിന് ഉപയോഗിക്കാവുന്നവയാണെന്ന് തെറ്റായി കരുതുന്നു. എന്നാൽ അവ അങ്ങനെയല്ല. തെറ്റായ ഒന്ന് തിരഞ്ഞെടുക്കുന്നത് ഗുരുതരമായ പ്രശ്നങ്ങൾക്ക് കാരണമാകും: ഡ്യൂപ്ലിക്കേറ്റ് ആയ ബിസിനസ് ലോജിക്, റീഫാക്ടറിംഗിനെ പ്രതിരോധിക്കുന്ന കടുപ്പമേറിയ ക്ലാസ് ട്രീകുകൾ, സുരക്ഷിതമാണെന്ന് കരുതിയ ഒരു ബേസ് ക്ലാസ്സിൽ നിന്ന് തുടങ്ങുന്ന സൂക്ഷ്മമായ റൺടൈം എററുകൾ എന്നിവ ഇതിൽ ഉൾപ്പെടുന്നു. ഇതിനുള്ള പരിഹാരം ഒരു നിയമപുസ്തകം കാണാതെ പഠിക്കുക എന്നതല്ല. മറിച്ച്, നിങ്ങളുടെ യഥാർത്ഥ ആവശ്യകതകൾ മനസ്സിലാക്കി അതിന് അനുയോജ്യമായ ടൂൾ തിരഞ്ഞെടുക്കാൻ പഠിക്കുക എന്നതാണ്.
ഇന്റർഫേസുകൾ വെറും കരാറുകൾ (Contracts) മാത്രമാണ്
ഒരു ഇന്റർഫേസ് എന്നത് കംപൈൽ സമയത്തെ ഒരു അതിർവരമ്പാണ്. ഒരു ഒബ്ജക്റ്റ് എങ്ങനെയുള്ളതായിരിക്കണം എന്നും അതിന് എന്തൊക്കെ ചെയ്യാൻ കഴിയണം എന്നും അത് വിവരിക്കുന്നു, എന്നാൽ അതിൽ യാതൊരു ഇംപ്ലിമെന്റേഷനും (implementation) ഉണ്ടാകില്ല. TypeScript കംപൈൽ ചെയ്ത് JavaScript ആകുമ്പോൾ, ഇന്റർഫേസ് പൂർണ്ണമായും അപ്രത്യക്ഷമാകുന്നു. ഇത് കൺസ്ട്രക്റ്ററുകളോ (constructor), പ്രോട്ടോടൈപ്പ് ചെയിനോ (prototype chain), ബണ്ടിലിൽ അധിക ബൈറ്റുകളോ അവശേഷിപ്പിക്കില്ല.
നിങ്ങൾ ഒരു നോട്ടിഫിക്കേഷൻ സിസ്റ്റം നിർമ്മിക്കുകയാണെന്ന് സങ്കൽപ്പിക്കുക. നിങ്ങൾ ഒരു ഇന്റർഫേസ് നിർവചിക്കുന്നു:
interface Notifier {
send(message: string): void;
readonly channel: string;
}
ആ രൂപത്തിന് അനുയോജ്യമായ ഏതൊരു ക്ലാസ്സും, അല്ലെങ്കിൽ ഒരു പ്ലെയിൻ ഒബ്ജക്റ്റ് ലിറ്ററലും (object literal) സാധുവായ ഒരു Notifier ആണ്. ഒരു EmailNotifier-ന് ഇത് ഇംപ്ലിമെന്റ് ചെയ്യാം. അതുപോലെ തന്നെ SmsNotifier, SlackNotifier, അല്ലെങ്കിൽ ഒരു യൂണിറ്റ് ടെസ്റ്റിന് നൽകുന്ന ഒരു മോക്ക് ഒബ്ജക്റ്റ് (mock object) എന്നിവയും സാധ്യമാണ്. ഇന്റർഫേസിൽ കോഡ് ഇല്ലാത്തതിനാൽ, ഓരോ ഇംപ്ലിമെന്റേഷനും സ്വന്തം send മെത്തേഡ് ആദ്യം മുതൽ തന്നെ എഴുതുന്നു. ഇംപ്ലിമെന്റേഷനുകൾ തമ്മിൽ പൊതുവായ രൂപം (public face) ഒഴികെ മറ്റൊന്നും പങ്കിടാത്ത സാഹചര്യത്തിൽ നിങ്ങൾ ആഗ്രഹിക്കുന്നത് ഇതാണ്.
അബ്സ്ട്രാക്റ്റ് ക്ലാസുകൾ യഥാർത്ഥ കോഡ് ഉൾക്കൊള്ളുന്നു
നേരിട്ട് ഇൻസ്റ്റാൻഷ്യേറ്റ് (instantiation) ചെയ്യാൻ പാടില്ലാത്ത ഒരു പൂർണ്ണമായ ക്ലാസ്സാണ് അബ്സ്ട്രാക്റ്റ് ക്ലാസ്സ്. ഇതിന് ഫീൽഡുകൾ, ലോജിക്കോട് കൂടിയ കോൺക്രീറ്റ് മെത്തേഡുകൾ (concrete methods), കൺസ്ട്രക്റ്റർ ലോജിക് എന്നിവ നിർവചിക്കാൻ കഴിയും. അബ്സ്ട്രാക്റ്റ് മെത്തേഡുകളിലൂടെ സബ്ക്ലാസുകളെ (subclasses) വിട്ടുപോയ ഭാഗങ്ങൾ പൂരിപ്പിക്കാൻ ഇത് നിർബന്ധിക്കുന്നു, എന്നാൽ അവയ്ക്ക് സ്വന്തമായി എഴുതേണ്ടതില്ലാത്ത ഇൻഹെറിറ്റഡ് ബിഹേവിയറുകളും (inherited behavior) ഇത് നൽകുന്നു.
ഒരു ഡാറ്റാ ആക്സസ് ലെയർ (data access layer) സങ്കൽപ്പിക്കുക. നിങ്ങളുടെ ആപ്ലിക്കേഷനിലെ ഓരോ റിപ്പോസിറ്ററിയും (repository) ഒരു ഐഡന്റിഫയർ പാഴ്സ് ചെയ്യുകയും, ഒരു സ്കീമയുമായി അത് പരിശോധിക്കുകയും (validate), അതിനുശേഷം മാത്രം സ്റ്റോറേജ്-സ്പെസിഫിക് ക്വറി പ്രവർത്തിപ്പിക്കുകയും വേണം. ഒരു ഇന്റർഫേസിന് ഈ പങ്കിട്ട പ്രക്രിയയെ (shared sequence) ഉൾക്കൊള്ളാൻ കഴിയില്ല, കാരണം ഒരു ഇന്റർഫേസിന് എക്സിക്യൂട്ടബിൾ കോഡ് ഉൾക്കൊള്ളാൻ കഴിയില്ല. എന്നാൽ ഒരു അബ്സ്ട്രാക്റ്റ് ക്ലാസ്സിന് അത് സാധ്യമാണ്:
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) സൗജന്യമായി ലഭിക്കുന്നു. ഇതിന് പകരം ഒരു ഇന്റർഫേസ് ഉപയോഗിക്കാൻ ശ്രമിച്ചാൽ, നിങ്ങൾ ഓരോ റിപ്പോസിറ്ററിയിലും validateId, findById എന്നിവ കോപ്പി ചെയ്യേണ്ടി വരും. അത് അബ്സ്ട്രാക്ഷൻ അല്ല, മറിച്ച് മെയിന്റനൻസ് ഭാരമാണ് (maintenance tax).
തീരുമാനമെടുക്കാൻ സഹായിക്കുന്ന ആ ഒരു ചോദ്യം
നിങ്ങളുടെ തിരഞ്ഞെടുപ്പ് എപ്പോഴും ഒരു ചോദ്യത്തിൽ ഒതുങ്ങണം: ഈ കരാറിൽ (contract) പങ്കിട്ട കോഡ് (shared code) ഉൾപ്പെടുത്തേണ്ടതുണ്ടോ?
ഉത്തരം 'അല്ല' എന്നാണെങ്കിൽ, ഒരു ഇന്റർഫേസ് ഉപയോഗിക്കുക. ഉത്തരം 'അതെ' എന്നാണെങ്കിൽ, ഒരു അബ്സ്ട്രാക്റ്റ് ക്ലാസ്സ് ഉപയോഗിക്കുക.
ഇത് തെറ്റായി ചെയ്താൽ പെട്ടെന്ന് തന്നെ പ്രത്യാഘാതങ്ങൾ ഉണ്ടാകും. ഒരു രൂപം (shape) മാത്രം മതിയാകുന്ന ഇടത്ത് നിങ്ങൾ ഒരു അബ്സ്ട്രാക്റ്റ് ക്ലാസ്സ് ഉപയോഗിച്ചാൽ, ഓരോ ഇംപ്ലിമെന്റേഷനെയും ഒരു ഇൻഹെറിറ്റൻസ് ചെയിനിലേക്ക് (inheritance chain) നിങ്ങൾ നിർബന്ധിക്കുന്നു. യൂണിറ്റ് ടെസ്റ്റുകൾക്ക് പെട്ടെന്ന് തന്നെ ഒരു യഥാർത്ഥ ക്ലാസ്സിന്റെ മോക്കിംഗോ (mocking) പാർട്ടിയൽ സ്റ്റബ്ബുകളോ (partial stubs) ആവശ്യമായി വരും. തേർഡ് പാർട്ടി എക്സ്റ്റൻഷനുകൾക്ക് വെറുമൊരു രൂപം പിന്തുടരുന്നതിന് പകരം നിങ്ങളുടെ ബേസ് ക്ലാസ്സ് സബ്ക്ലാസ് ചെയ്യേണ്ടി വരും. നിങ്ങൾ ഒരു ലളിതമായ കരാറിനെ ഒരു വലിയ മലയായും മാറ്റിയിരിക്കുന്നു.
മറുവശത്ത്, പങ്കിട്ട ബിഹേവിയർ ഉള്ള ഇടങ്ങളിൽ നിങ്ങൾ ഒരു ഇന്റർഫേസ് ഉപയോഗിക്കുകയാണെങ്കിൽ, ഒരേ ലോജിക്കിന്റെ പകർപ്പുകൾ നിങ്ങളുടെ കോഡ്ബേസിലുടനീളം ചിതറിക്കിടക്കും. ആ ലോജിക്കിൽ ഒരു ബഗ് ഉണ്ടെങ്കിൽ, നിങ്ങൾക്ക് അത് ഒരിടത്ത് മാത്രം ശരിയാക്കാൻ കഴിയില്ല. പത്ത് ഇംപ്ലിമെന്റേഷനുകളിലൂടെ നിങ്ങൾ അത് തിരയേണ്ടി വരും, പതിനൊന്നാമത്തേത് വിട്ടുപോകില്ല എന്ന് പ്രത്യാശിക്കുകയേ ഉള്ളൂ.
പ്രായോഗികമായി അവ എങ്ങനെ വ്യത്യാസപ്പെട്ടിരിക്കുന്നു
തത്വപരമായ വ്യത്യാസങ്ങൾ കൂടാതെ, മൂന്ന് പ്രായോഗികമായ വ്യത്യാസങ്ങൾ ഇവയെ വേർതിരിക്കുന്നു.
പെർഫോമൻസും ബണ്ടിൽ സൈസും (Performance and bundle size). കംപൈലേഷൻ സമയത്ത് ഇന്റർഫേസുകൾ നീക്കം ചെയ്യപ്പെടുന്നു. അവ നിങ്ങളുടെ ജാവാസ്ക്രിപ്റ്റ് ഔട്ട്പുട്ടിന് യാതൊരു ഭാരവും കൂട്ടുന്നില്ല, റൺടൈം മെമ്മറിയും ഉപയോഗിക്കുന്നില്ല. എന്നാൽ അബ്സ്ട്രാക്റ്റ് ക്ലാസുകൾ യഥാർത്ഥ കൺസ്ട്രക്റ്റർ ഫംഗ്ഷനുകളായും പ്രോട്ടോടൈപ്പ് ചെയിനുകളായും കംപൈൽ ചെയ്യപ്പെടുന്നു. നിങ്ങൾ നിർവചിക്കുന്ന ഓരോ ക്ലാസ്സും നിങ്ങളുടെ ബണ്ടിലിൽ ഭാരം കൂട്ടുകയും ഇൻസ്റ്റാൻഷ്യേറ്റ് ചെയ്യുമ്പോൾ മെമ്മറി ഉപയോഗിക്കുകയും ചെയ്യുന്നു. ആയിരക്കണക്കിന് ഇൻസ്റ്റൻസുകൾ കൈകാര്യം ചെയ്യുന്ന ഒരു സെർവറിലോ, സൂക്ഷ്മമായി പരിശോധിക്കപ്പെടുന്ന ഒരു ഫ്രണ്ട്എൻഡ് ബണ്ടിലിലോ ഈ വ്യത്യാസം കേവലം സൈദ്ധാന്തികമല്ല.
കോമ്പോസിഷന്റെ ഫ്ലെക്സിബിലിറ്റി (Flexibility of composition). ഒരു ക്ലാസ്സിന് ഒരേസമയം ഒന്നിലധികം ഇന്റർഫേസുകൾ ഇംപ്ലിമെന്റ് ചെയ്യാൻ കഴിയും. Cache, Disposable, EventEmitter എന്നിവ ഒരേസമയം ഇംപ്ലിമെന്റ് ചെയ്യുന്ന ഒരു FileCache നിങ്ങൾക്ക് നിർമ്മിക്കാം. ഇന്റർഫേസുകൾ ഒരു ഹൈരാർക്കി (hierarchy) അടിച്ചേൽപ്പിക്കാത്തതിനാൽ TypeScript ഇതിൽ സംതൃപ്തമാണ്. എന്നാൽ ഒരു ക്ലാസ്സിന് ഒരു അബ്സ്ട്രാക്റ്റ് ക്ലാസ്സ് മാത്രമേ എക്സ്റ്റെൻഡ് ചെയ്യാൻ കഴിയൂ. ജാവാസ്ക്രിപ്റ്റിന്റെ പ്രോട്ടോടൈപ്പ് ചെയിൻ മൾട്ടിപ്പിൾ ഇൻഹെറിറ്റൻസിനെ (multiple inheritance) പിന്തുണയ്ക്കുന്നില്ല. നിങ്ങൾ അബ്സ്ട്രാക്റ്റ് ക്ലാസുകളെ അമിതമായി ആശ്രയിക്കുകയാണെങ്കിൽ, ഒരേ കാര്യങ്ങൾ അടങ്ങിയ രണ്ട് ബേസ് ക്ലാസുകളെ യോജിപ്പിക്കാൻ ശ്രമിക്കുന്ന ഒരു സങ്കീർണ്ണമായ പ്രശ്നത്തെ നിങ്ങൾ നേരിടേണ്ടി വരും...
