SOLID Principles dalam Pembuatan Software

Ditulis oleh:Waktu baca:
admin~10 menit

Hai gaess, sebagai programmer kita pasti sudah banyak menulis kode program dalam bahasa apapun yang kita kuasai. Bahasa c#, java, golang, javascript, python, ruby, php, dll. Tapi pernahkah kita berfikir apakah saya sudah ngoding dengan “baik dan benar”? Apakah kode yang saya tulis sudah mengikuti “standar”? Hmm, lalu kita bertanya lagi, memang ada standar dalam ngoding? Seperti apakah kode yang “baik dan benar” itu?

Dalam posting kali ini, saya akan membahas salah satu prinsip dalam pemrograman — terutama pemrograman yang berasis objek (OOP) — , yaitu SOLID Principle. SOLID Principles adalah salah satu prinsip “standar” yang bisa diterapkan pada kode program yang kita buat. Apabila kita menerapkan prinsip SOLID, maka kode yang kita buat akan menjadi jauh lebih baik: lebih mudah di-maintain, flexible, reusable, extendable. Dengan kata lain, kode kita menjadi lebih “pro”.

SOLID terdiri dari lima prinsip:

  1. SRP, Single Responsibility Principle
  2. OCP, Open Closed Principle
  3. LSP, Liskov Substitution Principle
  4. ISP, Interface Segregation Principle
  5. DIP, Dependency Inversion/Injention Principle

SOLID dipopulerkan oleh Uncle Bob (Robert C. Martin), meskipun beberapa item di dalamnya sudah pernah dipopulerkan sebelumnya oleh orang lain. Uncle Bob adalah penulis buku “Clean Code: A Handbook of Agile Software Craftsmanship”. Ini adalah salah satu buku yang saya rekomendasikan kepada setiap programmer untuk dibaca.

“Writing clean code is what you must do in order to call yourself a professional. There is no reasonable excuse for doing anything less than your best.”

~ Robert C. Martin


Mari kita bahas satu per satu prinsip-prinsip SOLID ini.

1. SRP — Single Responsibility Principle

Definisi: “A class should have one and only one reason to change (single responsibility)

Dalam satu class, hanya boleh diisi dengan satu tanggung jawab. Jangan memasukan kode yang beraneka ragam ke dalam satu class. Contoh: Jika class tersebut bertanggung jawab untuk me-manage tabel “users”, maka tidak boleh dibebani lagi dengan tabel “orders” misalnya, atau dengan menambahkan perhitungan diskon akhir tahun. Perhitungan diskon harus dipisahkan ke class tersendiri. Manajemen tabel “orders” pun harus dipisah ke class tersendiri.

Contoh:

Melanggar SRP

File User.php:

<?php

class User  
{

    function create($user)  
    {  
        // Kode untuk membuat user  
    }

    function createOrder($order)  
    {  
        // Kode untuk membuat order  
    }

    function cancelOrder($order)  
    {  
        // Kode untuk membatalkan order  
    }

    function countDiscount($order)  
    {  
        // Kode untuk menghitung diskon  
    }  
    // Kode lainnya  
}

Dalam contoh di atas, class “_User” berisi beberapa tanggung jawab: _user, order, discount. Padahal menurut SRP, seharusnya di dalam satu class hanya ada satu tanggung jawab. Agar dapat mengikuti SRP, berikut contoh kode yang sudah di-perbaiki:

Mengikuti SRP

File User.php:

<?php

class User  
{

    function create($user)  
    {  
        // Kode untuk membuat user  
    }

    // Kode lain yang hanya berkaitan dengan user  
}

File Order.php:

<?php

class Order  
{

    function create($order)  
    {  
        // Kode untuk membuat order    
    }

    function cancelOrder($order)  
    {  
        // Kode untuk membatalkan order  
    }

    // Kode lain yang hanya berkaitan dengan order  
}

File Discount.php:

<?php

class Discount  
{

    function count($order)  
    {  
        // Kode untuk menghitung diskon  
    }

    // Kode lain yang hanya berkaitan dengan diskon  
}

Kode yang bertanggung jawab terhadap order, di-extract ke class tersendiri: “Order”. Kode yang bertanggung jawab terhadap discount, di-extract ke class tersendiri: “Discount”.

SRP juga berlaku untuk bagian lain dalam program, contoh function. Function juga harus berisi hanya satu tanggung jawab. Bahkan ada semacam “aturan” function tidak boleh lebih dari 20 baris. Lebih dari itu, dianggap terlalu banyak tanggung jawab / terlalu kompleks. Saya sarankan ini kita jadikan guidance awal ketika kita membuat function. Jika kita memiliki function yang lebih dari itu, coba untuk dipecah lagi menjadi beberapa function.

