Open Closed Principle

Classes should be open for extension but closed for modification

‘O’ pada SOLID adalah Open Closed Principle (OCP). Ide utama dari prinsip ini adalah menjaga existing code kita untuk tidak diubah tetapi memungkinkan untuk membuat feature baru dari code existing tersebut. Dengan kata lain code yang sudah kita ubah jangan sampai dapat dimodifikasi (CLOSED) tetapi kita tetap bisa meng-extend code (OPEN) tersebut untuk membuat feature baru. Alasan kenapa tidak bisa diubah sangat bervariasi, bisa saja existing code sudah dicompile menjadi sebuah library, sudah ditest atau mungkin juga sudah menjadi standard. 

Untuk context sebuah Class maka Class yang sudah kita buat jangan sampai dapat diubah tetapi Class tersebut dapat dibuat Sub-Class, dengan Sub-Class kita dapat menambahkan method atau field baru sesuai yang kita inginkan. Pada dasarnya  Class yang sudah dibuat dan digunakan oleh class lain terlalu beresiko jika kita mengubahnya apalagi sudah digunakan pada tahap live atau production.

Seperti yang sudah saya sebutkan di atas bahwa Class yang sudah didevelop, ditest, review dan digunakan oleh class atau library lain terlalu beresiko untuk diubah. Kalaupun kita perlu mengubah Class tersebut kita jangan langsung merubah pada Class tersebut lebih kita membuat Sub-Class. OCP ini sangat berkaitan dengan Single Responsibility Principle, jika kita buruk dalam mendesain atau membuat Class agar SRP, maka kemungkinan kita melanggar aturan OCP sangat besar. OCP ini bukan berarti memaksa kita harus memelihara code yang memiliki bug, tetap saja code atau class yang bermasalah harus tetap kita perbaiki. Kita tidak perlu menjaga code yang memiliki bug yang harus kita lakukan tetap memperbaiki Class tersebut. Jadi jangan paksa Sub-Class menanggung bug yang ada pada Class 🙂

Contoh menggunakan Python

Kita memiliki sebuah Class namanya InsuranceCalculator. Class ini bertugas untuk menghitung berapa biaya asuransi yang harus dibayarkan oleh orang yang akan mengirim paket. Attribute yang diperlukan adalah jenis kurir dan nilai dari barang tersebut.

class InsuranceCalculator:

    def __init__(self, shipping_type):
        self._shipping_type = shipping_type

    def get_insurance_value(self, goods_value):
        if self._shipping_type == 'jne':
            return 0.002 * goods_value + 5000
        elif self._shipping_type == 'sicepat':
            return 0.003 * goods_value
        else:
            raise Exception('Kurir tidak ditemukan')


#code client
a = InsuranceCalculator('jne')
a.get_insurance_value(50000)

Pada contoh diatas semua terlihat baik – baik saja tetapi sebenarnya code tersebut melanggar OCP. Pada method get_insurance_value, jika kita hendak menambah kurir baru maka kita perlu mengubah code yang ada pada method tersebut, padahal aturan OCP kita dilarang untuk memodifikasi code tersebut. Lalu bagaimana seharusnya?

Pertama kita buah sebuah ShippingInsurance dimana kita memiliki sebuah hitungan asuransi yang umum untuk semua kurir.

Pertama kita buat sebuah class dengan nama ShippingInsurance, dimana memiliki method calculate dengan parameter goods value.


class ShippingInsurance:

    def calculate(self, goods_value):
        return 0.002 * goods_value + 5000

Kemudian kita buat class concrete dimana mengextend class IShippingInsurance, contohnya class SicepatShippingInsurance. Kita override method calculate dengan hitungan pada kurir tersebut.

class SicepatShippingInsurance(IShippingInsurance):
    def calculate(self, goods_value):
        return 0.0025 * goods_value

Buat Class InsuranaceCalculate dimana memiliki method get_insurance_value dengan parameter goods_value. Sedangkan pada bagian constructor nya menerima sebuah parameter dengan class ShippingInsurance.

