Cara mengaplikasikan prinsip SOLID dalam Python langkah demi langkah

  • Prinsip SOLID menyediakan asas yang jelas untuk mereka bentuk kod Python berorientasikan objek yang lebih mudah dibaca, diselenggara dan boleh diskala.
  • Setiap prinsip (SRP, OCP, LSP, ISP dan DIP) menangani jenis masalah reka bentuk tertentu, daripada tanggungjawab yang diasingkan dengan teruk kepada kebergantungan yang tegar.
  • Menggunakan SOLID dengan kelas, abstraksi dan suntikan kebergantungan dalam Python mengurangkan gandingan, meningkatkan kebolehujian dan memudahkan evolusi sistem.

Pepejal dalam Python

Apabila anda mula mengerjakan projek Python yang besar, salah satu perkara pertama yang anda perhatikan ialah Kod tersebut menjadi sukar untuk difahami, diuji dan dikembangkan. Jika anda tidak mengikuti beberapa peraturan reka bentuk asas. Di sinilah prinsip SOLID yang terkenal memainkan peranan: koleksi amalan terbaik yang direka untuk menjadikan kehidupan pasukan lebih mudah.

Prinsip-prinsip ini berasal dari bidang pengaturcaraan berorientasikan objek klasik (Java, C++, C#, dll.)Tetapi ia sesuai dengan Python selagi anda menggunakan kelas dan objek dengan cara yang lebih serius atau kurang serius. Mari kita lihat secara terperinci apakah ia, dari mana asalnya, mengapa ia penting, dan yang paling penting, bagaimana Gunakan SOLID dengan contoh yang jelas dalam Python untuk menjadikan kod anda lebih mudah diselenggara, boleh diskala dan menyenangkan untuk digunakan.

Apakah itu SOLID dan dari manakah datangnya semua itu?

Istilah SOLID ialah akronim yang dipopularkan oleh Michael Feathers untuk mengumpulkan lima prinsip reka bentuk yang pada asalnya dicadangkan oleh Robert C. Martin, lebih dikenali sebagai Uncle Bob. Jurutera perisian Amerika ini, salah seorang penandatangan Manifesto Agile, menerbitkan artikel "Prinsip OOD" pada pertengahan 90-an dan kemudian "Prinsip Reka Bentuk dan Corak Reka Bentuk," di mana beliau meletakkan banyak asas reka bentuk berorientasikan objek moden.

Lama-kelamaan, penulis lain seperti Barbara Liskov dan Bertrand Meyer Mereka juga menyumbangkan idea-idea yang disepadukan ke dalam set prinsip ini. Michael Feathers hanya mempunyai idea (yang sangat bijak) untuk menyusun semulanya supaya inisialnya membentuk perkataan SOLID, yang membantunya merebak seperti api liar dalam komuniti pembangunan.

Lima huruf SOLID sepadan dengan prinsip reka bentuk berorientasikan objek ini, yang juga boleh digunakan untuk Python:

  • S – Prinsip Tanggungjawab Tunggal (Prinsip Tanggungjawab Tunggal)
  • O – Prinsip Terbuka/Tertutup (Prinsip Terbuka/Tertutup)
  • Prinsip Penggantian L – Liskov (Prinsip Penggantian Liskov)
  • I – Prinsip Pengasingan Antara Muka (Prinsip Pengasingan Antara Muka)
  • D – Prinsip Penyongsangan Kebergantungan (Prinsip Pembalikan Kebergantungan)

Idea umum ialah kelima-lima prinsip ini, jika digunakan bersama, Mereka membantu anda menulis perisian yang fleksibel, mudah diuji dan boleh diselenggaraIni diterjemahkan kepada penggunaan yang lebih pantas, kurang pepijat misteri, penggunaan semula kod yang lebih baik dan kurang masalah apabila projek itu telah dihasilkan selama beberapa tahun.

Apakah prinsip SOLID yang digunakan dalam Python?

Mengaplikasikan prinsip SOLID dalam Python bukan sekadar latihan akademik; ia mempunyai kesan langsung terhadap kerja harian pasukan. Apabila anda mematuhi prinsip-prinsip ini, Ia mengurangkan kod spageti, mengurangkan bau kod dan menghalang pangkalan kod anda daripada "berbau busuk".menggunakan analogi yang terkenal, “jika baunya busuk, sesuatu itu direka bentuk dengan buruk.” Dalam Windows, ramai pembangun memilih untuk Pasang dan konfigurasikan WSL2 untuk mempunyai persekitaran Linux yang lebih dekat dengan pengeluaran.

Dalam persekitaran kolaboratif (pasukan pembangunan bahagian belakang, kejuruteraan data, produk dengan kitaran yang panjang, dll.) prinsip-prinsip ini adalah kunci kepada berbilang orang boleh bekerja pada pangkalan kod yang sama tanpa melampaui batas mereka atau melanggar segala-galanya dengan sedikit sentuhan.Tambahan pula, Python, walaupun fleksibel dan dinamik, membolehkan aplikasi abstraksi OOP yang tipikal secara lancar: kelas abstrak, hierarki pewarisan, komposisi dan antara muka melalui abc, Dll

Secara ringkasnya, SOLID membantu anda mencapai:

  • Kod yang lebih bersih dan mudah dibacawalaupun bertahun-tahun selepas menulisnya.
  • Kebolehujian yang dipertingkatkankerana tanggungjawab diasingkan dengan jelas.
  • Kebolehgunaan semula dan kebolehskalaan yang tinggi terima kasih kepada kebergantungan tegar yang lebih sedikit antara modul.
  • Kurang ralat cagaranApabila anda mengubah sesuatu dalam satu modul, anda tidak akan secara tidak sengaja melanggar lima perkara lain.

S – Prinsip Tanggungjawab Tunggal

Prinsip pertama menyatakan bahawa Sesebuah kelas sepatutnya hanya mempunyai satu sebab untuk berubah.Dalam erti kata lain, ia mesti memikul satu tanggungjawab yang jelas. Ini tidak bermakna hanya mempunyai satu kaedah, tetapi semua logiknya harus mengarah ke arah satu tujuan yang koheren.

Bayangkan kelas Python yang mewakili pengguna dan, selain menyimpan data mereka, juga mengendalikan akses pangkalan data dan menjana laporan:

class User:
    def __init__(self, name: str):
        self.name = name

    def get_user_from_database(self, user_id: int) -> dict:
        # Recupera datos desde la base de datos
        # ...
        pass

    def save_user_to_database(self) -> None:
        # Persiste el usuario en la base de datos
        # ...
        pass

    def generate_user_report(self) -> str:
        # Genera un informe del usuario
        # ...
        pass

Inilah kelasnya menggabungkan tiga tanggungjawab yang berbezaMewakili pengguna, mengurus kegigihan dan membina laporan. Perubahan pada pangkalan data, format laporan atau atribut pengguna memerlukan pengubahsuaian kelas yang sama, meningkatkan risiko memperkenalkan pepijat silang.

Jika kita asingkan kebimbangan ini, reka bentuknya bertambah baik dengan ketara:

class User:
    def __init__(self, name: str):
        self.name = name


class UserDB:
    @staticmethod
    def get_user(user_id: int) -> User:
        # Lógica para obtener usuarios de la base de datos
        # ...
        return User("John Doe")

    @staticmethod
    def save_user(user: User) -> None:
        # Lógica para guardar el usuario
        # ...
        pass


class UserReportGenerator:
    @staticmethod
    def generate_report(user: User) -> str:
        # Lógica para generar informes de usuario
        # ...
        return f"Report for user: {user.name}"

Sekarang kelas Pengguna hanya mewakili pengguna sebagai entitiJika cara laporan dijana berubah, hanya ketik UserReportGeneratorJika anda menukar pangkalan data, anda hanya perlu menyentuh UserDBSetiap kelas mempunyai satu sebab untuk perubahan, yang memudahkan penyahpepijatan dan evolusi sistem.

SRP digunakan untuk contoh yang lebih realistik: itik dan komunikasi

Mari kita lihat senario klasik yang diadaptasi: sebuah kelas Itik Pada mulanya, tanggungjawab akan ditambah secara beransur-ansur sehingga ia menjadi raksasa yang sukar untuk dikekalkan. Bayangkan pelaksanaan yang naif:

class Duck:
    def __init__(self, name: str):
        self.name = name

    def fly(self) -> None:
        print(f"{self.name} is flying not very high")

    def swim(self) -> None:
        print(f"{self.name} swims in the lake and quacks")

    def do_sound(self) -> str:
        return "Quack"

    def greet(self, other_duck: "Duck") -> None:
        print(f"{self.name}: {self.do_sound()}, hello {other_duck.name}")

Kelas Ia harus ditakrifkan hanya sebagai "itik"Tetapi ia juga menguruskan cara mereka berkomunikasi antara satu sama lain. Jika esok anda mengubah logik perbualan (lebih banyak frasa, bahasa lain, saluran berbeza), anda perlu mengubah suai kelas itik, yang sudah berfungsi dengan baik sebagai entiti.

Penyelesaian yang menghormati SRP adalah untuk mendapatkan tanggungjawab kedua daripada kelas lain yang pakar dalam komunikasi:

class Duck:
    def __init__(self, name: str):
        self.name = name

    def fly(self) -> None:
        print(f"{self.name} is flying not very high")

    def swim(self) -> None:
        print(f"{self.name} swims in the lake and quacks")

    def do_sound(self) -> str:
        return "Quack"


class Communicator:
    def __init__(self, channel: str):
        self.channel = channel

    def communicate(self, duck1: Duck, duck2: Duck) -> None:
        sentence1 = f"{duck1.name}: {duck1.do_sound()}, hello {duck2.name}"
        sentence2 = f"{duck2.name}: {duck2.do_sound()}, hello {duck1.name}"
        conversation = 
        print(*conversation, f"(via {self.channel})", sep="\n")

Berkat perpisahan ini, Anda boleh mengembangkan logik komunikasi tanpa menyentuh definisi itikTambahan pula, kod ini lebih mudah untuk diuji: anda menguji tingkah laku Duck dan sebaliknya, salah satu daripada Communicatortanpa mencampuradukkan tanggungjawab.

O – Prinsip Terbuka/Tertutup

Prinsip OCP menyatakan bahawa Entiti perisian harus terbuka untuk melanjutkan tingkah laku mereka, tetapi tertutup untuk pengubahsuaian langsung.Dalam erti kata lain, apabila anda ingin menambah fungsi baharu, idealnya anda tidak perlu menulis semula kelas yang sudah berfungsi dan digunakan oleh modul lain.

Satu contoh klasik ialah mengira luas rajah geometri. Mari kita lihat dahulu versi yang tidak menghormati OCP:

class Rectangle:
    def __init__(self, width: float, height: float):
        self.width = width
        self.height = height


class Circle:
    def __init__(self, radius: float):
        self.radius = radius


class AreaCalculator:
    def calculate_area(self, shape) -> float:
        if isinstance(shape, Rectangle):
            return shape.width * shape.height
        elif isinstance(shape, Circle):
            return 3.14159 * shape.radius * shape.radius
        else:
            raise ValueError("Forma no soportada")

Jika anda ingin menambah segi tiga esok, anda terpaksa mengubah suai kod daripada AreaCalculatormenambah satu lagi elifIni melanggar OCP, kerana kelas tersebut tidak lagi "tertutup" kepada perubahan.

Versi yang betul melibatkan pengenalan abstraksi Shape dengan kaedah area() yang dilaksanakan oleh setiap rajah dengan cara mereka sendiri:

from abc import ABC, abstractmethod


class Shape(ABC):
    @abstractmethod
    def area(self) -> float:
        pass


class Rectangle(Shape):
    def __init__(self, width: float, height: float):
        self.width = width
        self.height = height

    def area(self) -> float:
        return self.width * self.height


class Circle(Shape):
    def __init__(self, radius: float):
        self.radius = radius

    def area(self) -> float:
        return 3.14159 * self.radius * self.radius


class AreaCalculator:
    def calculate_area(self, shape: Shape) -> float:
        return shape.area()

Terima kasih kepada reka bentuk ini, untuk tambah segi tiga yang anda tidak sentuh AreaCalculatorAnda hanya perlu mencipta subkelas baharu:

class Triangle(Shape):
    def __init__(self, base: float, height: float):
        self.base = base
        self.height = height

    def area(self) -> float:
        return 0.5 * self.base * self.height

Prinsip Terbuka/Tertutup sangat sesuai dengan idea tentukan titik peluasan yang jelas melalui abstraksi: antara muka, kelas abstrak, cangkuk, dsb. Dalam Python, modul abc Ia membolehkan anda menyatakannya secara eksplisit, walaupun bahasanya dinamik.

OCP digunakan pada contoh komunikator

Jika kita kembali kepada contoh CommunicatorKita boleh melangkah lebih jauh dan menyediakan reka bentuk untuk menyokong pelbagai jenis perbualan tanpa menulis semula komunikator setiap kali. Untuk melakukan ini, kita mentakrifkan abstraksi perbualan dan hanya komunikator yang menggunakannya:

from typing import final
from abc import ABC, abstractmethod


class AbstractConversation(ABC):
    @abstractmethod
    def do_conversation(self) -> list:
        pass


class SimpleConversation(AbstractConversation):
    def __init__(self, duck1: Duck, duck2: Duck):
        self.duck1 = duck1
        self.duck2 = duck2

    def do_conversation(self) -> list:
        sentence1 = f"{self.duck1.name}: {self.duck1.do_sound()}, hello {self.duck2.name}"
        sentence2 = f"{self.duck2.name}: {self.duck2.do_sound()}, hello {self.duck1.name}"
        return 


class Communicator:
    def __init__(self, channel: str):
        self.channel = channel

    @final
    def communicate(self, conversation: AbstractConversation) -> None:
        print(*conversation.do_conversation(), f"(via {self.channel})", sep="\n")

Dalam versi ini, Jika anda ingin menambah cara bercakap baharu (contohnya, perbualan yang agresif, perbualan yang mengambil giliran, dsb.), anda hanya perlu mencipta subkelas lain bagi AbstractConversation. Cara communicate() de Communicator Ia tidak berubah, mematuhi sepenuhnya OCP.

Prinsip Penggantian L – Liskov

Prinsip Penggantian Liskov, yang dirumuskan oleh Barbara Liskov, menyatakan bahawa Subkelas sepatutnya dapat menggantikan kelas asasnya tanpa mengubah tingkah laku program yang dijangkakan.Dalam praktiknya, ini bermakna jika kod berfungsi dengan satu contoh kelas asas, ia sepatutnya berfungsi dengan baik dengan mana-mana contoh subkelas.

Satu contoh tipikal pelanggaran LSP ialah pemodelan semua burung dengan satu kaedah fly()termasuk burung unta:

class Bird:
    def fly(self) -> None:
        pass


class Duck(Bird):
    def fly(self) -> None:
        print("¡El pato está volando!")


class Ostrich(Bird):
    def fly(self) -> None:
        # Las avestruces no vuelan
        raise NotImplementedError("Las avestruces no pueden volar")

Mana-mana kod yang menganggap bahawa Setiap burung yang boleh terbang akan gagal apabila ia menerima burung unta. Maksudnya, Ostrich Ia bukan pengganti yang sah untuk Bird, dengan itu melanggar LSP.

Penyelesaiannya adalah untuk melaraskan hierarki agar lebih mencerminkan realiti: tidak semua burung terbang, jadi Hanya sebahagian daripada burung yang sepatutnya mempunyai kaedah tersebut fly():

class Bird:
    pass


class FlyingBird(Bird):
    def fly(self) -> None:
        pass


class Duck(FlyingBird):
    def fly(self) -> None:
        print("¡El pato está volando!")


class Ostrich(Bird):
    # No vuela, así que no implementa fly()
    pass

Dengan reka bentuk ini, Sebarang fungsi yang memerlukan burung terbang akan mengisytiharkan bahawa ia memerlukannya. FlyingBirddan tidak akan pernah menerima burung unta. Dengan cara ini, LSP dihormati dan pengecualian masa jalan yang tidak dijangka dapat dielakkan.

Perbualan LSP dan burung

Kembali kepada contoh perbualan, adalah perkara biasa untuk mula membuat pengekodan dengan hanya memikirkan tentang itik dan kemudian ingin menambah burung gagak atau burung lain. Jika kelas perbualan bergantung pada Duck, Anda tidak akan dapat menggunakannya semula dengan jenis burung lain tanpa menyentuh kod:

class Crow:
    # Implementación específica del cuervo
    ...

Si SimpleConversation Ia ditaip hanya untuk itik; anda tidak boleh membiarkan burung gagak melaluinya tanpa mengubah suainya. Pendekatan yang betul adalah dengan mencipta abstraksi yang sama. Bird dan jadikan perbualan bergantung pada abstraksi itu:

from abc import ABC, abstractmethod


class Bird(ABC):
    def __init__(self, name: str):
        self.name = name

    @abstractmethod
    def do_sound(self) -> str:
        pass


class Crow(Bird):
    def do_sound(self) -> str:
        return "Caw"


class Duck(Bird):
    def do_sound(self) -> str:
        return "Quack"


class SimpleConversation(AbstractConversation):
    def __init__(self, bird1: Bird, bird2: Bird):
        self.bird1 = bird1
        self.bird2 = bird2

    def do_conversation(self) -> list:
        sentence1 = f"{self.bird1.name}: {self.bird1.do_sound()}, hello {self.bird2.name}"
        sentence2 = f"{self.bird2.name}: {self.bird2.do_sound()}, hello {self.bird1.name}"
        return 

Dengan cara ini, mana-mana subkelas bagi Bird yang menghormati kontrak (do_sound()(nama, dsb.) ialah pengganti yang sah dan tidak akan melanggar tingkah laku yang dijangkakan SimpleConversation.

I – Prinsip Pengasingan Antara Muka

Prinsip ISP menyatakan bahawa Tiada pelanggan yang harus dipaksa bergantung pada kaedah yang tidak mereka gunakan.Diterjemahkan kepada kelas atau antara muka abstrak, ini bermakna adalah lebih baik untuk mempunyai beberapa antara muka kecil yang khusus daripada satu antara muka generik yang besar.

Perhatikan reka bentuk ini di mana antara muka Worker Ia memerlukan semua yang melaksanakannya mempunyai kaedah kerja dan pemakanan tertentu:

from abc import ABC, abstractmethod


class Worker(ABC):
    @abstractmethod
    def work(self) -> None:
        pass

    @abstractmethod
    def eat(self) -> None:
        pass


class Human(Worker):
    def work(self) -> None:
        print("El humano está trabajando")

    def eat(self) -> None:
        print("El humano está comiendo")


class Robot(Worker):
    def work(self) -> None:
        print("El robot está trabajando")

    def eat(self) -> None:
        # El robot no come, pero está obligado a declarar este método
        pass

Kelas Robot bergantung pada kaedah eat() yang tidak memerlukanSebarang perubahan yang berkaitan dengan makanan akan menjejaskan robot, walaupun ia tidak ada kaitan dengan tingkah laku itu.

Dengan menggunakan ISP, kami membahagikan antara muka kepada dua yang lebih kecil dan lebih spesifik:

class Workable(ABC):
    @abstractmethod
    def work(self) -> None:
        pass


class Eatable(ABC):
    @abstractmethod
    def eat(self) -> None:
        pass


class Human(Workable, Eatable):
    def work(self) -> None:
        print("El humano está trabajando")

    def eat(self) -> None:
        print("El humano está comiendo")


class Robot(Workable):
    def work(self) -> None:
        print("El robot está trabajando")

Sekarang, Setiap kelas hanya melaksanakan kaedah yang sebenarnya diperlukan.Ini mengurangkan gandingan, memudahkan evolusi reka bentuk dan menjadikan kod lebih ekspresif: ia menjadi sangat jelas siapa yang boleh melakukan apa.

ISP dalam pemodelan burung: terbang dan berenang

Sesuatu yang serupa berlaku apabila memodelkan burung yang terbang dan berenang. Jika abstraksi asas Bird Ia memerlukan pelaksanaan kedua-duanya fly() sebagai swim()Anda akan mendapat kelas seperti Crow yang perlu berpura-pura mereka tahu berenang:

class Bird(ABC):
    def __init__(self, name: str):
        self.name = name

    @abstractmethod
    def fly(self) -> None:
        pass

    @abstractmethod
    def swim(self) -> None:
        pass

    @abstractmethod
    def do_sound(self) -> str:
        pass

Penyelesaiannya menurut ISP ialah mengasingkan antara muka kepada keupayaan yang lebih khusus:

class Bird(ABC):
    def __init__(self, name: str):
        self.name = name

    @abstractmethod
    def do_sound(self) -> str:
        pass


class FlyingBird(Bird):
    @abstractmethod
    def fly(self) -> None:
        pass


class SwimmingBird(Bird):
    @abstractmethod
    def swim(self) -> None:
        pass


class Crow(FlyingBird):
    def fly(self) -> None:
        print(f"{self.name} is flying high and fast!")

    def do_sound(self) -> str:
        return "Caw"


class Duck(SwimmingBird, FlyingBird):
    def fly(self) -> None:
        print(f"{self.name} is flying not very high")

    def swim(self) -> None:
        print(f"{self.name} swims in the lake and quacks")

    def do_sound(self) -> str:
        return "Quack"

Jika anda pernah memutuskan untuk memodelkan penguin, cukuplah kamu jadikan dia mewarisi daripada SwimmingBird tetapi bukan dari FlyingBirdDan anda tidak perlu melaksanakan kaedah kosong atau membuang pengecualian buatan.

D – Prinsip Penyongsangan Kebergantungan

Prinsip terakhir, DIP, boleh diringkaskan dalam dua idea utama: Modul peringkat tinggi tidak seharusnya bergantung pada modul peringkat rendah; kedua-duanya harus bergantung pada abstraksi.Dan abstraksi tidak seharusnya bergantung pada perincian, tetapi perincian harus bergantung pada abstraksi.

Dalam praktiknya, ini bermakna logik perniagaan anda tidak seharusnya terikat dengan butiran khusus seperti "Saya menggunakan MySQL," "Saya menulis ke fail setempat," atau "Saya menghantar mesej SMS dengan pembekal ini." Sebaliknya, anda mentakrifkan antara muka abstrak (contohnya, Database, Channel, NotificationService) dan anda menjadikan kod peringkat tinggi anda hanya bercakap dengan mereka.

Reka bentuk yang rehat DIP Ini akan menjadi repositori pengguna yang secara langsung mewujudkan pangkalan data MySQL:

class MySQLDatabase:
    def connect(self) -> None:
        # Conectar a MySQL
        pass

    def query(self, sql: str) -> list:
        # Ejecutar consulta
        return []


class UserRepository:
    def __init__(self) -> None:
        self.database = MySQLDatabase()  # Dependencia directa

    def get_users(self) -> list:
        return self.database.query("SELECT * FROM users")

Jika anda memutuskan untuk menggunakan PostgreSQL esok, anda perlu ubah suai kelas peringkat tinggi UserRepositoryAnda terikat dengan butiran pelaksanaan tertentu.

Dengan menggunakan DIP, kita mula-mula mentakrifkan abstraksi pangkalan data dan kemudian mewarisi pelaksanaan konkrit daripadanya:

from abc import ABC, abstractmethod


class Database(ABC):
    @abstractmethod
    def connect(self) -> None:
        pass

    @abstractmethod
    def query(self, sql: str) -> list:
        pass


class MySQLDatabase(Database):
    def connect(self) -> None:
        # Conexión a MySQL
        pass

    def query(self, sql: str) -> list:
        # Consulta en MySQL
        return []


class PostgreSQLDatabase(Database):
    def connect(self) -> None:
        # Conexión a PostgreSQL
        pass

    def query(self, sql: str) -> list:
        # Consulta en PostgreSQL
        return []


class UserRepository:
    def __init__(self, database: Database) -> None:
        self.database = database  # Depende de una abstracción

    def get_users(self) -> list:
        return self.database.query("SELECT * FROM users")

Oleh itu, Anda boleh menyuntik sebarang pelaksanaan Database semasa mencipta repositori, tanpa menyentuh kod dalamannya:

mysql_db = MySQLDatabase()
user_repo = UserRepository(mysql_db)

postgres_db = PostgreSQLDatabase()
user_repo = UserRepository(postgres_db)

Corak ini dikenali sebagai Suntikan Ketergantungan Dan ia adalah cara paling biasa untuk menggunakan DIP: kelas tidak mencipta kebergantungan mereka sendiri, tetapi menerimanya dari luar (melalui pembina atau melalui kaedah tertentu), sentiasa menggunakan abstraksi sebagai jenis.

DIP digunakan untuk saluran dan komunikator

Dalam contoh perbualan burung, kita juga boleh menambah baik pengurusan saluran dengan menggunakan DIP. Katakan anda mentakrifkan satu abstraksi untuk saluran dan satu lagi untuk komunikator:

class AbstractChannel(ABC):
    @abstractmethod
    def get_channel_message(self) -> str:
        pass


class AbstractCommunicator(ABC):
    @abstractmethod
    def get_channel(self) -> AbstractChannel:
        pass

    @final
    def communicate(self, conversation: AbstractConversation) -> None:
        print(*conversation.do_conversation(),
              self.get_channel().get_channel_message(),
              sep="\n")

Pelaksanaan pertama yang naif boleh jadi:

class SMSChannel(AbstractChannel):
    def get_channel_message(self) -> str:
        return "(via SMS)"


class SMSCommunicator(AbstractCommunicator):
    def __init__(self) -> None:
        self._channel = SMSChannel()  # Depende de detalle concreto

    def get_channel(self) -> AbstractChannel:
        return self._channel

Walaupun ia kelihatan betul, Komunikator ini masih bersambung secara langsung dengan SMSChannelKami menambah baik reka bentuk dengan meminta komunikator menerima saluran dari luar (suntikan kebergantungan), dan dengan itu hanya bergantung pada abstraksi:

class SimpleCommunicator(AbstractCommunicator):
    def __init__(self, channel: AbstractChannel) -> None:
        self._channel = channel

    def get_channel(self) -> AbstractChannel:
        return self._channel

Dengan pendekatan ini, mana-mana saluran baharu (e-mel, pemberitahuan tolak, dll.) dilaksanakan AbstractChannel y Ia boleh digunakan tanpa mengubah kod komunikator.Sekali lagi, kelas peringkat tinggi bergantung pada abstraksi, bukan butiran.

Apa yang berlaku apabila anda mengabaikan SOLID?

Jika prinsip-prinsip ini tidak diambil kira, kod tersebut cenderung untuk mengalami masalah seperti bau kod, reput kod dan gandingan yang mustahil untuk diuraikanIaitu, kelas besar dengan seribu tanggungjawab, subkelas yang melanggar kontrak, kebergantungan kitaran, dan kaedah yang berubah setiap hari kerana mereka melakukan terlalu banyak perkara.

Akibatnya jelas dan agak menyakitkan bagi mana-mana pasukan: Lebih banyak kelemahan, lebih banyak pepijat, pemfaktoran semula yang berterusan, dan, dalam kes terburuk, kod yang akhirnya menjadi tidak dapat digunakan.Ia adalah apa yang biasa dipanggil "kod spageti": sukar untuk diikuti, penuh dengan tampalan, dan hampir mustahil untuk dilanjutkan tanpa melanggar sesuatu yang penting.

Prinsip-prinsip SOLID tidak tetap, dan tidak selalunya berbaloi untuk mengaplikasikannya secara tegar, terutamanya dalam prototaip pantas atau projek yang sangat kecil. Walaupun begitu, Ingatlah perkara ini dan gunakannya pada kebanyakan reka bentuk berorientasikan objek anda dalam Python. Ia membezakan antara projek yang berkembang dari semasa ke semasa dan projek yang gagal sebaik sahaja ia berkembang sedikit.

IDE terbaik untuk pengaturcaraan Windows 11
artikel berkaitan:
IDE terbaik untuk pengaturcaraan pada Windows 11

Tambah sebagai sumber pilihan