Dengan mengikuti SRP, kita mendapat keuntungan:

• Kode jadi tidak terlalu kompleks
• Mudah dibaca dan dipahami
• Mengurangi coupling (keterikatan kode)

OCP — Open/Closed Principle

Definisi: “Entities (classes, modules, functions etc.) should be open for extension but closed for modifications

Class/function harus dapat di-extend fungsinya tanpa merubah kode di dalamnya. Hmm terdengar tricky, tapi dalam programming hal itu bisa dilakukan.

Contoh ada class untuk menghitung luas persegi panjang.

file: AreaCalculator.php

<?php

class AreaCalculator  
{

    function areaRectangle($width, $height)  
    {  
        return $width * $height;  
    }  
}

Untuk menggunakan class tersebut contoh nya sebagai berikut:

File: index.php

<?php  
spl_autoload_register();

$areaCalculator = new AreaCalculator;

$area = $areaCalculator->areaRectangle(10, 5);

print($area);

Kemudian ada requirement tambahan agar bisa menghitung luas lingkaran. Normalnya, kita akan membuat lagi function baru:

File: AreaCalculator.php

<?php

class AreaCalculator  
{

    function areaRectangle($width, $height)  
    {  
        return $width * $height;  
    }

 **function areaCircle($radius)  
    {  
        return $radius * $radius * M_PI;  
    }**}

Untuk menggunakannya, berikut kodenya:

File: index.php

<?php  
spl_autoload_register();

$areaCalculator = new AreaCalculator;

$areaRectangle = $areaCalculator->areaRectangle(10, 5);  
$areaCircle = $areaCalculator->areaCircle(4);

print($areaRectangle);  
print(PHP_EOL);  
print($areaCircle);

Jika digunakan dalam loop:

File: index-loop.php

<?php  
spl_autoload_register();

$areaCalculator = new AreaCalculator;

$shapes = [  
    ['type' => 'rectangle', 'width' => 10, 'height' => 5],  
    ['type' => 'circle', 'radius' => 6],  
    ['type' => 'rectangle', 'width' => 3, 'height' => 1],  
];

foreach ($shapes as $shape) {  
    if ($shape['type'] == 'rectangle') {  
        $areaRectangle = $areaCalculator->areaRectangle($shape['width'], $shape['height']);  
        print($areaRectangle);  
    } else {  
        $areaCircle = $areaCalculator->areaCircle($shape['radius']);  
        print($areaCircle);  
    }  
    print(PHP_EOL);  
}

Setiap ada penambahan jenis shape, harus mengedit kode di class AreaCalculator dan menambah if di file index tersebut. Ini adalah cara yang tidak efisien dan yang pastinya melanggar prinsip OCP.

Mari kita solusikan masalah tersebut dengan prinsip OCP! Kita akan extract kode untuk menghitung luas ke class tersendiri. Ini juga sesuai dengan prinsip SRP sebelumnya, yaitu satu class hanya berisi satu tanggung jawab. Selain membuat class, kita juga harus membuat satu interface yang bertujuan sebagai ”kontrak” untuk menentukan function apa saja yang harus disediakan di setiap class implementasinya.

Berikut kode nya:

File: **Shape.php**

<?php

interface Shape  
{

    function area();  
}

File: Rectangle.php

<?php

class Rectangle implements Shape  
{

    private $width;  
    private $height;

    function __construct($width, $height)  
    {  
        $this->width = $width;  
        $this->height = $height;  
    }

    function area()  
    {  
        return $this->width * $this->height;  
    }  
}

File: Circle.php

<?php

class Circle implements Shape  
{

    private $radius;

    function __construct($radius)  
    {  
        $this->radius = $radius;  
    }

    function area()  
    {  
        return $this->radius * $this->radius * M_PI;  
    }  
}

File: AreaCalculator.php

<?php

class AreaCalculator  
{

    function area(Shape $shape)  
    {  
        return $shape->area();  
    }  
}

File: index.php

<?php  
spl_autoload_register();

$areaCalculator = new AreaCalculator;

$shapes = [];  
$shapes[] = new Rectangle(10, 5);  
$shapes[] = new Circle(6);  
$shapes[] = new Rectangle(3, 1);

foreach ($shapes as $shape) {  
    $area = $areaCalculator->area($shape);  
    print($area);  
    print(PHP_EOL);  
}

