SOLID PRINCIPLE adalah sebuah singkatan dari beberapa prinsip pemrograman yang ditujukan untuk membuat kode aplikasi lebih adaptif terhadap perubaha. SOLID sendiri bukan prinsip, tetapi singkatan dari 5 prinsip pola pemrograman yang dicetuskan oleh Robert C. Martin, Michael Feathers, Bertrand Meyer, dan James Coplien di sekitar tahun 2000-an. SOLID merupakan singkatan dari :

  • [S] Single Responsibility Principle
  • [O] Open Close Principle
  • [L] Liskov Substitution Principle
  • [I] Interface Segregation Principle
  • [D] Dependency Inversion Principle

Seiring berkembangnya dunia komputer dari yang semula skrip dan prosedur hingga terlahirnya pemrograman berorientasi object, teknik rekayasa pengembangan perangkat lunak juga telah banyak berkembang. Kompleksitas dalam penulisan kode harus berhadapan dengan kebutuhan user yang dinamis dalam aplikasi, pengembangan dengan banyak developer, penambahan pengurangan modul aplikasi, ketergantungan terhadap library yang ada dan pengelolaan kode aplikasi yang sudah berjalan.

SOLID Principle memberikan beberapa keuntungan jika diterapkan dengan benar saat pembuatan kode program aplikasi.

  • Memudahkan kolaborasi. Code yang dibuat dengan menerapkan SOLID Principle akan lebih jelas , lebih terstruktur, dan memungkinkan tim pengembang membangun aplikasi di bagian yang berbeda beda / modul terpisah secara bersamaan tanpa adanya konflik kode.
  • Lebih mudah dilakukan testing. Kode yang dibangun dengan Single Responsibility dan loosely coupling akan sangat mudah untuk diisolasi dan dilakukan unit testing secara terpisah.
  • High Scalability dan Lebih Fleksible. SOLID Principle memberikan kesempatan yang besar akan adanya perubahan aplikasi di masa mendatang dan memungkinkan adanya ekspansi fitur aplikasi tanpa mengurangi stabilitas sistem. Open close principle memungkinkan kita extend kode untuk menerapkan fitur baru dibandingkan mengubah kode.
  • Mengurangi Kompleksitas. SOLID Principle memecah requirement komplek dari aplikasi menjadi class class yang lebih kecil dan lebih mudah dikelola. SIngle Responsibility principle membuat class class yang dikembangkan akan lebih fokus pada satu tujuan utama dan lebih jelas peruntukannya.
  • Maintenance Lebih Mudah. Kode yang dibuat dengan SOLID principle akan lebih mudah dibaca dan dipahami, lebih terorganisir, serta menyederhanakan maintenance jangka panjang. Perubahan kode di salah satu class aplikasi akan sangat sedikit sekali berdampak pada sisi lain aplikasi.

A. Single Responsibility Principle

Single Responsibility Principle menginstruksikan developer bahwa suatu kelas hanya boleh memiliki satu alasan untuk perubahan. Jika suatu class memiliki lebih dari 1 alasan perubahan, maka class tersebut bisa dipastikan memiliki lebih dari 1 responsibility. Suatu class yang memiliki banyak alasan perubahan harus dipecah menjadi class yang lebih kecil yang memiliki satu alasan perubahan dan responsibility.

