Note : SOLID in Go
Source from : https://towardsdev.com/golang-solid-principles-fd7bf513874d
1. Single responsability 單一職責
— Definition: "A module should have one, and only one reason to change"
Bad
package main
import (
"fmt"
"math"
)
type circle struct {
radius float32
}
func (c circle) area() {
fmt.Printf("circle area: %f\n", math.Pi*c.radius*c.radius)
}
type square struct {
sideLen float32
}
func (s square) area() {
fmt.Printf("square area: %f\n", s.sideLen*s.sideLen)
}
func main() {
c := circle{radius: 5}
c.area()
s := square{sideLen: 2}
s.area()
}
good
package main
import (
"fmt"
"math"
)
type shape interface {
area() float32
}
type outPrinter struct{}
func (ou outPrinter) toText(s shape) string {
return fmt.Sprintf("the area is : %f", s.area())
}
type circle struct {
radius float32
}
func (c circle) area() float32 {
return math.Pi * c.radius * c.radius
}
type square struct {
sideLen float32
}
func (s square) area() float32 {
return s.sideLen * s.sideLen
}
func main() {
c := circle{radius: 5}
// c.area()
s := square{sideLen: 2}
// s.area()
out := outPrinter{}
fmt.Println(out.toText(c))
fmt.Println(out.toText(s))
}
圖形定義了算面積的操作, 而不做印出字錯 類型 outPrinter 將定義所有需要生成所需的字符串輸出
2. Open-Close principle 開放關閉原則
— Definition: "A software artifact should be open for extension but closed for modifications"
Bad
package main
import (
"fmt"
"math"
)
type circle struct {
radius float32
}
type square struct {
sideLen float32
}
type calculator struct {
total float32
}
func (c calculator) sumAreas(shapes ...interface{}) float32 {
var sum float32
for _, shape := range shapes {
switch shape.(type) {
case circle:
r := shape.(circle).radius
sum += math.Pi * r * r
case square:
l := shape.(square).sideLen
sum += l * l
}
}
return sum
}
func main() {
c := circle{radius: 5}
s := square{sideLen: 2}
calc := calculator{}
fmt.Println("total of areas", calc.sumAreas(c, s))
}
Good
package main
import (
"fmt"
"math"
)
type shape interface {
area() float32
}
type circle struct {
radius float32
}
func (c circle) area() float32 {
return math.Pi * c.radius * c.radius
}
type square struct {
sideLen float32
}
func (s square) area() float32 {
return s.sideLen * s.sideLen
}
type calculator struct {
total float32
}
func (c calculator) sumAreas(shapes ...shape) float32 {
var sum float32
for _, shape := range shapes {
sum += shape.area()
}
return sum
}
func main() {
c := circle{radius: 5}
s := square{sideLen: 2}
calc := calculator{}
fmt.Println("total of areas", calc.sumAreas(c, s))
}
calculator type 的 sumAreas 方法, 他參數定義了 shapes 的類型為 interface{}.
此時他用switch來映射每一個可能的類型, 這邊就會出問題了, 假如出現了新的一個形狀, 譬如三角形
此事就緒要修改 sumAreas方法來處理新類型
3. Liskov substitution principle 里氏替換原則
— Definition: "What is wanted here is something like the following substitution property: If for each object o1 of type S there is an object o2 of type T such that for all programs P defined in terms of T, the behavior of P is unchanged when o1 is substituted for o2 then S is a subtype of T"
若對型態 S 的每一個物件 o1,都存在一個型態為 T 的物件 o2,使得在所有針對 T 編寫的程式 P 中,用 o1 替換 o2後,程式 P 的行為功能不變,則 S 是 T 的子型態。 子型態必須遵從父型態的行為進行設計
source: https://towardsdev.com/golang-solid-principles-fd7bf513874d
package main
import "fmt"
type transport interface {
getName() string
}
type vehicle struct {
name string
}
func (v vehicle) getName() string {
return v.name
}
type car struct {
vehicle
wheel int
gates int
}
type motocycle struct {
vehicle
wheel int
}
type printer struct {
}
func (printer) printTransportName(p transport) {
fmt.Println("Name:", p.getName())
}
func main() {
v := vehicle{name: "Ford"}
c := car{
vehicle: vehicle{name: "car-name"},
wheel: 4,
gates: 2,
}
m := motocycle{
vehicle: vehicle{name: "motocycle-name"},
wheel: 2,
}
p := printer{}
p.printTransportName(v)
p.printTransportName(c)
p.printTransportName(m)
}
4. Interface segregation 介面隔離原則
— Definition: "Clients should not be forced to depend on methods they don't use" 你不應該去依賴你根本不會用到的東西, “Keep interfaces simple, preferable just one method”
Bad
type sahape interface {
area() float64
volume() float64
}
func (s square) area() float64 {
return s.sideLen * s.sideLen
}
func (s square) volume() float64 {
return 0
}
type cube struct {
sideLen float64
}
func (c cube) area() float64 {
return math. Pow(c. sideLen, 2)
}
func (c cube) volume() float64 {
return math. Pow( c. sideLen, 3)
}
// sum the shapes areas
func areaSum (shapes ...shape) float64 {
var sum float64
for _, s := range shapes {
sum += s.area ( )
}
return sum
}
// areaVolumeSum sum the shapes volumes
func areaVolumeSum(shapes ... shape) float64 {
var sum float64
for _, s := range shapes {
sum += s.area() + s.volume()
}
return sum
}
這段程式有個問題 Square 不需要 volume function, 只有cube需要volume function
Good
type shape interface {
area float64
}
type object interface {
shape
volume ( ) float64
}
type square struct {
sideLen float64
}
func (s square) area() float64 {
return math.Pow(s.sideLen, 2)
}
type cube struct {
square
}
func (c cube) volume() float64 {
return math. Pow(c. sideLen,3)
}
// sum the shapes areas
func areaSum (shapes ...shape) float64 {
var sum float64
for _, s := range shapes {
sum += s.area ( )
}
return sum
}
// areaVolumeSum sum the shapes volumes
func areaVolumeSum(shapes ... object) float64 {
var sum float64
for _, s := range shapes {
sum += s.area() + s.volume()
}
return sum
}
5. Dependency inversion 依賴反轉原則
— Definition: "High-level modules should not depend on low-level modules. Both should depend on abstractions. Abstractions should not depend on details. Details should not depend on abstractions"
- 高層次的模組不應該依賴於低層次的模組,兩者都應該依賴於抽象介面。
- 抽象介面不應該依賴於具體實現。而具體實現則應該依賴於抽象介面。
Bad
該代碼不符合該原則,因為它在MyRepository中定義了 access the query method 的結構類型.
type MyRepository struct {db MySQL}
package main
import "fmt"
// Defines the database connection
type MySQL struct {
// some properties here
}
func (db MySQL) QuerySomeDate() []string {
return []string{"info1", "info2", "info3"}
}
type MyRepository struct {
db MySQL
}
func (r MyRepository) GetData() []string {
return r.db.QuerySomeDate()
}
func main() {
mysqlDB := MySQL{}
repo := MyRepository{db: mysqlDB}
fmt.Println(repo.GetData())
}
Good
package main
import "fmt"
type DBConn interface {
QuerySomeDate() []string
}
// Defines the database connection
type MySQL struct {
// some properties here
}
func (db MySQL) QuerySomeDate() []string {
return []string{"info1", "info2", "info3"}
}
type MyRepository struct {
db DBConn
}
func (r MyRepository) GetData() []string {
return r.db.QuerySomeDate()
}
func main() {
mysqlDB := MySQL{}
repo := MyRepository{db: mysqlDB}
fmt.Println(repo.GetData())
}
