Open Close Principle (OCP)

drajatisme

drajatisme

Posted on September 8, 2023

Open Close Principle (OCP)

Robert C. Martin menjelaskan:

Software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification.

Open for extension artinya kode dapat dapat diperluas sesuai kebutuhan. Closed for modification artinya kode tidak dapat dirubah. Dengan kata lain, kita dapat menambah fungsionalitas pada sistem tanpa mengubah kode yang sudah ada.

Dave Cheney menjelaskan:

The Open / Closed Principle encourages you to compose simple types into more complex ones using embedding.

Agar lebih jelasnya, perhatikan kode yang ada pada contoh SRP sebelumnya:

package main

import (
    "fmt"
)

// User adalah tipe data yang mewakili informasi pengguna.
type User struct {
    ID       int
    Username string
    Email    string
}

// UserRepository bertanggung jawab untuk berinteraksi dengan basis data pengguna.
type UserRepository struct{}

func (us *UserRepository) GetUserByUsername(username string) (User, error) {
    // Logika untuk mengambil pengguna berdasarkan username
    return User{}, nil
}

func (us *UserRepository) Save() error {
    // Logika untuk menyimpan ke basis data
    return nil
}

// UserValidator bertanggung jawab untuk memvalidasi data pengguna.
type UserValidator struct{}

func (uv *UserValidator) Validate() error {
    // Logika validasi pengguna seperti validasi email, kata sandi, dsb.
    return nil
}

// UserService adalah layanan yang berfungsi sebagai penghubung antara klien dan data pengguna.
type UserService struct {
    Repository UserRepository
    Validator  UserValidator
}

// CreateUser membuat pengguna baru dengan menggunakan Repository dan Validator.
func (us *UserService) CreateUser(user User) error {
    // Validasi pengguna menggunakan Validator
    if err := us.Validator.Validate(); err != nil {
        return err
    }

    // Simpan pengguna ke basis data menggunakan Repository
    if err := us.Repository.Save(); err != nil {
        return err
    }

    return nil
}

func (us *UserService) GetUserByID(userID int) (User, error) {
    // GetUserByID mengambil pengguna berdasarkan ID dari Repository.
    return us.Repository.GetUserByID(userID)
}

func main() {
    // Inisialisasi layanan pengguna
    userService := UserService{
        Repository: UserRepository{}, // Contoh sederhana, seharusnya menggunakan koneksi basis data nyata.
        Validator:  UserValidator{},  // Contoh sederhana, seharusnya ada validasi yang lebih lengkap.
    }

    // Membuat pengguna baru
    newUser := User{ID: 1, Username: "john_doe", Email: "john@example.com"}
    err := userService.CreateUser(newUser)
    if err != nil {
        fmt.Println("Error:", err)
        return
    }

    // Mengambil pengguna berdasarkan ID
    user, err := userService.GetUserByID(1)
    if err != nil {
        fmt.Println("Error:", err)
        return
    }

    fmt.Println("User:", user)
}
Enter fullscreen mode Exit fullscreen mode

Kita asumsikan kode di atas menggunakan basis data MySQL. Jika ada perubahan untuk mengganti basis data menjadi Oracle akan ada beberapa kemungkinan.

  1. Mengubah seluruh kode agar dapat berinteraksi dengan basis data Oracle
  2. Menambah method GetUserByIDAtOracle dan SaveAtOracle

Agar dapat menerapkan OCP, kode harus dibuat seperti berikut:

package main

import (
    "fmt"
)

// User adalah tipe data yang mewakili informasi pengguna.
type User struct {
    ID       int
    Username string
    Email    string
}

type UserRepository interface {
    GetUserByUsername(username string) (User, error)
    Save() error
}

// UserRepository bertanggung jawab untuk berinteraksi dengan basis data pengguna.
type UserMySQLRepository struct{}

func (r *UserMySQLRepository) GetUserByUsername(username string) (User, error) {
    // Logika untuk mengambil pengguna berdasarkan username
    return User{}, nil
}

func (r *UserMySQLRepository) Save() error {
    // Logika untuk menyimpan ke basis data
    return nil
}

// UserValidator bertanggung jawab untuk memvalidasi data pengguna.
type UserValidator struct{}

func (uv *UserValidator) Validate() error {
    // Logika validasi pengguna seperti validasi email, kata sandi, dsb.
    return nil
}

// UserService adalah layanan yang berfungsi sebagai penghubung antara klien dan data pengguna.
type UserService struct {
    Repository UserRepository
    Validator  UserValidator
}

// CreateUser membuat pengguna baru dengan menggunakan Repository dan Validator.
func (us *UserService) CreateUser(user User) error {
    // Validasi pengguna menggunakan Validator
    if err := us.Validator.Validate(); err != nil {
        return err
    }

    // Simpan pengguna ke basis data menggunakan Repository
    if err := us.Repository.Save(); err != nil {
        return err
    }

    return nil
}