Perhatikan contoh kode class berikut ini

    public class Payment
    {
        public string Account { get; set; }
        public decimal? Amount { get; set; }
    }
    public class DataProcessor
    {        
        public void ImportData(string filepath)
        {
            var lines = System.IO.File.ReadAllLines(filepath);
            List<Payment> data = new List<Payment>();
            foreach (var line in lines)
            {
                var cols = line.Split(new[] { ';', ',', '\t' });
                var pendingPayment = new Payment();            
                pendingPayment.Account = cols[0].Trim();
                pendingPayment.Amount = decimal.Parse(cols[1]);
                data.Add(pendingPayment);
            }

            using (var connection = new Microsoft.Data.SqlClient.SqlConnection("Data Source = (local); Initial Catalog = Payment; Integrated Security = True"))
            {
                connection.Open();
                using (var transaction = connection.BeginTransaction())
                {
                    foreach (var payment in data)
                    {
                        var command = connection.CreateCommand();
                        command.Transaction = transaction;
                        command.CommandType = System.Data.CommandType.StoredProcedure;
                        command.CommandText = "dbo.submit_payment";
                        var Params = new[]
                        {
                            new SqlParameter("@account", payment.Account),
                            new SqlParameter("@amount", payment.Amount)
                        };
                        command.Parameters.AddRange(Params);                        
                        command.ExecuteNonQuery();
                    }
                    transaction.Commit();
                }
                connection.Close();
            }
        }
    }

Kode tersebut menyalahi prinsip Single Responsibility Principle dikarenakan memiliki lebih dari 1 alasan perubahan dan responsibility

  • Bagaimana jika format file berubah, misal posisi kolom atau separator data, atau tidak lagi file teks yang dapat dibaca baris per baris
  • Bagaimana jika penyimpanan ke basis data berubah, bagaimana jika bukan basis data SQL, bagaimana jika stored procedure yang berbeda, atau bagaimana jika tidak menggunakan SqlCommand untuk ke depannya?

Kita perlu melakukan refactor terhadap kode dan memecahnya menjadi beberapa class agar sesuai dengan prinsip Single responsibility.

    public interface IFileSourceReader
    {
        List<Payment> ReadFileData(string filepath);
    }
    public class CSVReader : IFileSourceReader
    {
        public List<Payment> ReadFileData(string filepath)
        {
            var lines = System.IO.File.ReadAllLines(filepath);
            List<Payment> data = new List<Payment>();
            foreach (var line in lines)
            {
                var cols = line.Split(new[] { ';', ',', '\t' });
                var pendingPayment = new Payment();
                pendingPayment.Account = cols[0].Trim();
                pendingPayment.Amount = decimal.Parse(cols[1]);
                data.Add(pendingPayment);
            }
            return data;
        }
    }
    public interface IPaymentDataAccess
    {
        void SavePaymentData(List<Payment> data);
    }
    public class PaymentDataAccess : IPaymentDataAccess
    {
        public void SavePaymentData(List<Payment> data)
        {
            using (var connection = new Microsoft.Data.SqlClient.SqlConnection("Data Source = (local); Initial Catalog = Payment; Integrated Security = True"))
            {
                connection.Open();
                using (var transaction = connection.BeginTransaction())
                {
                    foreach (var payment in data)
                    {
                        var command = connection.CreateCommand();
                        command.Transaction = transaction;
                        command.CommandType = System.Data.CommandType.StoredProcedure;
                        command.CommandText = "dbo.submit_payment";
                        var Params = new[]
                        {
                            new SqlParameter("@account", payment.Account),
                            new SqlParameter("@amount", payment.Amount)
                        };
                        command.Parameters.AddRange(Params);
                        command.ExecuteNonQuery();
                    }
                    transaction.Commit();
                }
                connection.Close();
            }
        }
    }
public class PaymentDataProcessor {

    private IFileSourceReader fileSourceReader;
    private IPaymentDataAccess dataAccess;

    public PaymentDataProcessor(
        IPaymentDataAccess dataAccess,
        IFileSourceReader fileReader)
    {
        this.dataAccess = dataAccess;
        this.fileSourceReader = fileReader;
    }

    public void ProcessData(string filepath)
    {
        var rows  = fileSourceReader.ReadFileData(filepath);
        dataAccess.SavePaymentData(rows);
    }
}

Dari hasil refactor code tersebut kita menghasilkan class dengan hanya 1 responsibility

  • Class CSVReader implement dari IFileSourceReader hanya akan berubah ketika format file dari data sumber berubah.
  • Class PaymentDataAccess implement dari IPaymentDataAccess hanya akan berubah jika mekanisme penyimpanan ke basis data berubah atau jika client koneksi ke database berubah.
  • PaymentDataProcessor sudah disederhanakan dan hanya merepresentasikan blueprint dari proses tanpa adanya detail implementasi.

