SOLID: Liskov Substitution Principle

Liskov Substitution Principle (LSP) adalah fokus pada inheritance, dimana objects dari superclass harus bisa direplace pada object subclasses tanpa menyebabkan masalah. Jadi objects dari subclasses harus memiliki behaviour yang sama dengan superclass.

Aturan LSP ini tidak cocok diaplikasikan pada Golang, karena Golang tidak memiliki base class dan derived class (turunan).

Kita akan coba simulasikan LSP dengan menggunakan object interface.

Contoh kasus, kita akan bekerja dengan geometri Rectangle, untuk itu dibuat interface yang berisi operasi seperti getter dan setter.

Kemudian kita buat fungsi helper UseIt, yang berguna untuk menampilkan ukuran ekspektasi dan ukuran aktual rectangle ke console. Fungsi ini nanti akan menunjukan masalah yang terjadi saat LSP dilanggar oleh type Square.

package main

import "fmt"

type Sized interface {
	GetWidth() int
	SetWidth(width int)
	GetHeight() int
	SetHeight(height int)
}

type Rectangle struct {
	width, height int
}

func (r *Rectangle) GetWidth() int {
	return r.width
}

func (r *Rectangle) SetWidth(width int) {
	r.width = width
}

func (r *Rectangle) GetHeight() int {
	return r.height
}

func (r *Rectangle) SetHeight(height int) {
	r.height = height
}

func UseIt(sized Sized) {
	width := sized.GetWidth()
	sized.SetHeight(10)
	expectedArea := 10 * width
	actualArea := sized.GetWidth() * sized.GetHeight()
	fmt.Print("Expected an area of ", expectedArea,
		", but got ", actualArea, "\n")
}

func main() {
	rc := &Rectangle{2, 3}
	UseIt(rc)
}

Sampai disini, tidak ada masalah, jika dijalankan akan menampilkan luas dari rectangle, dimana ukuran ekspektasi dan hasil perhitungan adalah sama.

Expected an area of 20, but got 20

Berikut masalah yang sengaja dibuat untuk menjelaskan LSP.

Misalnya dibuat Object Square. Karena Square adalah Rectangle dengan panjang sisi yang sama. Kita coba paksakan akan nilai panjang dan lebar menjadi sama dengan memodifikasi method dari Interface Sized.

package main

import "fmt"

type Sized interface {
	GetWidth() int
	SetWidth(width int)
	GetHeight() int
	SetHeight(height int)
}

type Rectangle struct {
	width, height int
}

func (r *Rectangle) GetWidth() int {
	return r.width
}

func (r *Rectangle) SetWidth(width int) {
	r.width = width
}

func (r *Rectangle) GetHeight() int {
	return r.height
}

func (r *Rectangle) SetHeight(height int) {
	r.height = height
}

type Square struct {
	Rectangle
}

func NewSquare(size int) *Square {
	sq := Square{}
	sq.width = size
	sq.height = size
	return &sq
}

// melanggar aturan LSP
func (s *Square) SetWidth(width int) {
	s.width = width
	s.height = width
}

// melanggar aturan LSP
func (s *Square) SetHeight(height int) {
	s.width = height
	s.height = height
}

func UseIt(sized Sized) {
	width := sized.GetWidth()
	sized.SetHeight(10)
	expectedArea := 10 * width
	actualArea := sized.GetWidth() * sized.GetHeight()
	fmt.Print("Expected an area of ", expectedArea,
		", but got ", actualArea, "\n")
}

func main() {
	rc := &Rectangle{2, 3}
	UseIt(rc)

	sq := NewSquare(5)
	UseIt(sq)
}

Jika dijalankan, akan terjadi logic error karena pada fungsi UseIt, terdapat code untuk SetHeight, karena pada implementasi setHeight dari Square akan mengubah height dan width secara bersamaan.

Expected an area of 20, but got 20
Expected an area of 50, but got 100

Jadi secara sederhana, LSP adalah, jika kita membuat implementasi secara general (pada contoh kasus diatas pada Interface), kita tidak boleh membuat implementasi baru yang merusak aturan general yang telah ditetapkan oleh interface.

Pada contoh diatas, implementasi setWidth dan setHeight dari Square berusaha mengubah width dan height menjadi sama.

Sementara pada interface Size, width hanya diubah oleh setWidth, dan height hanya akan diubah oleh setHeight.

Sharing is caring:

Leave a Comment