TADA!!! Sekarang jika ada requirement tambahan shape baru, tinggal membuat class yang meng-implements interface “_Shape”, dan tinggal digunakan di file index.php, tidak perlu meng-edit _class “AreaCalculator”. Contoh ada requirement tambahan untuk menghitung luas segitiga:

File: Triangle.php

<?php

class Triangle implements Shape  
{

    private $base;  
    private $height;

    function __construct($base, $height)  
    {  
        $this->base = $base;  
        $this->height = $height;  
    }

    function area()  
    {  
        return ($this->base * $this->height) / 2;  
    }  
}

File: AreaCalculator.php (tidak perlu diedit 😀)

File: index.php

<?php  
spl_autoload_register();

$areaCalculator = new AreaCalculator;

$shapes = [];  
$shapes[] = new Rectangle(10, 5);  
$shapes[] = new Circle(6);  
$shapes[] = new Rectangle(3, 1);  
**$shapes[] = new Triangle(4, 5);**

foreach ($shapes as $shape) {  
    $area = $areaCalculator->area($shape);  
    print($area);  
    print(PHP_EOL);  
}

Mudah bukan?

Keuntungan:

  • Mudah untuk menambah fungsi sesuai dengan requirement baru
  • Mudah untuk mengetes hanya fungsi yang baru
  • Kode lebih mudah di-maintain
  • Lebih sedikit bugs, karena kode eksisting tidak diubah

LSP — Liskov Substitution Principle

Definisi: “Functions that use pointers or references to base classes must be able to use objects of derived classes without knowing it

Class harus bisa di-switch dengan class turunannya tanpa menyebabkan kesalahan logic ataupun teknis. Agak sedikit sulit untuk dijelaskan, tapi kita bisa lihat contohnya langsung:

Kita punya base class “Bird”, turunannya ada “Sparrow” dan “Kiwi”. Kita ketahui bahwa burung Sparrow bisa terbang sedangkan Kiwi tidak bisa.

Contoh pelanggaran LSP dalam kode:

File: Bird.php

<?php

class Bird  
{

    function fly()  
    {  
        print("I'm flying...");  
    }  
}

File: Sparrow.php

<?php

class Sparrow extends Bird  
{

    function fly()  
    {  
        print("I'm flying just like a sparrow is...");  
    }  
}

File: Kiwi.php

<?php

class Kiwi extends Bird  
{

    function fly()  
    {  
        print("I'm sorry, but I can't fly...");  
    }  
}

File: index.php

<?php  
spl_autoload_register();

$birds = [];  
$birds[] = new Bird();  
$birds[] = new Sparrow();  
$birds[] = new Kiwi();

foreach ($birds as $bird) {  
    $bird->fly();  
    print(PHP_EOL);  
}

Hasilnya:

I'm flying...  
I'm flying just like a sparrow is...  
I'm sorry, but I can't fly...

Kok burung tidak bisa terbang? Ada sesuatu yang salah di sini. Ternyata, tidak semua burung bisa terbang. Dan burung yang tidak bisa terbang tidak boleh dipaksakan untuk terbang.. kan kacian… 😢

Solusinya, kita harus membuat dua class yang spesifik: “FlyingBird” dan “Bird”.

Contoh:

File: FlyingBird.php

<?php

class FlyingBird  
{

    function fly()  
    {  
        print("I'm flying...");  
    }  
}

File: Bird.php

<?php

class Bird  
{

    function walk()  
    {  
        print("I'm walking...");  
    }  
}

File: Sparrow.php

<?php

class Sparrow extends FlyingBird  
{

    function fly()  
    {  
        print("I'm flying just like a sparrow is...");  
    }  
}

File: Kiwi.php

<?php

class Kiwi extends Bird  
{

    function walk()  
    {  
        print("I'm walking just like a kiwi is...");  
    }  
}

File: index.php

<?php  
spl_autoload_register();

$flyingBirds = [];  
$flyingBirds[] = new FlyingBird();  
$flyingBirds[] = new Sparrow();

foreach ($flyingBirds as $bird) {  
    $bird->fly();  
    print(PHP_EOL);  
}

$birds = [];  
$birds[] = new Bird();  
$birds[] = new Kiwi();

foreach ($birds as $bird) {  
    $bird->walk();  
    print(PHP_EOL);  
}

Dengan LSP, class beserta turunannya menjadi lebih jelas dan spesifik, dan terhindar dari kesalahan logic maupun teknis.

Keuntungan:

  • Ekstensi dari OCP
  • Lebih mudah me-reuse kode
  • Class turunan tidak harus mengimplementasi function yang tidak relevan
  • Menghindari kesalahan logic dalam class turunannya

ISP — Interface Segregation Principle