Single Responsibility Pattern dan Decorator Patern

Decorator pattern dapat digunakan dengan Single Responsibility Principle untuk memastikan tetap memiliki hanya 1 responsibility namun dapat menambahkan fungsionalitas ke class yang diinginkan dan tetap mengimplementasikan interface yang ada

 public class JSONReader : IFileSourceReader
 {
     public List<Payment> ReadFileData(string filepath)
     {
         var json = System.IO.File.ReadAllText(filepath);
         var data = System.Text.Json.JsonSerializer.Deserialize<List<Payment>>(json);
         return data ?? new List<Payment>();            
     }
 }

 public class CompositeDecoratorReader: IFileSourceReader
 {
     private IFileSourceReader csv;
     private IFileSourceReader json;

     public CompositeDecoratorReader(IFileSourceReader csvReader, IFileSourceReader jsonReader)
     {
         csv= csvReader;
         json= jsonReader;
     }

     public List<Payment> ReadFileData(string filepath)
     {
         if(System.IO.Path.GetExtension(filepath) == ".json")
         {
             return json.ReadFileData(filepath);
         }
         else
         {
             return csv.ReadFileData(filepath);
         }
     }
 }

Class CompositeDecoratorReader merupakan class Decorator yang masih mengimplementasikan IFileSourceReader dan tetap memiliki satu responsibility untuk membaca file data, namun dengan decorator tersebut, class menjadi dapat membaca baik json dan csv yang implementasinya diterapkan di class terpisah.

B. Open Close Principle

Open Close Principle menyatakan bahwa suatu program harus terbuka (Open) terhadap ekstensi perluasan, namun tertutup (Close) terhadap perubahan.

Terbuka terhadap ekstensi, seiring dengan perubahan requirement dari aplikasi , kita dapat mengubah bagaimana suatu modul bekerja, kita dapat memperluas modul dengan perilaku baru atau menambah fungsionalitas yang memenuhi kebutuhan baru dari aplikasi. Dengan kata lain, kita dapat merubah apa yang dapat dilakukan oleh suatu modul aplikasi.

Tertutup terhadap perubahan. Kita dapat memperluas bagaimana modul bekerja tanpa merubah kode sumber yang ada atau binary modul tersebut. Binary dari modul baik itu file Java JAR, library windows DLL, tetap tidak berubah.

Beberapa penerapan dari Open Close Principle adalah menggunakan abstraksi (abstract class, abstract method, virtual method, interface) ataupun extension method.

Perhatikan class berikut ini

public class VolumeCalculator
{
    public double CalculateVolume(Object3D object3)
    {
        if(object3 is Kubus kubus)
        {
            return kubus.Sisi * kubus.Sisi * kubus.Sisi;
        }
        else if(object3 is Balok balok)
        {
            return balok.Panjang * balok.Lebar * balok.Tinggi;
        }
        else if(object3 is Bola bola)
        {
            return (4/3) * Math.PI * Math.Pow(bola.JariJari, 3);
        }
        else
        {
            throw new ArgumentException("Unknown object type");
        }
    }
}

Class tersebut tidak sesuai dengan prinsip Open Close Principle dikarenakan ketika kita membuat class turunan Object3D baru, katakanlah class Tabung, maka mau tidak mau kita harus mengubah class VolumeCalculator untuk menyesuaikan perubahan tersebut. Ini menyalahi prinsip tertutup untuk perubahan, yang mana kita harus menambahkan baris baru untuk if-else.

