Timu nyingi za TypeScript huchukulia abstract class na interface kama viungo vinavyoweza kubadilishana katika sandwich moja. Lakini si hivyo. Kutumia kimoja badala ya kingine husababisha madhara ya kweli: kurudia mantiki ya biashara (business logic), miti ya madarasa (class trees) migumu inayopinga kila marekebisho (refactor), na makosa madogo ya wakati wa utendaji (runtime errors) yanayotokana na darasa la msingi (base class) ambalo mtu alidhani lilikuwa salama. Suluhisho si kukariri kitabu cha sheria. Ni kujifunza kuangalia mahitaji yako halisi na kuchagua kifaa kinachoyalingana.

Interface ni Mikataba Safi

Interface ni mpaka wa wakati wa kuunganisha (compile-time boundary). Inaelezea jinsi kitu (object) kinapaswa kuonekana na kile kinachopaswa kuweza kufanya, lakini haitoi utekelezaji (implementation) wowote. Baada ya TypeScript kuunganishwa (compile) kuwa JavaScript, interface hutoweka kabisa. Haiazi constructor, mnyororo wa prototaipe (prototype chain), wala bayti za ziada kwenye kifurushi (bundle) chako.

Fikiria unajenga mfumo wa arifa (notification system). Unafafanua interface:

interface Notifier {
  send(message: string): void;
  readonly channel: string;
}

Darasa lolote, au hata kitu rahisi (object literal), kinachokidhi umbo hilo ni Notifier halali. EmailNotifier inaweza kuitekeleza. Vivyo hivyo SmsNotifier, SlackNotifier, au kitu cha kuiga (mock object) unachokipa kwa ajili ya jaribio la kitengo (unit test). Kwa sababu interface haina kodi, kila utekelezaji huandika njia yake ya send kuanzia mwanzo. Hicho ndicho unachotaka wakati utekelezaji haushiriki kitu chochote isipokuwa muonekano wake wa nje (public face).

Abstract Classes Hubeba Kodi Halisi

Abstract class ni darasa kamili ambalo limekatazwa kutengenezwa moja kwa moja (direct instantiation). Inaweza kufafanua nyanja (fields), njia madhubuti (concrete methods) zenye mantiki, na mantiki ya constructor. Inazilazimisha madarasa madogo (subclasses) kujaza nafasi zilizo wazi kupitia njia za abstract, lakini pia inazipa tabia zilizorithiwa (inherited behavior) ambazo hawahitaji kuzijua wenyewe.

Wazia tabaka la ufikiaji wa data (data access layer). Kila ghala (repository) katika programu yako inahitaji kuchanganua utambulisho (identifier), kuuhakiki dhidi ya muundo (schema), na kisha pekee ndipo iendeshe hoja (query) maalum ya hifadhi. Interface haiwezi kunasa mfuatano huo wa pamoja kwa sababu interface haiwezi kuwa na kodi inayoweza kutekelezwa. Abstract class inaweza:

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 inalazimisha muundo. Inadai kwamba madarasa madogo yatekeleze fetchById. Lakini pia inatoa mantiki inayofanya kazi. Madarasa madogo hupata miongozo (guardrails) na kodi ya awali (boilerplate) bila malipo. Ukijaribu kubadilisha hii kwa interface, utajikuta unanakili validateId na findById kwenye kila ghala. Hiyo si abstraction. Ni kodi ya ziada ya matengenezo (maintenance tax).

Swali Moja linaloamua

Chaguo lako linapaswa kila wakati kurejea swali moja: Je, mkataba huu unahitaji kusambaza kodi ya pamoja?

Ikiwa jibu ni hapana, tumia interface. Ikiwa jibu ni ndiyo, tumia abstract class.

Kukosea huku kuna madhara ya haraka. Ukitumia abstract class pale ambapo umbo (shape) lingetosha, unalazimisha kila utekelezaji kuingia katika mnyororo wa urithi (inheritance chain). Majaribio ya kitengo (unit tests) ghafla yanahitaji uigaji (mocking) wa ajabu au sehemu za darasa halisi (partial stubs). Viongezi vya upande wa tatu (third-party extensions) lazima viwe madarasa madogo ya msingi wako badala ya kulingana tu na umbo. Umegeuza mkataba rahisi kuwa mlima.

Kwa upande mwingine, ukitumia interface pale ambapo tabia ya pamoja ipo, unasambaza nakala za mantiki ile ile kwenye kodi yako (codebase). Mantiki hiyo inapokuwa na hitilafu (bug), huirekebishi mahali pamoja. Unatafuta kupitia utekelezaji kumi na unatumai hutakosa wa kumi na moja.

Jinsi Zinavyotofautiana Kiuhalisia

Zaidi ya mgawanyiko wa kifalsafa, kuna mapengo matatu ya kivitendo yanayotofautisha hizi mbili.

Utendaji na ukubwa wa kifurushi (bundle size). Interface hufutwa wakati wa kuunganisha (compilation). Haziongezi uzito wowote kwenye matokeo yako ya JavaScript na hazitumii kumbukumbu ya wakati wa utendaji (runtime memory). Abstract classes huunganishwa kuwa kazi za constructor halisi na minyororo ya prototaipe. Kila moja unayofafanua inaongeza ukubwa wa kifurushi chako na inakaa kwenye kumbukumbu inapoundwa (instantiated). Kwenye seva inayoshughulikia maelfu ya mifano, au kifurushi cha mbele (frontend bundle) kinachokaguliwa kwa karibu, tofauti hiyo si ya kinadharia tu.

Unyumbufu wa muundo (Flexibility of composition). Darasa moja linaweza kutekeleza interface nyingi kwa wakati mmoja. Unaweza kujenga FileCache inayotekeleza Cache, Disposable, na EventEmitter kwa pamoja. TypeScript inafurahi kwa sababu interface hazilazimishi utaratibu wa daraja (hierarchy). Hata hivyo, darasa linaweza kupanua (extend) abstract class moja tu. Mnyororo wa prototaipe wa JavaScript hausaidii urithi mwingi (multiple inheritance). Ukitegemea sana abstract classes, hatimaye utakabiliana na mtatizo wa kawaida wa kujaribu kuunganisha madarasa mawili ya msingi ambayo yote yana