SOLID: Open Closed Principle

Open Closed Principle (OCP), terbuka untuk extension, namun tertutup untuk modification.

Berikut skenarionya, online store dengan list product yang dapat difilter. Misal filter berdasarkan warna, ukuran dan lainnya.

Pendekatan dibawah melanggar prinsip Open Closed Principle, karena kita terus melakukan modifikasi, ketika setiap diperlukan tipe filter baru.

Misal, pada awalnya hanya dibutuhkan filter berdasarkan warna, lalu kita buat code seperti dibawah.

package main

import "fmt"

type Color int

const (
	red Color = iota
	green
	blue
)

type Size int

const (
	small Size = iota
	medium
	large
)

type Product struct {
	name  string
	color Color
	size  Size
}

type Filter struct {
	//
}

func (f *Filter) filterByColor(products []Product, color Color) []*Product {
	result := make([]*Product, 0)
	for i, v := range products {
		if v.color == color {
			result = append(result, &products[i])
		}
	}
	return result
}

func main() {
	buah := Product{"Apel", green, small}
	motor := Product{"Vario", green, large}
	mobil := Product{"Mercy", red, large}

	products := []Product{buah, motor, mobil}

	fmt.Print("Green products (old):\n")
	f := Filter{}
	for _, v := range f.filterByColor(products, green) {
		fmt.Printf(" - %s is green\n", v.name)
	}
}

Kemudian, ternyata dibutuhkan filter berdasarkan ukuran. Kita ubah kode menjadi seperti berikut

//....
//code sengaja dibuang untuk menghemat space
//....

type Filter struct {
	//
}

func (f *Filter) filterByColor(products []Product, color Color) []*Product {
	result := make([]*Product, 0)
	for i, v := range products {
		if v.color == color {
			result = append(result, &products[i])
		}
	}
	return result
}

func (f *Filter) filterBySize(products []Product, size Size) []*Product {
	result := make([]*Product, 0)

	for i, v := range products {
		if v.size == size {
			result = append(result, &products[i])
		}
	}

	return result
}

func main() {
	buah := Product{"Apel", green, small}
	motor := Product{"Vario", green, large}
	mobil := Product{"Mercy", red, large}

	products := []Product{buah, motor, mobil}

	fmt.Print("Green products (old):\n")
	f := Filter{}
	for _, v := range f.filterByColor(products, green) {
		fmt.Printf(" - %s is green\n", v.name)
	}

	fmt.Print("Large products (old):\n")
	f = Filter{}
	for _, v := range f.filterBySize(products, large) {
		fmt.Printf(" - %s is large\n", v.name)
	}
}

Pendekatan diatas tidak salah, namun setiap ada tipe filter baru, kita harus melakukan modifikasi untuk menambahkan fungsi filter.

Jika ini digunakan sebagai API, kebayangkan, kode dibagian frontend akan menjadi acak-acakan, karena harus memanggil fungsi filter yang berbeda-beda.

Contoh Code OCP

Oleh karena itu kita gunakan pendekatan OCP yang dapat dicapai dengan menggunakan Specification Pattern.

Pertama kita buat interface untuk menentukan Specification. Kemudian buat struct spesfikasi yang perlukan, contoh pada code adalah ColorSpecification.

Kemudian buat method untuk struct ColorSpecification.

type Specification interface {
	IsSatisfied(p *Product) bool
}

type ColorSpecification struct {
	color Color
}

func (spec ColorSpecification) IsSatisfied(p *Product) bool {
	return p.color == spec.color
}

Kemudian buat struct serta method untuk melakukan filter.

type BetterFilter struct{}

func (f *BetterFilter) Filter(
	products []Product, spec Specification) []*Product {
	result := make([]*Product, 0)
	for i, v := range products {
		if spec.IsSatisfied(&v) {
			result = append(result, &products[i])
		}
	}
	return result
}

Dengan kode diatas, kita cukup membuat spesifikasi filter baru, tanpa harus mengubah method Filter.

Seperti contoh kode berikut, kita cukup membuat greenSpec jika ingin filter berdasarkan warna green.

fmt.Print("Green products (new):\n")
greenSpec := ColorSpecification{green}
bf := BetterFilter{}
for _, v := range bf.Filter(products, greenSpec) {
	fmt.Printf(" - %s is green\n", v.name)
}

Berikut kami sertakan kode lengkap baik untuk cara lama yang kurang disarankan (dalam bentuk comment) dan kode yang sesuai prinsip OCP.

Pada kode juga sudah ditambahkan cara melakukan filtering dengan 2 spesifikasi.

package main

import "fmt"

type Color int

const (
	red Color = iota
	green
	blue
)

type Size int

const (
	small Size = iota
	medium
	large
)

type Product struct {
	name  string
	color Color
	size  Size
}

//Cara yang tidak disarankan

// type Filter struct {
// 	//
// }

// func (f *Filter) filterByColor(products []Product, color Color) []*Product {
// 	result := make([]*Product, 0)
// 	for i, v := range products {
// 		if v.color == color {
// 			result = append(result, &products[i])
// 		}
// 	}
// 	return result
// }

// func (f *Filter) filterBySize(products []Product, size Size) []*Product {
// 	result := make([]*Product, 0)

// 	for i, v := range products {
// 		if v.size == size {
// 			result = append(result, &products[i])
// 		}
// 	}

// 	return result
// }

//OCP
type Specification interface {
	IsSatisfied(p *Product) bool
}

type ColorSpecification struct {
	color Color
}

func (spec ColorSpecification) IsSatisfied(p *Product) bool {
	return p.color == spec.color
}

type SizeSpecification struct {
	size Size
}

func (spec SizeSpecification) IsSatisfied(p *Product) bool {
	return p.size == spec.size
}

type AndSpecification struct {
	first, second Specification
}

func (spec AndSpecification) IsSatisfied(p *Product) bool {
	return spec.first.IsSatisfied(p) &&
		spec.second.IsSatisfied(p)
}

type BetterFilter struct{}

func (f *BetterFilter) Filter(
	products []Product, spec Specification) []*Product {
	result := make([]*Product, 0)
	for i, v := range products {
		if spec.IsSatisfied(&v) {
			result = append(result, &products[i])
		}
	}
	return result
}

func main() {
	buah := Product{"Apel", green, small}
	motor := Product{"Vario", green, large}
	mobil := Product{"Mercy", red, large}

	products := []Product{buah, motor, mobil}

	// Cara yang Tidak disarankan

	// fmt.Print("Green products (old):\n")
	// f := Filter{}
	// for _, v := range f.filterByColor(products, green) {
	// 	fmt.Printf(" - %s is green\n", v.name)
	// }

	// fmt.Print("Large products (old):\n")
	// f = Filter{}
	// for _, v := range f.filterBySize(products, large) {
	// 	fmt.Printf(" - %s is large\n", v.name)
	// }

	//menggunakan prinsip OCP

	fmt.Print("Green products (new):\n")
	greenSpec := ColorSpecification{green}
	bf := BetterFilter{}
	for _, v := range bf.Filter(products, greenSpec) {
		fmt.Printf(" - %s is green\n", v.name)
	}

	largeSpec := SizeSpecification{large}
	fmt.Print("Large items:\n")
	for _, v := range bf.Filter(products, largeSpec) {
		fmt.Printf(" - %s is large \n", v.name)
	}

	greenLargeSpec := AndSpecification{greenSpec, largeSpec}
	fmt.Print("Large blue items:\n")
	for _, v := range bf.Filter(products, greenLargeSpec) {
		fmt.Printf(" - %s is large and green\n", v.name)
	}
}
Sharing is caring:

Leave a Comment