Definisi: “Clients should not be forced to depend upon interfaces that they do not use.”

Mirip-mirip SRP, tapi ini berlaku ntuk interface. Interface juga tidak boleh memiliki terlalu banyak function sehingga memaksa client class nya untuk mengimplementasi semua function. Karena, mungkin beberapa function tidak dibutuhkan oleh class tersebut.

Contoh:

File: SmartPrinter.php

<?php

interface SmartPrinter  
{

    function print();

    function scan();

    function copy();  
}

File: ModernPrinter.php

<?php

class ModernPrinter implements SmartPrinter  
{

    function print()  
    {  
        print("I'm printing...");  
    }

    function scan()  
    {  
        print("I'm scanning...");  
    }

    function copy()  
    {  
        print("I'm copying...");  
    }  
}

Class “ModernPrinter” memiliki kemampuan untuk print, scan, dan copy dalam satu alat. “ModernPrinter” cukup mengimplementasi _interface “_SmartPrinter”. Maka semuanya berjalan lancar.

Tetapi kemudian ada printer “ClassicPrinter” yang kemampuannya hanya bisa print saja, sebagaimana printer biasa pada umumnya. Ketika “ClassicPrinter” meng-implementasi interface “_SmartPrinter”, “ClassicPrinter” dipaksa untuk meng-implementasi fungsi _scan dan copy padahal dia tidak mampu. Mirip LSP juga sedikit.

Solusi untuk masalah ini adalah dengan ISP. Mari kita terapkan ISP pada program kita. Pertama kita harus men-segregasi / memecah interface menjadi beberapa interface yang spesifik, yaitu menjadi “Printable”, “Scanable”, dan “Copyable”. Kemudian class-class implementasinya bisa memilih interface mana saja yang dibutuhkan sesuai spesifikasi class masing-masing.

File: Printable.php

<?php

interface Printable  
{

    function print();  
}

File: Scanable.php

<?php

interface Scanable  
{

    function scan();  
}

File: Copyable.php

<?php

interface Copyable  
{

    function copy();  
}

File: ModernPrinter.php

<?php

class ModernPrinter implements Printable, Scanable, Copyable  
{

    function print()  
    {  
        print("I'm printing...");  
    }

    function scan()  
    {  
        print("I'm scanning...");  
    }

    function copy()  
    {  
        print("I'm copying...");  
    }  
}

File: ClassicPrinter.php

<?php

class ClassicPrinter implements Printable  
{

    function print()  
    {  
        print("I'm printing...");  
    }  
}

Begitu kira-kira penerapan ISP.

Keuntungan ISP

  • Desain yang fleksible
  • Mudah untuk meng-extend fungsi
  • Kode lebih mudah di-maintain dalam bagian-bagian kecil
  • Client tidak perlu dipaksa untuk meng-implement fungsi yang tidak dibutuhkan

DIP — Dependency Inversion Principle

Definisi:

A. “High level modules should not depend upon low level modules. both should depend upon abstractions.

B. “Abstractions should not depend upon details. Details should depend upon abstractions.”

Cukup membingungkan ya. Apalagi jika kita belum terbiasa dengan OOP. Simple-_nya kurang lebih: setiap kita perlu suatu _class, use / import interface atau abstract class nya, jangan concrete class nya.

Contoh kasusnya aplikasi kita memiliki fitur pembayaran menggunakan payment gateway. Saat ini payment gateway yang digunakan misalnya Doku. Kita mungkin akan membuat kode seperti ini:

File: Doku.php

<?php

class Doku  
{

    function payWithDoku()  
    {  
        print("paying with Doku...");  
    }  
}

File: PaymentController.php

<?php

class PaymentController  
{

    function __construct(Doku $doku)  
    {  
        $this->paymentGateway = $doku;  
    }

    function pay()  
    {  
        $this->paymentGateway->payWithDoku();  
    }  
}

File: index.php

<?php  
spl_autoload_register();

$paymentCtl = new PaymentController(new Doku());  
$paymentCtl->pay();

Pada contoh tersebut, “PaymentController” depends terhadap concrete class “_Doku”. Ini yang melanggar prinsip _DIP. Seharusnya depends-_nya terhadap abstraksi yaitu: _interface / abstract class. Mari kita perbaiki!

File: PaymentGateway.php

<?php

interface PaymentGateway  
{

    function pay();  
}

File: Doku.php

<?php

class Doku implements PaymentGateway  
{

    function pay()  
    {  
        print("paying with Doku...");  
    }  
}

File: PaymentController.php

<?php

class PaymentController  
{