Agar supaya sesuai dengan prinsip Open Close Principle, kita perlu membuat suatu abstract class atau interface. Perhatikan interface dan class implementasinya berikut ini.

 public interface IObject3D
 {
     double CalculateVolume();
 }

 public class Kubus:  IObject3D
 {
     public double Sisi { get; set; }

     public double CalculateVolume()
     {
         return Sisi * Sisi * Sisi;
     }
 }

 public class Balok :  IObject3D
 {
     public double Panjang { get; set; }
     public double Lebar { get; set; }
     public double Tinggi { get; set; }

     public double CalculateVolume()
     {
         return Panjang * Lebar * Tinggi;
     }
 }

 public class Bola : IObject3D
 {
     public double JariJari { get; set; }

     public double CalculateVolume()
     {
         return (4/3) * Math.PI * Math.Pow(JariJari, 3);
     }
 }

Dengan demikian, kita dapat merubah class VolumeCalculator sehingga menjadi sesuai dengan prinsip Open Close Principle berikut ini

 public class VolumeCalculator
 {
     public double CalculateVolume(IObject3D object3d)
     {
         return object3d.CalculateVolume();
     }
 }

Class VolumeCalculator yang baru ini sesuai dengan Open Close Principle dikarenakan : ketika ada object 3d baru, tidak akan merubah class VolumeCalculator maupun interfacenya (tertutup terhadap perubahan), karena kita cukup membuat class object3d baru yang mengimplementasikan interface IObject3D. Membuat class baru yang mengimplementasikan interface IObject3D membuktikan bahwa hal ini terbuka terhadap ekstensi.

Terkadang pula, kita perlu mengimplementasikan fungsi baru akan tetapi kita tidak bisa merubah abstract class atau interface aslinya sama sekali, padahal kita perlu membuat fungsi untuk semua object3d. Kita dapat menggunakan pola Extension method yang ada pada C#. Perhatikan class berikut

 public static class Object3DExtensions
 {
     public static string GetObject3DTypeName(this IObject3D obj)
     {
         return obj.GetType().Name;
     }
 }

internal class Program
{
    static void Main(string[] args)
    {
        Bola bola = new Bola() { JariJari = 10 };
        Console.WriteLine($"Object 3D {bola.GetObject3DTypeName()} memiliki volume {bola.CalculateVolume()}");
    }
}

Dari listing kode tersebut terlihat bahwa kita memang tidak dapat merubah interface maupun abstract class, namun kita dapat menggunakan extensions method yang ada pada C# untuk menambahkan fungsi dari object turunan IObject3D tanpa kita perlu merubah class lainnya.

C. Liskov Substitution Principle

Liskov Substitution Principle (LSP) prinsip ini menyatakan bahwa objek dari sebuah superclass (kelas induk) harus dapat digantikan oleh objek dari subclass (kelas anak) tanpa memengaruhi kebenaran atau perilaku program. LSP berawal dari Barbara Liskov di akhir tahun 80-an dan dikaji ulang pada tahun 90-an oleh Liskov dan Jeannette Wing untuk menciptakan prinsip yang kita kenal dan gunakan saat ini. Prinsip ini juga mirip dengan Design by contract karya Bertrand Meyer.

Let Ø(x) be a property provable about objects x of type T. Then, Ø(y)
should be true for objects y of type S, where S is a subtype of T.

Maksud dari pernyataan tersebut adalah, Anda dapat menukar objek bertipe T dengan objek bertipe S, di mana S adalah subtipe T, tanpa merusak kebenaran program yang telah Anda buat.

Berikut ini adalah beberapa rule yang menjadi catatan penting agar memenuhi prinsip dari Liskov Substitution Principle

  • Aturan Kontravariant untuk Method Argumen pada Subclass

Method pada subclass harus menerima argumen yang sama atau lebih umum (contravariant) dibandingkan argumen yang diterima pada method superclass. Subclass tidak boleh memperketat prasyarat dari superclass. Jika suatu method di superclass menerima parameter (method argument) bertipe Karyawan, maka subclass harus menerima parameter bertipe Karyawan juga ,atau yang lebih umum mungkin bertipe Person, jangan di subclass menggunakan parameter bertipe object BusinessAnalystKaryawan. Hal ini bertujuan supaya kode yang mengandalkan tipe superclass dapat berfungsi jika parameternya diisi object dari subclass.

  • Aturan Kovarian pada Return Tipe pada Subclass

