ઘણી TypeScript ટીમો abstract class અને interface ને એક જ સેન્ડવિચના બદલી શકાય તેવા પૂરક (fillings) તરીકે જુએ છે. તેઓ એવા નથી. ખોટી પસંદગી કરવાથી ગંભીર સમસ્યાઓ ઊભી થાય છે: ડુપ્લીકેટ બિઝનેસ લોજિક, રિફેક્ટરિંગમાં અવરોધરૂપ એવા જડ ક્લાસ ટ્રીઝ, અને એવા સૂક્ષ્મ રનટાઇમ એરર્સ જે કોઈ એવા બેઝ ક્લાસમાંથી ઉદભવે છે જેને સુરક્ષિત માનવામાં આવ્યો હતો. આનો ઉકેલ કોઈ નિયમાવલી મોઢે કરવાનો નથી, પરંતુ તમારી વાસ્તવિક જરૂરિયાતોને સમજીને તેને અનુરૂપ સાધન પસંદ કરતા શીખવામાં છે.
ઇન્ટરફેસ (Interfaces) એ શુદ્ધ કરારો (Pure Contracts) છે
ઇન્ટરફેસ એ કમ્પાઈલ-ટાઇમ બાઉન્ડ્રી છે. તે વર્ણવે છે કે ઓબ્જેક્ટ કેવો દેખાવો જોઈએ અને તે શું કરી શકવો જોઈએ, પરંતુ તે કોઈ ઇમ્પ્લીમેન્ટેશન (implementation) આપતું નથી. જ્યારે TypeScript કમ્પાઈલ થઈને JavaScript બને છે, ત્યારે ઇન્ટરફેસ સંપૂર્ણપણે અદ્રશ્ય થઈ જાય છે. તે કોઈ કન્સ્ટ્રક્ટર, પ્રોટોટાઇપ ચેઇન અથવા તમારા બંડલમાં વધારાના બાઇટ્સ છોડતું નથી.
કલ્પના કરો કે તમે નોટિફિકેશન સિસ્ટમ બનાવી રહ્યા છો. તમે એક ઇન્ટરફેસ વ્યાખ્યાયિત કરો છો:
interface Notifier {
send(message: string): void;
readonly channel: string;
}
કોઈપણ ક્લાસ, અથવા સાદો ઓબ્જેક્ટ લિટરલ (object literal), જે તે આકારને સંતોષે છે તે માન્ય Notifier છે. EmailNotifier તેને ઇમ્પ્લીમેન્ટ કરી શકે છે. તેવી જ રીતે SmsNotifier, SlackNotifier, અથવા યુનિટ ટેસ્ટ માટે તમે આપેલું મોક (mock) ઓબ્જેક્ટ પણ કરી શકે છે. ઇન્ટરફેસમાં કોઈ કોડ હોતો નથી, તેથી દરેક ઇમ્પ્લીમેન્ટેશન પોતાનું send મેથડ શૂન્યથી લખે છે. જ્યારે ઇમ્પ્લીમેન્ટેશનમાં તેમના પબ્લિક ફેસ સિવાય બીજું કંઈ શેર ન કરવાનું હોય, ત્યારે તમારે આ જ જોઈએ છે.
એબ્સ્ટ્રેક્ટ ક્લાસ (Abstract Classes) વાસ્તવિક કોડ ધરાવે છે
એબ્સ્ટ્રેક્ટ ક્લાસ એ એક સંપૂર્ણ ક્લાસ છે જે સીધું ઇન્સ્ટન્શિયેશન (instantiation) કરવાની મનાઈ કરે છે. તે ફીલ્ડ્સ, લોજિક સાથેના કોંક્રિટ મેથડ્સ અને કન્સ્ટ્રક્ટર લોજિક વ્યાખ્યાયિત કરી શકે છે. તે સબક્લાસને એબ્સ્ટ્રેક્ટ મેથડ્સ દ્વારા ખાલી જગ્યાઓ ભરવા માટે મજબૂર કરે છે, પરંતુ તે તેમને વારસામાં મળતું બિહેવિયર (inherited behavior) પણ આપે છે જે તેમણે જાતે લખવાની જરૂર નથી.
ડેટા એક્સેસ લેયરની કલ્પના કરો. તમારા એપ્લિકેશનમાં દરેક રિપોઝિટરીએ આઈડેન્ટિફાયરને પાર્સ કરવાની, તેને સ્કીમા સામે વેલિડેટ કરવાની અને ત્યારપછી જ સ્ટોરેજ-સ્પેસિફિક ક્વેરી ચલાવવાની જરૂર હોય છે. ઇન્ટરફેસ આ શેર કરેલી પ્રક્રિયાને કેપ્ચર કરી શકતું નથી કારણ કે ઇન્ટરફેસમાં એક્ઝિક્યુટેબલ કોડ હોઈ શકતો નથી. એબ્સ્ટ્રેક્ટ ક્લાસ કરી શકે છે:
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) શેર કરેલા કોડની જરૂર છે?
જો જવાબ 'ના' હોય, તો ઇન્ટરફેસનો ઉપયોગ કરો. જો જવાબ 'હા' હોય, તો એબ્સ્ટ્રેક્ટ ક્લાસનો ઉપયોગ કરો.
આમાં ભૂલ કરવાથી તાત્કાલિક પરિણામો મળે છે. જો તમે એવી જગ્યાએ એબ્સ્ટ્રેક્ટ ક્લાસનો ઉપયોગ કરો જ્યાં માત્ર આકાર (shape) પૂરતો હોત, તો તમે દરેક ઇમ્પ્લીમેન્ટેશનને ઇનહેરિટન્સ ચેઇનમાં ધકેલી દો છો. યુનિટ ટેસ્ટ માટે અચાનક અજીબ મોકિંગ અથવા વાસ્તવિક ક્લાસના પાર્શિયલ સ્ટબ્સની જરૂર પડે છે. થર્ડ-પાર્ટી એક્સ્ટેન્શન માત્ર આકાર સાથે મેચ કરવાને બદલે તમારા બેઝ ક્લાસના સબક્લાસ બનવા મજબૂર થાય છે. તમે એક સરળ કરારને પહાડ બનાવી દીધો છે.
બીજી બાજુ, જો તમે એવી જગ્યાએ ઇન્ટરફેસનો ઉપયોગ કરો જ્યાં શેર કરેલું બિહેવિયર અસ્તિત્વ ધરાવે છે, તો તમે તમારા કોડબેઝમાં એક જ લોજિકની નકલો વિખેરી નાખો છો. જ્યારે તે લોજિકમાં બગ હોય, ત્યારે તમે તેને એક જગ્યાએ સુધારી શકતા નથી. તમારે દસ ઇમ્પ્લીમેન્ટેશનમાં શોધવું પડશે અને આશા રાખવી પડશે કે તમે અગિયારમું ભૂલી ન જાઓ.
વ્યવહારમાં તેઓ ખરેખર કેવી રીતે અલગ પડે છે
દાર્શનિક વિભાજન સિવાય, ત્રણ વ્યવહારુ તફાવતો આ બંનેને અલગ કરે છે.
પરફોર્મન્સ અને બંડલ સાઈઝ. ઇન્ટરફેસ કમ્પાઈલેશન દરમિયાન ભૂંસી