    function __construct(PaymentGateway $paymentGateway)  
    {  
        $this->paymentGateway = $paymentGateway;  
    }

    function pay()  
    {  
        $this->paymentGateway->pay();  
    }  
}

Dengan DIP, “PaymentController” hanya depends terhadap interface “_PaymentGateway” (abstraksi). Jika suatu saat kita perlu _switch implementasi “PaymentGateway” yang digunakan, contoh kita perlu menggunakan Midtrans. Maka kita tidak perlu meng-edit “PaymentController”, tapi cukup membuat class baru yang implements “_PaymentGateway”. Hmm, ingat sesuatu? Yup, prinsip _OCP. DIP cukup berkaitan erat dengan OCP. Dengan DIP prinsip OCP juga akan tercapai. Langsung saja kita lihat contohnya:

File: Midtrans.php

<?php

class Midtrans implements PaymentGateway  
{

    function pay()  
    {  
        print("paying with Midtrans...");  
    }  
}

File: index.php

<?php  
spl_autoload_register();

$controllers = [];  
$controllers[] = new PaymentController(new Midtrans());  
$controllers[] = new PaymentController(new Doku());

foreach ($controllers as $ctl) {  
    $ctl->pay();  
    print(PHP_EOL);  
}

Pada waktu dijalankan, kita bisa memberikan parameter “Doku” ataupun “Midtrans” sesuai requirement, tanpa harus meng-edit class “PaymentController”.

Keuntungan DIP

  • Loose coupling
  • Mudah untuk meng-implementasi “plug-and-playservice
  • Bisa dibuat mock dan mudah dites (jika menggunakan TDD)
  • Kode menjadi lebih reusable

Kesimpulan

Whew, cukup melelahkan ya gaess… Jika belum paham benar, kamu bisa baca-baca lagi di lain waktu. Kamu juga bisa tanya-tanya di comment biar bisa saling tukar pikiran, karena saya juga masih belajar dan selalu belajar.

Saya sarankan untuk memahami dengan baik prinsip ini karena SOLID adalah salah satu “jurus andalan” para programmer pro baik dalam maupun luar negeri. Salah satu nya Taylor Otwell — sang penulis framework Laravel — , menyarankan agar memahami prinsip SOLID dan Design Patterns dengan baik agar bisa membuat program yang bagus.

Kode di posting ini bisa kamu download dari Github Repository ya.

Akhir kata, mudah-mudahan posting ini bermanfaat khususnya buat penulis umumnya buat pembaca semua. Jangan lupa clap dan comment ya buat bantu penulis bikin lebih banyak lagi posting yang mudah-mudahan bermanfaat.

Wassalam..


Share postingan ini:

Perlu dukungan software, template desain, ataupun ilmu tambahan mengenai digital marketing dan bisnis online ?

Cek produk-produk pilihan berikut ini untuk meningkatkan omset bisnis Anda:

WhatsApp Master Closing

WhatsApp Master Closing

Ecourse

Perhatian !!! Jika Anda Menggunakan WhatsApp untuk promosi, maka Anda butuh ini . . . Kuasai SEMUA STRATEGI JUALAN Laris Manis di WhatsApp yang Sudah Terbukti Berhasil di Banyak Bisnis Tanpa Perlu Coba-coba Buat apa bertahan dengan cara lama, kalau ternyata jualan Anda bisa SEMAKIN LARIS SECEPATNYA

WA Master Closing adalah program belajar berisi video-video dan modul-modul yang isinya adalah strategi KOMPLIT jualan di WhatsApp. Selain ilmu yang lengkap, pesertanya nanti juga akan dapat support grup diskusi dan update materi secara berkala. Jadi pesertanya boleh bertanya sepuasnya alias KONSULTASI apapun tentang strategi jualan di WA. Ditambah lagi, nanti juga akan terus dapat UPDATE ilmu tentang jualan di WA.

Sehingga, setelah ikut ini otomatis jualan Anda di WA akan LEBIH BAIK dari sebelumnya.

 
Selengkapnya
WBS Pro

WBS Pro

Software

8.000+ Orang Sudah Menggunakan Tools Whatsapp Marketing Ini!

Follow Up Ataupun Mengirim Pesan ke Whatsapp yang ada dalam database Anda dengan sangat singkat, tanpa harus manual satu persatu.

 
Selengkapnya
WBS Chat

WBS Chat

Software

Sudah Saatnya Memaksimalkan Penjualan Anda Melalui Whatsapp

Sekarang! Satu Akun Whatsapp Bisa Di Akses Dari Mana Saja Secara Bersamaan, Baik Dari Laptop Maupun Smartphone

 
Selengkapnya