Atskleiskite Python abstrakčiųjų bazinių klasių (ABC) galią. Sužinokite esminį skirtumą tarp protokolu pagrįsto struktūrinio tipizavimo ir formalaus sąsajos dizaino.
Python abstrakčiosios bazinės klasės: įvaldome protokolo įgyvendinimą ir sąsajos dizainą
Programinės įrangos kūrimo pasaulyje galutinis tikslas yra kurti patikimas, prižiūrimas ir mastelį keičiančias (angl. scalable) programas. Projektams augant nuo kelių scenarijų iki sudėtingų sistemų, kurias valdo tarptautinės komandos, aiškios struktūros ir nuspėjamų kontraktų poreikis tampa svarbiausiu. Kaip užtikrinti, kad skirtingi komponentai, kuriuos galbūt rašė skirtingi programuotojai skirtingose laiko juostose, galėtų sąveikauti sklandžiai ir patikimai? Atsakymas slypi abstrakcijos principe.
Python, pasižymintis savo dinamiška prigimtimi, turi garsią abstrakcijos filosofiją: „duck typing“. Jei objektas vaikšto kaip antis ir kvaksi kaip antis, mes jį laikome ančia. Šis lankstumas yra viena didžiausių Python stiprybių, skatinanti greitą kūrimą ir švarų, skaitomą kodą. Tačiau didelės apimties programose pasikliavimas vien numanomais susitarimais gali sukelti subtilių klaidų ir priežiūros galvos skausmų. Kas nutinka, kai „antis“ netikėtai negali skristi? Štai čia į sceną žengia Python abstrakčiosios bazinės klasės (ABC), suteikiančios galingą mechanizmą kurti formalius kontraktus, neaukojant Python dinamiškumo dvasios.
Tačiau čia slypi esminis ir dažnai neteisingai suprantamas skirtumas. Python ABC nėra universalus įrankis, tinkantis visoms situacijoms. Jos tarnauja dviem skirtingoms, galingoms programinės įrangos projektavimo filosofijoms: kurti aiškias, formalias sąsajas, kurios reikalauja paveldėjimo, ir apibrėžti lanksčius protokolus, kurie tikrina gebėjimus. Suprasti skirtumą tarp šių dviejų požiūrių – sąsajos dizaino ir protokolo įgyvendinimo – yra raktas į pilno objektinio programavimo potencialo atskleidimą Python'e ir kodo rašymą, kuris yra tiek lankstus, tiek saugus. Šis vadovas išnagrinės abi filosofijas, pateiks praktinių pavyzdžių ir aiškių gairių, kada naudoti kiekvieną požiūrį jūsų pasauliniuose programinės įrangos projektuose.
Pastaba dėl formatavimo: siekiant laikytis specifinių formatavimo apribojimų, kodo pavyzdžiai šiame straipsnyje pateikiami standartinėse teksto žymose, naudojant paryškintą ir pasvirąjį šriftus. Siekdami geriausio skaitomumo, rekomenduojame juos nukopijuoti į savo redaktorių.
Pagrindai: kas tiksliai yra abstrakčiosios bazinės klasės?
Prieš gilinantis į dvi projektavimo filosofijas, sukurkime tvirtą pagrindą. Kas yra abstrakčioji bazinė klasė? Iš esmės, ABC yra planas (angl. blueprint) kitoms klasėms. Ji apibrėžia metodų ir savybių rinkinį, kurį turi įgyvendinti bet kuris reikalavimus atitinkantis poklasis. Tai būdas pasakyti: „Bet kuri klasė, kuri teigia esanti šios šeimos dalis, privalo turėti šiuos specifinius gebėjimus.“
Python integruotas `abc` modulis suteikia įrankius ABC kūrimui. Du pagrindiniai komponentai yra:
- `ABC`: pagalbinė klasė, naudojama kaip metaklasė ABC sukurti. Šiuolaikiniame Python (3.4+), galite tiesiog paveldėti iš `abc.ABC`.
- `@abstractmethod`: dekoratorius, naudojamas pažymėti metodus kaip abstrakčius. Bet kuris ABC poklasis privalo įgyvendinti šiuos metodus.
Yra dvi pagrindinės taisyklės, reglamentuojančios ABC:
- Negalima sukurti ABC egzemplioriaus, kuriame yra neįgyvendintų abstrakčių metodų. Tai yra šablonas, o ne baigtas produktas.
- Bet kuri konkreti poklasė privalo įgyvendinti visus paveldėtus abstrakčius metodus. Jei to nepadaro, ji taip pat tampa abstrakčia klase, ir jos egzemplioriaus sukurti negalima.
Pažiūrėkime, kaip tai veikia su klasikiniu pavyzdžiu: sistema, skirta medijos failams apdoroti.
Pavyzdys: paprasta MediaFile ABC
Įsivaizduokime, kad kuriame programą, kuri turi apdoroti įvairių tipų mediją. Žinome, kad kiekvienas medijos failas, nepriklausomai nuo jo formato, turėtų būti paleidžiamas ir turėti tam tikrus metaduomenis. Šį kontraktą galime apibrėžti su ABC.
import abc
class MediaFile(abc.ABC):
def __init__(self, filepath: str):
self.filepath = filepath
print(f"Base init for {self.filepath}")
@abc.abstractmethod
def play(self) -> None:
"""Paleidžia medijos failą."""
raise NotImplementedError
@abc.abstractmethod
def get_metadata(self) -> dict:
"""Grąžina medijos metaduomenų žodyną."""
raise NotImplementedError
Jei bandysime sukurti `MediaFile` egzempliorių tiesiogiai, Python mus sustabdys:
# Tai sukels TypeError klaidą
# media = MediaFile("path/to/somefile.txt")
# TypeError: Negalima sukurti abstrakčios klasės MediaFile egzemplioriaus su abstrakčiais metodais get_metadata, play
Norėdami naudoti šį planą, turime sukurti konkrečius poklasius, kurie pateikia `play()` ir `get_metadata()` įgyvendinimus.
class AudioFile(MediaFile):
def play(self) -> None:
print(f"Playing audio from {self.filepath}...")
def get_metadata(self) -> dict:
return {"codec": "mp3", "duration_seconds": 180}
class VideoFile(MediaFile):
def play(self) -> None:
print(f"Playing video from {self.filepath}...")
def get_metadata(self) -> dict:
return {"codec": "h264", "resolution": "1920x1080"}
Dabar galime kurti `AudioFile` ir `VideoFile` egzempliorius, nes jie atitinka `MediaFile` apibrėžtą kontraktą. Tai yra pagrindinis ABC mechanizmas. Tačiau tikroji galia slypi tame, *kaip* mes naudojame šį mechanizmą.
Pirmoji filosofija: ABC kaip formalus sąsajos dizainas (nominalus tipizavimas)
Pirmasis ir labiausiai tradicinis būdas naudoti ABC yra formaliam sąsajos dizainui. Šis požiūris yra pagrįstas nominaliu tipizavimu – koncepcija, pažįstama programuotojams, dirbantiems su tokiomis kalbomis kaip Java, C++ ar C#. Nominalioje sistemoje tipo suderinamumas nustatomas pagal jo pavadinimą ir aiškią deklaraciją. Mūsų kontekste klasė laikoma `MediaFile` tik tuo atveju, jei ji aiškiai paveldi iš `MediaFile` ABC.
Pagalvokite apie tai kaip apie profesinį sertifikatą. Norėdami būti sertifikuotu projektų vadovu, negalite tiesiog elgtis kaip toks; turite mokytis, išlaikyti konkretų egzaminą ir gauti oficialų sertifikatą, kuriame aiškiai nurodyta jūsų kvalifikacija. Jūsų sertifikato pavadinimas ir kilmė yra svarbūs.
Šiame modelyje ABC veikia kaip nediskutuotinas kontraktas. Paveldėdama iš jos, klasė duoda formalų pažadą likusiai sistemos daliai, kad ji suteiks reikiamą funkcionalumą.
Pavyzdys: duomenų eksportuotojo karkasas
Įsivaizduokime, kad kuriame karkasą (angl. framework), leidžiantį vartotojams eksportuoti duomenis įvairiais formatais. Norime užtikrinti, kad kiekvienas eksportuotojo įskiepis (angl. plugin) laikytųsi griežtos struktūros. Galime apibrėžti `DataExporter` sąsają.
import abc
from datetime import datetime
class DataExporter(abc.ABC):
"""Formali sąsaja duomenų eksportavimo klasėms."""
@abc.abstractmethod
def export(self, data: list[dict]) -> str:
"""Eksportuoja duomenis ir grąžina būsenos pranešimą."""
pass
def get_timestamp(self) -> str:
"""Konkretus pagalbinis metodas, bendras visiems poklasiams."""
return datetime.utcnow().isoformat()
class CSVExporter(DataExporter):
def export(self, data: list[dict]) -> str:
filename = f"export_{self.get_timestamp()}.csv"
print(f"Exporting {len(data)} rows to {filename}")
# ... faktinė CSV rašymo logika ...
return f"Successfully exported to {filename}"
class JSONExporter(DataExporter):
def export(self, data: list[dict]) -> str:
filename = f"export_{self.get_timestamp()}.json"
print(f"Exporting {len(data)} records to {filename}")
# ... faktinė JSON rašymo logika ...
return f"Successfully exported to {filename}"
Čia `CSVExporter` ir `JSONExporter` yra aiškiai ir patikrinamai `DataExporter` tipo. Mūsų programos pagrindinė logika gali saugiai pasikliauti šiuo kontraktu:
def run_export_process(exporter: DataExporter, data_to_export: list[dict]):
print("--- Starting export process ---")
if not isinstance(exporter, DataExporter):
raise TypeError("Exporter must be a valid DataExporter implementation.")
status = exporter.export(data_to_export)
print(f"Process finished with status: {status}")
# Naudojimas
data = [{"id": 1, "name": "Alice"}, {"id": 2, "name": "Bob"}]
run_export_process(CSVExporter(), data)
run_export_process(JSONExporter(), data)
Atkreipkite dėmesį, kad ABC taip pat suteikia konkretų metodą, `get_timestamp()`, kuris siūlo bendrą funkcionalumą visiems savo vaikams. Tai yra įprastas ir galingas šablonas sąsajomis pagrįstame dizaine.
Formalaus sąsajos požiūrio privalumai ir trūkumai
Privalumai:
- Nedviprasmiška ir aišku: Kontraktas yra visiškai aiškus. Programuotojas gali pamatyti paveldėjimo eilutę `class CSVExporter(DataExporter):` ir iš karto suprasti klasės vaidmenį bei galimybes.
- Draugiška įrankiams: IDE, linteriai ir statinės analizės įrankiai gali lengvai patikrinti kontraktą, suteikdami puikų automatinį užbaigimą ir klaidų tikrinimą.
- Bendras funkcionalumas: ABC gali suteikti konkretų metodų, veikiančių kaip tikra bazinė klasė ir mažinančių kodo dubliavimą.
- Pažįstamumas: Šis šablonas yra akimirksniu atpažįstamas programuotojams, atėjusiems iš didžiosios daugumos kitų objektinių programavimo kalbų.
Trūkumai:
- Glaudus susiejimas: Konkreti klasė dabar yra tiesiogiai susieta su ABC. Jei ABC reikia perkelti ar pakeisti, tai paveikia visus poklasius.
- Standumas: Tai verčia laikytis griežtos hierarchinės priklausomybės. O kas, jei klasė logiškai galėtų veikti kaip eksportuotojas, bet jau paveldi iš kitos, esminės bazinės klasės? Python daugybinis paveldėjimas gali tai išspręsti, bet jis taip pat gali įnešti savo sudėtingumų (pvz., deimanto problema).
- Invazyvumas: Negalima naudoti trečiųjų šalių kodo pritaikymui. Jei naudojate biblioteką, kuri suteikia klasę su `export()` metodu, negalite jos paversti `DataExporter` tipo, nepaveldėdami iš jos (o tai gali būti neįmanoma arba nepageidautina).
Antroji filosofija: ABC kaip protokolo įgyvendinimas (struktūrinis tipizavimas)
Antroji, labiau „pytoniška“ filosofija, dera su „duck typing“. Šis požiūris naudoja struktūrinį tipizavimą, kur suderinamumas nustatomas ne pagal pavadinimą ar kilmę, o pagal struktūrą ir elgseną. Jei objektas turi reikiamus metodus ir atributus darbui atlikti, jis laikomas tinkamo tipo tam darbui, nepriklausomai nuo jo deklaruotos klasių hierarchijos.
Pagalvokite apie gebėjimą plaukti. Norint būti laikomam plaukiku, jums nereikia sertifikato ar priklausyti „Plaukikų“ šeimos medžiui. Jei sugebate judėti vandenyje nenuskęsdami, jūs struktūriškai esate plaukikas. Žmogus, šuo ir antis gali būti plaukikai.
ABC gali būti naudojamos šiai koncepcijai formalizuoti. Užuot privertę paveldėti, galime apibrėžti ABC, kuri atpažįsta kitas klases kaip savo virtualius poklasius, jei jos įgyvendina reikiamą protokolą. Tai pasiekiama per specialų magišką metodą: `__subclasshook__`.
Kai iškviečiate `isinstance(obj, MyABC)` arba `issubclass(SomeClass, MyABC)`, Python pirmiausia patikrina aiškų paveldėjimą. Jei tai nepavyksta, jis patikrina, ar `MyABC` turi `__subclasshook__` metodą. Jei taip, Python jį iškviečia, klausdamas: „Ei, ar laikote šią klasę savo poklasiu?“ Tai leidžia ABC apibrėžti savo narystės kriterijus remiantis struktūra.
Pavyzdys: `Serializable` protokolas
Apibrėžkime protokolą objektams, kuriuos galima serializuoti į žodyną. Nenorime priversti kiekvieno serializuojamo objekto mūsų sistemoje paveldėti iš bendros bazinės klasės. Tai gali būti duomenų bazės modeliai, duomenų perdavimo objektai ar paprasti konteineriai.
import abc
class Serializable(abc.ABC):
@abc.abstractmethod
def to_dict(self) -> dict:
pass
@classmethod
def __subclasshook__(cls, C):
if cls is Serializable:
# Patikrinti, ar 'to_dict' yra C metodų paieškos eilėje (MRO)
if any("to_dict" in B.__dict__ for B in C.__mro__):
return True
return NotImplemented
Dabar sukurkime keletą klasių. Svarbiausia, kad nė viena iš jų nepaveldės iš `Serializable`.
class User:
def __init__(self, name: str, email: str):
self.name = name
self.email = email
def to_dict(self) -> dict:
return {"name": self.name, "email": self.email}
class Product:
def __init__(self, sku: str, price: float):
self.sku = sku
self.price = price
# Ši klasė neatitinka protokolo
class Configuration:
def __init__(self, setting: str):
self.setting = setting
Patikrinkime jas pagal mūsų protokolą:
print(f"Ar Vartotojas serializuojamas? {isinstance(User('Test', 't@t.com'), Serializable)}")
print(f"Ar Produktas serializuojamas? {isinstance(Product('T-1000', 99.99), Serializable)}")
print(f"Ar Konfigūracija serializuojama? {isinstance(Configuration('ON'), Serializable)}")
# Išvestis:
# Ar Vartotojas serializuojamas? True
# Ar Produktas serializuojamas? False <- Palaukite, kodėl? Ištaisykime tai.
# Ar Konfigūracija serializuojama? False
Ak, įdomi klaida! Mūsų `Product` klasė neturi `to_dict` metodo. Pridėkime jį.
class Product:
def __init__(self, sku: str, price: float):
self.sku = sku
self.price = price
def to_dict(self) -> dict: # Pridedame metodą
return {"sku": self.sku, "price": self.price}
print(f"Ar Produktas dabar serializuojamas? {isinstance(Product('T-1000', 99.99), Serializable)}")
# Išvestis:
# Ar Produktas dabar serializuojamas? True
Nors `User` ir `Product` neturi bendros tėvinės klasės (išskyrus `object`), mūsų sistema gali traktuoti jas abi kaip `Serializable`, nes jos atitinka protokolą. Tai neįtikėtinai galinga priemonė atsaistymui (decoupling).
Protokolo požiūrio privalumai ir trūkumai
Privalumai:
- Maksimalus lankstumas: Skatina itin laisvą susiejimą. Komponentams rūpi tik elgsena, o ne įgyvendinimo kilmė.
- Pritaikomumas: Puikiai tinka esamo kodo, ypač iš trečiųjų šalių bibliotekų, pritaikymui prie jūsų sistemos sąsajų, nekeičiant originalaus kodo.
- Skatina kompoziciją: Skatina dizaino stilių, kai objektai kuriami iš nepriklausomų gebėjimų, o ne per gilias, standžias paveldėjimo medžių struktūras.
Trūkumai:
- Numanomas kontraktas: Ryšys tarp klasės ir jos įgyvendinamo protokolo nėra iš karto akivaizdus iš klasės apibrėžimo. Programuotojui gali tekti ieškoti kode, kad suprastų, kodėl `User` objektas traktuojamas kaip `Serializable`.
- Vykdymo laiko našta: `isinstance` patikrinimas gali būti lėtesnis, nes jam reikia iškviesti `__subclasshook__` ir atlikti klasės metodų patikrinimus.
- Potencialus sudėtingumas: Logika `__subclasshook__` viduje gali tapti gana sudėtinga, jei protokolas apima kelis metodus, argumentus ar grąžinimo tipus.
Moderni sintezė: `typing.Protocol` ir statinė analizė
Didėjant Python naudojimui didelės apimties sistemose, augo ir noras turėti geresnę statinę analizę. `__subclasshook__` požiūris yra galingas, bet tai yra grynai vykdymo laiko mechanizmas. O kas, jei galėtume gauti struktūrinio tipizavimo privalumus *prieš* paleidžiant kodą?
Tai lėmė `typing.Protocol` įvedimą PEP 544. Jis suteikia standartizuotą ir elegantišką būdą apibrėžti protokolus, kurie pirmiausia skirti statiniams tipų tikrintojams, tokiems kaip Mypy, Pyright ar PyCharm inspektorius.
`Protocol` klasė veikia panašiai kaip mūsų `__subclasshook__` pavyzdys, bet be šabloninio kodo. Jūs tiesiog apibrėžiate metodus ir jų parašus. Bet kuri klasė, turinti atitinkamus metodus ir parašus, bus laikoma struktūriškai suderinama statinio tipo tikrintojo.
Pavyzdys: `Quacker` protokolas
Grįžkime prie klasikinio „duck typing“ pavyzdžio, bet su moderniais įrankiais.
from typing import Protocol
class Quacker(Protocol):
def quack(self, volume: int) -> str:
"""Produces a quacking sound."""
... # Pastaba: protokolo metodo turinys nėra reikalingas
class Duck:
def quack(self, volume: int) -> str:
return f"QUACK! (at volume {volume})"
class Dog:
def bark(self, volume: int) -> str:
return f"WOOF! (at volume {volume})"
def make_sound(animal: Quacker):
print(animal.quack(10))
make_sound(Duck()) # Statinė analizė praeina sėkmingai
make_sound(Dog()) # Statinė analizė nepavyksta!
Jei paleisite šį kodą per tipo tikrintoją, pvz., Mypy, jis pažymės `make_sound(Dog())` eilutę su klaida: `Argumentas 1 funkcijai "make_sound" yra nesuderinamo tipo "Dog"; tikėtasi "Quacker"`. Tipo tikrintojas supranta, kad `Dog` neatitinka `Quacker` protokolo, nes jam trūksta `quack` metodo. Tai sugauna klaidą dar prieš kodo vykdymą.
Vykdymo laiko protokolai su `@runtime_checkable`
Pagal numatytuosius nustatymus, `typing.Protocol` skirtas tik statinei analizei. Jei bandysite jį naudoti vykdymo laiko `isinstance` patikrinime, gausite klaidą.
# isinstance(Duck(), Quacker) # -> TypeError: Protokolo 'Quacker' negalima sukurti egzemplioriaus
Tačiau galite sujungti statinės analizės ir vykdymo laiko elgseną su `@runtime_checkable` dekoratoriumi. Tai iš esmės nurodo Python automatiškai sugeneruoti `__subclasshook__` logiką už jus.
from typing import Protocol, runtime_checkable
@runtime_checkable
class Quacker(Protocol):
def quack(self, volume: int) -> str: ...
class Duck:
def quack(self, volume: int) -> str: return "..."
print(f"Is Duck an instance of Quacker? {isinstance(Duck(), Quacker)}")
# Išvestis:
# Is Duck an instance of Quacker? True
Tai suteikia jums geriausią iš abiejų pasaulių: švarius, deklaratyvius protokolo apibrėžimus statinei analizei ir galimybę vykdymo laiko patvirtinimui, kai to reikia. Tačiau atminkite, kad vykdymo laiko patikrinimai su protokolais yra lėtesni nei standartiniai `isinstance` iškvietimai, todėl juos reikėtų naudoti apgalvotai.
Praktinis sprendimų priėmimas: pasaulinio programuotojo vadovas
Taigi, kurį požiūrį turėtumėte pasirinkti? Atsakymas visiškai priklauso nuo jūsų konkretaus naudojimo atvejo. Štai praktinis vadovas, pagrįstas įprastais scenarijais tarptautiniuose programinės įrangos projektuose.
1 scenarijus: įskiepių architektūros kūrimas pasauliniam SaaS produktui
Jūs projektuojate sistemą (pvz., el. prekybos platformą, TVS), kurią plės tiek jūsų pačių, tiek trečiųjų šalių programuotojai visame pasaulyje. Šie įskiepiai turi glaudžiai integruotis su jūsų pagrindine programa.
- Rekomendacija: Formali sąsaja (nominali `abc.ABC`).
- Argumentai: Aiškumas, stabilumas ir konkretumas yra svarbiausi. Jums reikia nediskutuotino kontrakto, kurį įskiepių kūrėjai turi sąmoningai priimti, paveldėdami iš jūsų `BasePlugin` ABC. Tai padaro jūsų API nedviprasmišką. Taip pat galite suteikti esminių pagalbinių metodų (pvz., registravimui, konfigūracijos prieigai, internacionalizacijai) bazinėje klasėje, o tai yra didžiulis privalumas jūsų programuotojų ekosistemai.
2 scenarijus: finansinių duomenų apdorojimas iš kelių, nesusijusių API
Jūsų fintech programa turi apdoroti transakcijų duomenis iš įvairių pasaulinių mokėjimo sistemų: Stripe, PayPal, Adyen ir galbūt regioninio teikėjo, pvz., Mercado Pago Lotynų Amerikoje. Objektai, kuriuos grąžina jų SDK, yra visiškai nepriklausomi nuo jūsų kontrolės.
- Rekomendacija: Protokolas (`typing.Protocol`).
- Argumentai: Jūs negalite keisti šių trečiųjų šalių SDK kodo, kad jie paveldėtų iš jūsų `Transaction` bazinės klasės. Tačiau žinote, kad kiekvienas jų transakcijos objektas turi metodus, tokius kaip `get_id()`, `get_amount()` ir `get_currency()`, net jei jie pavadinti šiek tiek skirtingai. Galite naudoti Adapterio šabloną kartu su `TransactionProtocol`, kad sukurtumėte vieningą vaizdą. Protokolas leidžia apibrėžti reikiamų duomenų *formą*, leidžiančią rašyti apdorojimo logiką, kuri veikia su bet kokiu duomenų šaltiniu, jei tik jį galima pritaikyti prie protokolo.
3 scenarijus: didelės, monolitinės senos programos refaktorinimas
Jums pavesta suskaidyti seną monolitą į modernias mikropaslaugas. Esamas kodas yra painus priklausomybių tinklas, ir jums reikia įvesti aiškias ribas, neperrašant visko iš karto.
- Rekomendacija: Mišrus požiūris, bet labiau linkstant prie protokolų.
- Argumentai: Protokolai yra išskirtinis įrankis laipsniškam refaktorinimui. Galite pradėti apibrėždami idealias sąsajas tarp naujų paslaugų naudodami `typing.Protocol`. Tada galite parašyti adapterius monolito dalims, kad jos atitiktų šiuos protokolus, iš karto nekeisdami pagrindinio seno kodo. Tai leidžia palaipsniui atsaistyti komponentus. Kai komponentas yra visiškai atsaistytas ir bendrauja tik per protokolą, jis yra paruoštas perkelti į savo atskirą paslaugą. Formalios ABC vėliau gali būti naudojamos apibrėžti pagrindinius modelius naujose, švariose paslaugose.
Išvados: abstrakcijos įpynimas į jūsų kodą
Python abstrakčiosios bazinės klasės yra kalbos pragmatiško dizaino įrodymas. Jos suteikia sudėtingą abstrakcijos įrankių rinkinį, kuris gerbia tiek struktūrizuotą tradicinio objektinio programavimo discipliną, tiek dinamišką „duck typing“ lankstumą.
Kelionė nuo numanomo susitarimo iki formalaus kontrakto yra brandėjančio kodo ženklas. Suprasdami dvi ABC filosofijas, galite priimti pagrįstus architektūrinius sprendimus, kurie veda prie švaresnių, lengviau prižiūrimų ir gerai mastelį keičiančių programų.
Apibendrinant pagrindines išvadas:
- Formalus sąsajos dizainas (nominalus tipizavimas): Naudokite `abc.ABC` su tiesioginiu paveldėjimu, kai jums reikia aiškaus, nedviprasmiško ir atrandamo kontrakto. Tai idealiai tinka karkasams, įskiepių sistemoms ir situacijoms, kai jūs kontroliuojate klasių hierarchiją. Tai apie tai, kuo klasė yra pagal deklaraciją.
- Protokolo įgyvendinimas (struktūrinis tipizavimas): Naudokite `typing.Protocol`, kai jums reikia lankstumo, atsaistymo ir gebėjimo pritaikyti esamą kodą. Tai puikiai tinka dirbant su išorinėmis bibliotekomis, refaktorinant senas sistemas ir projektuojant elgsenos polimorfizmą. Tai apie tai, ką klasė gali daryti pagal savo struktūrą.
Pasirinkimas tarp sąsajos ir protokolo nėra tik techninė detalė; tai fundamentalus dizaino sprendimas, kuris formuos, kaip jūsų programinė įranga vystysis. Įvaldę abu, jūs pasiruošiate rašyti Python kodą, kuris yra ne tik galingas ir efektyvus, bet ir elegantiškas bei atsparus pokyčiams.