Kursus Python / Memilih pewarisan dan komposisi untuk kebijakan biaya

Inheritance atau pewarisan memperluas hubungan dan perilaku class, sedangkan composition atau komposisi memakai objek lain sebagai komponen. Pilih berdasarkan apakah subtype dapat menggantikan tipe dasar dan apakah komponen perlu dapat ditukar, bukan hanya karena dua bagian kode tampak mirip.

Nilai hubungan dan arah perubahan

Jika objek turunan dapat memenuhi janji yang diharapkan dari objek dasar, pewarisan dapat sesuai. Ledger memakai kebijakan biaya, tetapi ledger bukan salah satu jenis kebijakan biaya. Hubungan pemakaian itu lebih alami dinyatakan dengan menyimpan objek kebijakan sebagai komponen.

Class turunan dapat mengganti implementasi metode. Namun pemanggil masih membutuhkan bentuk input, makna hasil, dan perilaku kegagalan yang dapat diandalkan. Memperketat syarat input secara mengejutkan atau mengembalikan hasil dengan arti berbeda dapat merusak kemampuan mengganti tipe dasar.

super() meneruskan pencarian implementasi menurut method resolution order (MRO), bukan selalu memanggil class induk yang namanya ditulis secara tetap. Pada struktur sederhana berikut, implementasi selanjutnya memang berasal dari FeePolicy. Rujukan dasarnya tersedia pada pewarisan class Python. Konsep instance dan tanggung jawab objek mengikuti dasar class dan metode.

Jalankan kebijakan biaya yang dapat diganti

Simpan kode lengkap sebagai composition.py dengan encoding UTF-8. Gunakan Python 3.12 atau lebih baru melalui lingkungan proyek, lalu jalankan python composition.py. Tidak diperlukan paket atau data tambahan.

class FeePolicy:
    def apply(self, amount):
        return amount

class FixedFee(FeePolicy):
    def __init__(self, fee):
        self.fee = fee

    def apply(self, amount):
        return super().apply(amount) + self.fee

class Ledger:
    def __init__(self, policy):
        self.policy = policy

    def charge(self, amount):
        return self.policy.apply(amount)

print(Ledger(FeePolicy()).charge(8000))
print(Ledger(FixedFee(500)).charge(8000))

Keluaran yang diharapkan:

8000
8500

FixedFee mewarisi FeePolicy dan mengganti metode apply. Ia meminta hasil implementasi dasar melalui super().apply(amount), kemudian menambahkan biaya tetap. Dengan jumlah 8000 dan biaya 500, hasilnya 8500.

Ledger memakai komposisi: ia menerima dan menyimpan objek policy, lalu mendelegasikan perhitungan ke policy.apply. Kode ledger tidak perlu diubah ketika kebijakan berganti dari kebijakan dasar ke biaya tetap. Artinya, contoh ini menggunakan pewarisan dan komposisi sekaligus pada hubungan yang berbeda.

Python tidak mewajibkan semua kebijakan mewarisi FeePolicy agar panggilan ini bekerja. Objek lain dengan metode apply(amount) yang sesuai dapat dipakai. Namun nama metode saja tidak membuktikan kompatibilitas: jumlah parameter, arti hasil, dan aturan kegagalannya juga harus sesuai.

Bandingkan dua rancangan untuk kebutuhan yang sama

Simpan program lengkap kedua sebagai policies.py dan jalankan python policies.py. Program berdiri sendiri dan membandingkan dua cara menambahkan biaya pada jumlah yang sama.

class InheritedLedger:
    def fee(self):
        return 0
    def charge(self, amount):
        return amount + self.fee()
class FixedLedger(InheritedLedger):
    def fee(self):
        return 500
class ComposedLedger:
    def __init__(self, fee_policy):
        self.fee_policy = fee_policy
    def charge(self, amount):
        return amount + self.fee_policy()
def fixed_fee():
    return 500
print(FixedLedger().charge(8000))
ledger = ComposedLedger(fixed_fee)
print(ledger.charge(8000))
ledger.fee_policy = lambda: 0
print(ledger.charge(8000))

Keluaran yang diharapkan:

8500
8500
8000

Pada rancangan pewarisan, FixedLedger mengganti fee(), sedangkan logika charge tetap berasal dari class dasar. Pada rancangan komposisi, ComposedLedger menerima fungsi yang dapat dipanggil tanpa argumen dan mengembalikan biaya. Kedua rancangan mula-mula menghasilkan 8500.

Hanya atribut fee_policy pada objek komposisi yang kemudian diganti dengan fungsi singkat yang mengembalikan nol. Perhitungan berikutnya menghasilkan 8000 tanpa membuat subtype ledger baru. Bentuk lambda: 0 di sini hanya kebijakan kecil yang mengembalikan konstanta.

Perhatikan dua antarmuka contoh: policy.apply(amount) menerima jumlah dan mengembalikan total akhir, sedangkan fee_policy() mengembalikan biaya yang nanti ditambahkan ledger. Keduanya tidak dapat dipertukarkan sembarang hanya karena sama-sama disebut kebijakan. Dokumentasikan kontrak yang dipakai.

Uji kontrak sebelum menambah hierarki

  1. Jalankan kedua sumber asli dan cocokkan masing-masing hasil. Pertahankan jumlah 8000 agar perbandingan memakai kondisi sama.
  2. Pada program pertama, ganti FixedFee(500) menjadi FixedFee(0). Kedua baris kini menghasilkan 8000.
  3. Pada program kedua, ganti nilai biaya 500 menjadi nol pada kedua implementasi biaya. Pastikan hasil dua rancangan tetap sesuai untuk kondisi yang sama.
  4. Pulihkan nilai 500 dan cocokkan kembali keluaran asli sebelum memperluas rancangan.

Jika komponen tidak menyediakan apply, program pertama dapat gagal dengan AttributeError. Jika metodenya ada tetapi tidak menerima jumlah, pemanggilan dapat gagal dengan TypeError. Pengujian komponen palsu yang kecil membantu membuktikan ketergantungan yang benar-benar dibutuhkan.

Contoh belum memvalidasi tipe atau menolak jumlah dan biaya negatif. Menjaga aturan angka merupakan kontrak lain yang perlu ditetapkan pada lapisan yang sesuai. Perhitungan yang dapat dipanggil tidak otomatis berarti inputnya sah secara bisnis.

  • Apakah class turunan tetap memenuhi janji input dan hasil class dasar?
  • Apakah hanya satu metode yang ingin dipakai ulang, sementara perilaku induk lainnya tidak berkaitan?
  • Apakah menerima fungsi atau objek kebijakan sebagai argumen membuat penggantian dan pengujian lebih sederhana?

Pisahkan input/output dari kebijakan agar efek pilihan rancangan mudah dibandingkan. Untuk kebutuhan kecil, satu fungsi dapat memadai. Hierarki yang lebih dalam layak digunakan hanya ketika hubungan subtype dan kebutuhan perluasannya jelas, bukan sebagai tujuan tersendiri.