Method pada subclass harus memiliki return tipe yang sama atau lebih spesifik (covariant) jika dibandingkan dengan return type dari superclassnya. Ini artinya, subclass tidak boleh melemahkan pasca kondisi dari superclass. Jika method asli superclass menghasilkan return tipe berupa object Karyawan, ketika method tersebut di override pada subclass maka method tersebut boleh mengembalikan object dengan tipe yang sama (object Karyawan) atau object yang lebih spesifik misalkan ProgrammerStaff (turunan dari Karyawan).

  • Aturan Exception pada Subclass

Method pada subclass tidak boleh melempar exception yang tidak dilempar pada superclass / base class. Kecuali jika exception yang dilempar pada subclass tersebut subtipe dari exception yang sudah ada (subtipe dari exception yang dilempar oleh superclass). Tujuan dari aturan ini adalah kode program yang telah ditulis untuk menangani object dari superclass tidak akan terkejut dikarenakan muncul exception baru ketika menggunakan object dari subclass.

  • Aturan History Constraint

Aturan ini mengatur bagaimana perubahan state dari object saat diperlakukan oleh subclass. Sebuah subclass tidak boleh mengijinkan perubahan state yang tidak diijinkan oleh superclassnya. Contoh, jika superclass bersifat immutable maka subclass juga harus bersifat immutable. Jika superclass bersifat read only, maka subclass seharusnya tidak bisa bersifat read write.

  • Aturan Mengenai Kondisi Prekondisi dan Pascakondisi

Semua prekondisi yang diimplementasikan pada method superclass seharusnya tidak diperketat pada method yang dioverride oleh subclass nya, subclass tidak boleh lebih ketat dari itu atau justru lebih longgar.

Semua pascakondisi yang diimplementasikan pada superclass tidak diperlemah pada method yang dioverride oleh subclassnya, subclass tidak boleh lebih longgar dari itu atau justru bisa diperketat.

  • Aturan Mengenai Data Invariant

Subclass tidak boleh mengubah aturan aturan yang berlaku dari superclass nya. Subtipe harus mempertahankan semua properti dan kondisi yang dijamin oleh supertipe. Contoh, class BankAccount memiliki property saldo yang tidak dapat negatif. Sebuah invariant yang merupakan subclass dari BankAccount bisa saja menjadi menyalahi aturan  Liskov Substitution Principle apabila ternyata dapat membuat saldo menjadi negatif.

D.  Interface Segregation Principle

Setiap interface harus sespesifik mungkin, setiap interface harus memiliki tujuan yang spesifik. Kita tidak dapat dipaksakan untuk mengimplementasikan suatu interface dimana class yang kita kembangkan tidak ditujukan untuk maksud tujuan tersebut. Tidak boleh ada klien yang boleh dipaksa untuk bergantung pada antarmuka (interface) yang tidak mereka gunakan. 

“Many client-specific interfaces are better than one general-purpose
interface.”

Dalam pengembangan aplikasi kita perlu membuat interface. Interface harus dibuat sekecil mungkin. Interface yang kecil akan membuatnya menjadi lebih fokus pada tujuan interface tersebut. Kita tidak dapat membuat interface yang multipurpose atau semacam satu interface yang bisa memiliki semua fungsi/method. Secara sederhana, Interface Segregation Principle menganjurkan pemecahan antarmuka yang besar dan banyak method menjadi antarmuka-antarmuka yang lebih kecil, lebih spesifik, dan hanya berisi metode-metode yang relevan dengan tujuan tertentu. Dengan menggunakan interface yang lebih kecil, class yang mengimplementasikannya hanya perlu peduli dengan method-method yang benar-benar relevan dengan tugasnya.   Interface Segregation Principle membantu mencegah situasi di mana sebuah class dipaksa harus mengimplementasikan method yang tidak relevan, yang sering kali justru menyebabkan exception karena tanpa implementasi (muncul exception NotImplementedException).