class InsuranceCalculator:

    def __init__(self, shipping_insurance: ShippingInsurance):
        self.shipping_insurance = shipping_insurance

    def get_insurance_value(self, goods_value):
        self.shipping_insurance.calculate(goods_value)

Method get_insurance_value pada Class InsuranceCalculator memanggil fungsi calculate yang ada pada shipping_insurance.

Pada code Client kita buat objek InsuranceCalculator dengan parameter objek dari SicepatShippingInsurance

#code client
shipping_insurance = ShippingInsurance()
a = InsuranceCalculator(shipping_insurance)
a.get_insurance_value(50000)

Sehingga jika kita gabungkan akan seperti code di bawah ini.


class ShippingInsurance:
    def calculate(self, goods_value):
        raise NotImplemented


class InsuranceCalculator:

    def __init__(self, shipping_insurance: IShippingInsurance):
        self.shipping_insurance = shipping_insurance

    def get_insurance_value(self, goods_value):
        self.shipping_insurance.calculate(goods_value)

#code client
shipping_insurance = ShippingInsurance()

a = InsuranceCalculator(shipping_insurance)
a.get_insurance_value(50000)

Lalu bagaimana kalau kita hendak menambah kurir baru. Maka yang perlu kita lakukan adalah membuat class baru dengan mengextend class IShippingInsurance, contohnya kita buat Class JneShippingInsurance. Class ini memiliki perhitungan yang berbeda dari SicepatShippingInsurance.

class JneShippingInsurance(IShippingInsurance):
    def calculate(self, goods_value):
        return (0.0025 * goods_value) + 5000

Setelah membuat class baru kita cukup mengimplementasikan pada code client dan kita tidak perlu mengubah pada code InsuranceCalculator

shipping_type = 'sicepat'

if shipping_type == 'jne':
    shipping_insurance = JneShippingInsurance()
elif shipping_type == 'sicepat':
    shipping_insurance = SicepatShippingInsurance()

a = InsuranceCalculator(shipping_insurance)
a.get_insurance_value(50000)

Code client hanyalah code yang memanggil class – class diatas dan tidak melanggar OCP.

Dengan demikian ketika kita ingin menambahkan kurir baru untuk menghitung asuransi kita tidak perlu mengubah code yang ada di method get_insurance_value.

Kemudian case berikutnya bagaimana jika kita ingin mengubah code yang ada di get_insurance_value pada class InsuranceCalculator di satu sisi Class InsuranceCalculator telah digunakan oleh class atau Code lain sehingga kita tidak mungkin atau terlalu beresiko untuk mengubah Class InsuranceCalculator.

Katakanlah kita memerlukan validasi tambahan kalau nilai goods_value maksimal 20jt. Maka berdasarkan prinsip OCP bahwa kita dilarang keras untuk memodifikasi Class tersebut. Oleh karena kita perlu membuat sebuah Sub-Class. Kita buat Class baru yang merupakan extend dari Class InsuranceCalculator dengan mana AdvancedCalcultor

class AdvancedInsuranceCalculator(InsuranceCalculator):

    def get_insurance_value(self, goods_value):
        if goods_value > 20000000:
            raise Exception('Maksimal nilai barang 20jt')
        super(AdvancedInsuranceCalculator, self).get_insurance_value()

Maka pada code client kita tidak mengimplementasikannya

Setelah membuat class baru kita cukup mengimplementasikan pada code client dan kita tidak perlu mengubah pada code InsuranceCalculator

shipping_type = 'sicepat'

if shipping_type == 'jne':
    shipping_insurance = JneShippingInsurance()
elif shipping_type == 'sicepat':
    shipping_insurance = SicepatShippingInsurance()

a = AdvancedInsuranceCalculator(shipping_insurance)
a.get_insurance_value(50000)

Class InsuranceCalculator memegang prinsip OCP. Di satu sisi kita menutup semua kemungkinan perubahan code (Closed) tetapi di satu sisi lain code tersebut terbuka untuk diextend (Open).