• Home
  • /
  • Coding Tips
  • /
  • SOLID Principles: Konsep Single Responsibility dalam Koding

SOLID Principles: Konsep Single Responsibility dalam Koding

 

Dalam dunia pengembangan perangkat lunak, kode program yang baik bukan hanya tentang bagaimana ia berjalan, tetapi juga tentang bagaimana ia dapat dipelihara dan dikembangkan. Inilah mengapa SOLID Principles menjadi standar dalam praktik pemrograman berorientasi objek (OOP). Banyak perusahaan yang mengharuskan programmernya memahami SOLID Principles ini.

Pada artikel pertama di blog ini, kita akan membahas prinsip pertama dalam SOLID yaitu: Single Responsibility Principle (SRP).

Apa Itu SOLID Principles?

SOLID adalah lima prinsip desain dalam Object-Oriented Programming (OOP) yang bertujuan untuk membuat kode lebih mudah dibaca, dipelihara, dan diperluas tanpa menyebabkan perubahan besar pada kode yang sudah ada. SOLID sendiri adalah akronim dari:

  1. S – Single Responsibility Principle (SRP)
  2. O – Open-Closed Principle (OCP)
  3. L – Liskov Substitution Principle (LSP)
  4. I – Interface Segregation Principle (ISP)
  5. D – Dependency Inversion Principle (DIP)

Apa Itu Single Responsibility Principle (SRP)?

Single Responsibility Principle menyatakan bahwa setiap kelas harus memiliki satu tanggung jawab utama. Artinya, sebuah kelas hanya boleh memiliki satu alasan untuk berubah. Jika sebuah kelas memiliki lebih dari satu tanggung jawab, maka kode tersebut akan sulit untuk dikelola dan dipelihara.

Kenapa SRP Itu Penting?

Tanpa menerapkan SRP, sebuah kode program akan menjadi monolitik, sulit diperbaiki, dan lebih rentan terhadap bug. Dengan SRP, kita mendapatkan keuntungan berikut:

  1. Kode lebih mudah dibaca dan dipahami
  2. Lebih mudah untuk diuji secara unit testing
  3. Meminimalisir efek domino saat ada perubahan
  4. Kode menjadi lebih modular dan fleksibel

Contoh Pelanggaran SRP (Bad Practice)

Mari kita lihat contoh koding dalam bahasa Java dan Python di mana SRP dilanggar. Bayangkan kita memiliki kelas Report yang bertanggung jawab untuk menghasilkan laporan, mencetak laporan, dan menyimpan laporan ke database:

Java:

class Report {
    void generatePDF() {
        // Logika untuk membuat laporan dalam format PDF
    }

    void printReport() {
        // Logika untuk mencetak laporan
    }

    void saveToDatabase() {
        // Logika untuk menyimpan laporan ke database
    }
}

Python:

class Report:
    def generate_pdf(self):
        # Logika untuk membuat laporan dalam format PDF
        pass
    
    def print_report(self):
        # Logika untuk mencetak laporan
        pass
    
    def save_to_database(self):
        # Logika untuk menyimpan laporan ke database
        pass

Masalah: Kelas Report menangani lebih dari satu tanggung jawab. Jika ada perubahan pada cara mencetak laporan, maka kita juga berisiko memodifikasi bagian lain yang tidak terkait, seperti penyimpanan ke database. Ini bertentangan dengan prinsip SRP.

Cara Memperbaiki dengan SRP (Good Practice)

Untuk menerapkan Single Responsibility Principle, kita harus memisahkan tanggung jawab ke dalam kelas-kelas yang lebih spesifik.

Java:

class ReportGenerator {
    void generatePDF() {
        // Logika untuk membuat laporan dalam format PDF
    }
}

class ReportPrinter {
    void printReport() {
        // Logika untuk mencetak laporan
    }
}

class ReportSaver {
    void saveToDatabase() {
        // Logika untuk menyimpan laporan ke database
    }
}

Python:

class ReportGenerator:
    def generate_pdf(self):
        # Logika untuk membuat laporan dalam format PDF
        pass

class ReportPrinter:
    def print_report(self):
        # Logika untuk mencetak laporan
        pass

class ReportSaver:
    def save_to_database(self):
        # Logika untuk menyimpan laporan ke database
        pass

Apa Keuntungan Penerapan SRP?

Setelah SRP diterapkan, apa saja sih keuntungannya?

  • Jika ada perubahan pada cara mencetak laporan, maka kita hanya perlu mengubah ReportPrinter, tanpa menyentuh ReportGenerator atau ReportSaver.
  • Kode menjadi lebih mudah untuk diuji karena setiap kelas memiliki tanggung jawab yang jelas.
  • Lebih fleksibel jika ke depannya kita ingin menambahkan fitur lain, seperti menyimpan laporan dalam format XML atau yang lainnya.

Apa Ciri Class yang Perlu Diterapkan SRP?