Perhatikan interface berikut ini,

public interface IMesin
{
    void Move(float x);
    void Rotate(float angle);
    void Open();
    void Close();
    void TurnOnLight();
    void TurnOffLight();
    void SendSignal(byte[] data);
}

Interface tersebut tidak sesuai dengan Interface Segregation Principle dikarenakan tidak semua class bisa mengimplementasikannya. Bayangkan sebuah gateway buka tutup yang hanya bisa membuka tutup Gate tanpa bisa bergerak maju mundur. Akibatnya jika dipaksa untuk diimplementasikan akan banyak method dengan implementasi kosong.

public class DoorGatewayController:IMesin
{
    public void Close()
    {
        // close the door
    }
    public void Move(float x)
    {
        throw new NotImplementedException();
    }
    public void Open()
    {
        // open the door 
    }
    public void Rotate(float angle)
    {
        throw new NotImplementedException();
    }
    public void SendSignal(byte[] data)
    {
        throw new NotImplementedException();
    }
    public void TurnOffLight()
    {
        throw new NotImplementedException();
    }
    public void TurnOnLight()
    {
        throw new NotImplementedException();
    }
}

Interface tersebut harus dipecah menjadi interface yang lebih kecil dan lebih sederhana seperti berikut

public interface IWheelController
{
    void Move(float x);
    void Rotate(float angle);        
}

public interface IGatewayController
{
    void Open();
    void Close();
}

public interface ILampController
{
    void TurnOnLight();
    void TurnOffLight();
}

public interface IRadioContorller
{        
    void SendSignal(byte[] data);
}

Ketika diimplementasikan ke dalam concrete class maka tidak akan ada lagi method dengan implementasi kosong karena interface lebih fokus ke tujuannya.

    public class DoorController : IGatewayController
    {
        public void Close()
        {
            Console.WriteLine("Door is closing");
        }

        public void Open()
        {
            Console.WriteLine("Door is opening");
        }
    }

    public class RoomLampController : ILampController
    {
        public void TurnOffLight()
        {
            Console.WriteLine("Room lamp is turning off");
        }
        public void TurnOnLight()
        {
            Console.WriteLine("Room lamp is turning on");
        }
    }

    public class InfraredRadioController : IRadioContorller
    {
        public void SendSignal(byte[] data)
        {
            Console.WriteLine("Sending AC command via Infrared");
        }
    }

    public class RobocatController : IWheelController, IRadioContorller, ILampController
    {
        public void Move(float x)
        {
            Console.WriteLine($"Robocat is moving {x} units");
        }

        public void Rotate(float angle)
        {
            Console.WriteLine($"Robocat is rotating {angle} degrees");
        }

        public void SendSignal(byte[] data)
        {
            Console.WriteLine("Robocat is sending signal via WIFI");
        }

        public void TurnOffLight()
        {
            Console.WriteLine("Robocat is turning off flash light");
        }

        public void TurnOnLight()
        {
            Console.WriteLine("Robocat is turning on flash light");
        }
    }

E. Dependency Inversion Principle

Dalam Dependency Inversion Principle menyatakan bahwa suatu modul tingkat tinggi tidak boleh bergantung langsung dengan modul tingkat rendah, ketergantungan harus melalui suatu abstraksi. Abstraksi tidak boleh bergantung pada Detail, suatu detail implementasi / concrete class haruslah bergantung pada abstraksi.

One should “depend upon abstractions, [not] concretions.”

Inti dari Dependency Inversion Principle adalah membalikkan arah ketergantungan. Maksud dari membalikkan ketergantungan adalah seperti berikut. Class Ninja bergantung langsung pada Class Weapon.

Jika kita membalikkan ketergantungan sehingga menjadi bergantung pada abstraksi, maka Class Ninja menjadi bergantung pada interface IWeapon, dan implementasi Weapon bergantung juga pada abstraksi interface IWeapon. Kita dapat mengganti Weapon tanpa membuat class Ninja bermasalah, selama class Weapon tersebut mengimplementasikan interface IWeapon.

