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)
}
}