Apakah semua class dalam sebuah project perlu diterapkan SRP ini? Tentu saja tidak! Berikut ini adalah beberapa ciri-cirinya:

  1. Class yang Memiliki Lebih dari Satu Tanggung Jawab

    Jika sebuah class memiliki lebih dari satu tugas utama, kemungkinan besar ia melanggar SRP. Tanda-tanda class ini adalah nama class terlalu umum atau mencakup banyak fungsi (misalnya OrderManager, UserHandler, ReportProcessor), atau class menangani banyak aspek dalam sistem, seperti mengolah data, menyimpan ke database, dan mencetak laporan.
  2. Class yang Sering Berubah karena Alasan Berbeda

    Jika sebuah class sering berubah karena berbagai alasan, itu pertanda bahwa ia menangani lebih dari satu tanggung jawab. Tanda-tanda class ini adalah saat ada perubahan kecil, banyak bagian class yang harus diedit.
  3. Class yang Memiliki Nama yang Terlalu Umum atau “God Class“

    Jika class memiliki nama seperti Manager, Handler, Processor, itu sering kali tanda bahwa class ini menangani banyak hal sekaligus. Tanda-tanda class ini adalah bahwa nama class sulit menggambarkan satu tanggung jawab spesifik, serta mengontrol banyak aspek sistem sekaligus.

Contoh Penerapan SRP dalam Proyek

Sekarang kita lihat bagaimana penerapan SRP ini ke dalam sebuah proyek. Misalkan kita ada proyek untuk membuat program Java dan Python untuk menghitung luas dan keliling dari beberapa bangun datar seperti persegi, persegi panjang, dan lingkaran.

Belum Menerapkan SRP (Bad Practice)

Di contoh kode program dengan Java ini, satu class menangani perhitungan luas, keliling, dan juga input/output, sehingga sulit untuk diperluas dan diuji.

import java.util.Scanner;

class ShapeCalculator {
    Scanner scanner = new Scanner(System.in);

    void calculate() {
        System.out.println("Pilih bangun datar: 1. Persegi  2. Persegi Panjang  3. Lingkaran");
        int choice = scanner.nextInt();

        switch (choice) {
            case 1:
                System.out.print("Masukkan sisi persegi: ");
                double sisi = scanner.nextDouble();
                System.out.println("Luas: " + (sisi * sisi));
                System.out.println("Keliling: " + (4 * sisi));
                break;
            case 2:
                System.out.print("Masukkan panjang: ");
                double panjang = scanner.nextDouble();
                System.out.print("Masukkan lebar: ");
                double lebar = scanner.nextDouble();
                System.out.println("Luas: " + (panjang * lebar));
                System.out.println("Keliling: " + (2 * (panjang + lebar)));
                break;
            case 3:
                System.out.print("Masukkan jari-jari lingkaran: ");
                double r = scanner.nextDouble();
                System.out.println("Luas: " + (Math.PI * r * r));
                System.out.println("Keliling: " + (2 * Math.PI * r));
                break;
            default:
                System.out.println("Pilihan tidak valid.");
        }
    }
}

public class Main {
    public static void main(String[] args) {
        ShapeCalculator calculator = new ShapeCalculator();
        calculator.calculate();
    }
}

atau jika dalam Python sebagai berikut

import math

class ShapeCalculator:
    def calculate(self):
        print("Pilih bangun datar: 1. Persegi  2. Persegi Panjang  3. Lingkaran")
        choice = int(input("Masukkan pilihan: "))

        if choice == 1:
            sisi = float(input("Masukkan sisi persegi: "))
            luas = sisi * sisi
            keliling = 4 * sisi
        elif choice == 2:
            panjang = float(input("Masukkan panjang: "))
            lebar = float(input("Masukkan lebar: "))
            luas = panjang * lebar
            keliling = 2 * (panjang + lebar)
        elif choice == 3:
            r = float(input("Masukkan jari-jari lingkaran: "))
            luas = math.pi * r * r
            keliling = 2 * math.pi * r
        else:
            print("Pilihan tidak valid.")
            return

        print(f"Luas: {luas}")
        print(f"Keliling: {keliling}")

if __name__ == "__main__":
    calculator = ShapeCalculator()
    calculator.calculate()

Masalah dari kode program di atas adalah Class ShapeCalculator menangani banyak hal yaitu terkait dengan pengambilan input, perhitungan luas dan keliling, dan menampilkan output. Hal ini mengakibatkan sulit diuji karena semua fungsionalitas bercampur. Selain itu hal ini juga membuat kode program tidak fleksibel, jika ingin menambahkan bangun datar lain, maka harus mengedit kode utama.

Setelah Menerapkan SRP (Good Practice)

Sekarang kita akan memisahkan tanggung jawab menjadi beberapa class agar lebih modular, fleksibel, dan mudah diuji.

import java.util.Scanner;

// Abstraksi untuk semua bangun datar
interface Shape {
    double calculateArea();
    double calculatePerimeter();
}

// Implementasi Persegi
class Square implements Shape {
    private double side;

    public Square(double side) {
        this.side = side;
    }

    @Override
    public double calculateArea() {
        return side * side;
    }

    @Override
    public double calculatePerimeter() {
        return 4 * side;
    }
}

// Implementasi Persegi Panjang
class Rectangle implements Shape {
    private double length, width;