Perhatikan listing kode class berikut, kita akan membuat implementasi logger tidak bergantung apakah menggunakan log ke file atau ke database.

    public interface ILogger
    {
        void WriteLog(string message);
    }

    public class FileLogger : ILogger
    {
        private string logFilePath;
        public FileLogger(string logFilePath)
        {
            this.logFilePath = logFilePath;
        }
        public void WriteLog(string message)
        {
            System.IO.File.AppendAllText(logFilePath, $"{DateTime.Now}: {message}{Environment.NewLine}");
        }
    }

    public class DatabaseLogger : ILogger
    {
        private string connectionString;
        public DatabaseLogger(string connectionString)
        {
            this.connectionString = connectionString;
        }
        public void WriteLog(string message)
        {
            using (var connection = new Microsoft.Data.SqlClient.SqlConnection(connectionString))
            {
                connection.Open();
                var command = connection.CreateCommand();
                command.CommandType = System.Data.CommandType.Text;
                command.CommandText = "INSERT INTO Logs (Message, CreatedAt) VALUES (@message, @createdAt)";
                command.Parameters.AddWithValue("@message", message);
                command.Parameters.AddWithValue("@createdAt", DateTime.Now);
                command.ExecuteNonQuery();
                connection.Close();
            }
        }
    }


    public class SavingAccount {         
        private decimal balance;
        private ILogger logger;
        public SavingAccount(ILogger logger)
        {
            this.logger = logger;
            balance = 0;
        }
        public void Deposit(decimal amount)
        {
            balance += amount;
            logger.WriteLog($"Deposited {amount}, new balance is {balance}");
        }
        public void Withdraw(decimal amount)
        {
            if (amount > balance)
            {
                logger.WriteLog($"Attempted to withdraw {amount} but only {balance} available");
                throw new InvalidOperationException("Insufficient funds");
            }
            balance -= amount;
            logger.WriteLog($"Withdrew {amount}, new balance is {balance}");
        }
        public decimal GetBalance()
        {
            return balance;
        }
    }

Berikut kode program jika dijalankan

internal class Program
{    
    static void Main(string[] args)
    {
        var filelogger = new FileLogger(System.IO.Path.Combine(AppContext.BaseDirectory, "app.log"));           
        var savingAccount1 = new SavingAccount(filelogger);
        savingAccount1.Deposit(10000000);
        savingAccount1.Withdraw(10000000);

        var dblogger = new DatabaseLogger("Data Source=(local);Initial Catalog=Logging;Integrated Security=True");
        var savingAccount2 = new SavingAccount(dblogger);
        savingAccount2.Deposit(100000000);
        savingAccount2.Withdraw(5000000);
    }
}

Keuntungan dari Dependency Inversion Principle adalah flexibilitas, kita dapat membuat implementasi dari interface yang diperlukan tanpa perlu bersentuhan dengan consumer class dari interface tersebut. Hal ini akan membuat kode lebih mudah dipelihara, karena low level modul dapat kita sesuaikan dengan membuat implementasi baru tanpa menggangu high level modul. Selain itu, Dependency Inversion Principle akan membuat kode menjadi loosely coupling, karena menghapus ketergantungan terhadap implementasi concrete class tertentu.

Kesimpulan

SOLID principle adalah lima prinsip sederhana dalam pengembangan kode aplikasi yang jika diterapkan akan memberikan banyak keuntungan, antara lain : Memudahkan kolaborasi, Lebih mudah dilakukan testing, High Scalability dan Lebih Fleksible, Mengurangi Kompleksitas, Maintenance Lebih Mudah.

Referensi:



Leave a Reply

Your email address will not be published. Required fields are marked *

Search

Welcome

Bayu Pratama R N is a lonely programmer who is very enthusiastic about .NET. He just try to live a life of a programmer life and write a blog post when he is so sick about love.

Gallery