// GetUserByUsername mengambil pengguna berdasarkan username.
func (us *UserService) GetUserByUsername(username string) (User, error) {
    return us.Repository.GetUserByUsername(username)
}

func main() {
    // Inisialisasi layanan pengguna
    userService := UserService{
        Repository: &UserMySQLRepository{}, // Contoh sederhana, seharusnya menggunakan koneksi basis data nyata.
        Validator:  UserValidator{},        // Contoh sederhana, seharusnya ada validasi yang lebih lengkap.
    }

    // Membuat pengguna baru
    newUser := User{ID: 1, Username: "john_doe", Email: "john@example.com"}
    err := userService.CreateUser(newUser)
    if err != nil {
        fmt.Println("Error:", err)
        return
    }

    // Mengambil pengguna berdasarkan username
    user, err := userService.GetUserByUsername("myUser")
    if err != nil {
        fmt.Println("Error:", err)
        return
    }

    fmt.Println("User:", user)
}
Enter fullscreen mode Exit fullscreen mode

Kode di atas ditambahkan interface UserRepository, lalu struct UserMySQLRepository akan mengimplementasikan method yang ada pada interface tersebut. Kode UserMySQLRepository berisi kode spesifik untuk berinteraksi dengan basis data MySQL. Saat menginisialisasi Jika akan mengubah basis data menjadi Oracle, maka kode akan menjadi seperti berikut:

package main

import (
    "fmt"
)

// User adalah tipe data yang mewakili informasi pengguna.
type User struct {
    ID       int
    Username string
    Email    string
}

type UserRepository interface {
    GetUserByUsername(username string) (User, error)
    Save() error
}

// UserRepository bertanggung jawab untuk berinteraksi dengan basis data pengguna.
type UserMySQLRepository struct{}

func (r *UserMySQLRepository) GetUserByUsername(username string) (User, error) {
    // Logika untuk mengambil pengguna berdasarkan username
    return User{}, nil
}

func (r *UserMySQLRepository) Save() error {
    // Logika untuk menyimpan ke basis data
    return nil
}

type UserOracleRepository struct{}

func (r *UserOracleRepository) GetUserByUsername(username string) (User, error) {
    // Logika untuk mengambil pengguna berdasarkan username
    return User{}, nil
}

func (r *UserOracleRepository) Save() error {
    // Logika untuk menyimpan ke basis data
    return nil
}


// UserValidator bertanggung jawab untuk memvalidasi data pengguna.
type UserValidator struct{}

func (uv *UserValidator) Validate() error {
    // Logika validasi pengguna seperti validasi email, kata sandi, dsb.
    return nil
}

// UserService adalah layanan yang berfungsi sebagai penghubung antara klien dan data pengguna.
type UserService struct {
    Repository UserRepository
    Validator  UserValidator
}

// CreateUser membuat pengguna baru dengan menggunakan Repository dan Validator.
func (us *UserService) CreateUser(user User) error {
    // Validasi pengguna menggunakan Validator
    if err := us.Validator.Validate(); err != nil {
        return err
    }

    // Simpan pengguna ke basis data menggunakan Repository
    if err := us.Repository.Save(); err != nil {
        return err
    }

    return nil
}

// GetUserByID mengambil pengguna berdasarkan ID dari Repository.
func (us *UserService) GetUserByUsername(username string) (User, error) {
    return us.Repository.GetUserByUsername(username)
}

func main() {
    // Inisialisasi layanan pengguna
    userService := UserService{
        Repository: &UserOracleRepository{}, // Contoh sederhana, seharusnya menggunakan koneksi basis data nyata.
        Validator:  UserValidator{},        // Contoh sederhana, seharusnya ada validasi yang lebih lengkap.
    }

    // Membuat pengguna baru
    newUser := User{ID: 1, Username: "john_doe", Email: "john@example.com"}
    err := userService.CreateUser(newUser)
    if err != nil {
        fmt.Println("Error:", err)
        return
    }

    // Mengambil pengguna berdasarkan username
    user, err := userService.GetUserByUsername("myUser")
    if err != nil {
        fmt.Println("Error:", err)
        return
    }

    fmt.Println("User:", user)
}
Enter fullscreen mode Exit fullscreen mode

Kode di atas ditambahkan struct bernama UserOracleRepository, yang didalamnya berisi kode spesifik untuk berinteraksi dengan basis data oracle. Struct UserOracleRepository dimasukan saat inisialiasi UserService.

Dengan OCP kita dapat menambah fungsionalitas tanpa mengubah kode yang sudah ada.

Referensi

đź’– đź’Ş đź™… đźš©
drajatisme
drajatisme

Posted on September 8, 2023

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related