    public Rectangle(double length, double width) {
        this.length = length;
        this.width = width;
    }

    @Override
    public double calculateArea() {
        return length * width;
    }

    @Override
    public double calculatePerimeter() {
        return 2 * (length + width);
    }
}

// Implementasi Lingkaran
class Circle implements Shape {
    private double radius;

    public Circle(double radius) {
        this.radius = radius;
    }

    @Override
    public double calculateArea() {
        return Math.PI * radius * radius;
    }

    @Override
    public double calculatePerimeter() {
        return 2 * Math.PI * radius;
    }
}

// Class khusus untuk mengambil input dari user
class ShapeInput {
    private Scanner scanner = new Scanner(System.in);

    public Shape getShape() {
        System.out.println("Pilih bangun datar: 1. Persegi  2. Persegi Panjang  3. Lingkaran");
        int choice = scanner.nextInt();

        switch (choice) {
            case 1:
                System.out.print("Masukkan sisi persegi: ");
                return new Square(scanner.nextDouble());
            case 2:
                System.out.print("Masukkan panjang: ");
                double length = scanner.nextDouble();
                System.out.print("Masukkan lebar: ");
                double width = scanner.nextDouble();
                return new Rectangle(length, width);
            case 3:
                System.out.print("Masukkan jari-jari lingkaran: ");
                return new Circle(scanner.nextDouble());
            default:
                System.out.println("Pilihan tidak valid.");
                return null;
        }
    }
}

// Class khusus untuk menampilkan hasil (SRP)
class ShapePrinter {
    public static void printShapeInfo(Shape shape) {
        if (shape != null) {
            System.out.println("Luas: " + shape.calculateArea());
            System.out.println("Keliling: " + shape.calculatePerimeter());
        }
    }
}

// Class utama
public class Main {
    public static void main(String[] args) {
        ShapeInput shapeInput = new ShapeInput();
        Shape shape = shapeInput.getShape();
        ShapePrinter.printShapeInfo(shape);
    }
}

dan berikut ini adalah versi Python

import math

# Abstraksi untuk semua bangun datar
class Shape:
    def calculate_area(self):
        pass

    def calculate_perimeter(self):
        pass


# Implementasi Persegi
class Square(Shape):
    def __init__(self, side):
        self.side = side

    def calculate_area(self):
        return self.side * self.side

    def calculate_perimeter(self):
        return 4 * self.side


# Implementasi Persegi Panjang
class Rectangle(Shape):
    def __init__(self, length, width):
        self.length = length
        self.width = width

    def calculate_area(self):
        return self.length * self.width

    def calculate_perimeter(self):
        return 2 * (self.length + self.width)


# Implementasi Lingkaran
class Circle(Shape):
    def __init__(self, radius):
        self.radius = radius

    def calculate_area(self):
        return math.pi * self.radius * self.radius

    def calculate_perimeter(self):
        return 2 * math.pi * self.radius


# Class khusus untuk mengambil input dari user
class ShapeInput:
    @staticmethod
    def get_shape():
        print("Pilih bangun datar: 1. Persegi  2. Persegi Panjang  3. Lingkaran")
        choice = int(input("Masukkan pilihan: "))

        if choice == 1:
            side = float(input("Masukkan sisi persegi: "))
            return Square(side)
        elif choice == 2:
            length = float(input("Masukkan panjang: "))
            width = float(input("Masukkan lebar: "))
            return Rectangle(length, width)
        elif choice == 3:
            radius = float(input("Masukkan jari-jari lingkaran: "))
            return Circle(radius)
        else:
            print("Pilihan tidak valid.")
            return None


# Class khusus untuk menampilkan hasil (SRP)
class ShapePrinter:
    @staticmethod
    def print_shape_info(shape):
        if shape:
            print(f"Luas: {shape.calculate_area()}")
            print(f"Keliling: {shape.calculate_perimeter()}")


# Class utama
if __name__ == "__main__":
    shape = ShapeInput.get_shape()
    ShapePrinter.print_shape_info(shape)

Dari perubahan tersebut, kita bisa merasakan benefitnya kan? Benefit yang pertama adalah kode lebih modular, di mana setiap class hanya memiliki satu tanggung jawab utama. Benefit ke dua yaitu mudah diperluas. Jika ingin menambahkan segitiga, cukup buat class Triangle yang mengimplementasikan Shape tanpa mengubah kode lain. Benefit ke tiga adalah mudah diuji, di mana kita bisa menguji class Square, Rectangle, dan Circle secara terpisah tanpa perlu melibatkan input/output.

Bisa dipahami dengan mudah ya penjelasannya? Pada artikel berikutnya, kita akan membahas prinsip ke dua dalam SOLID yaitu Open/Closed Principle (OCP). Pastikan Anda mengikuti blog ini agar tidak ketinggalan pembahasannya.


Assalaamu'alaikum.. aktivitas keseharian saya mengajar di Universitas Sebelas Maret, dengan matakuliah pemrograman dan basis data. Adapun bidang penelitian saya tentang computational thinking dan computer-aided learning.

Leave a Reply