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"

  1. 高層次的模組不應該依賴於低層次的模組,兩者都應該依賴於抽象介面。
  2. 抽象介面不應該依賴於具體實現。而具體實現則應該依賴於抽象介面。

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

Reference

© Kimi Tsai all right reserved.            Updated : 2023-07-12 09:04:53

results matching ""

    No results matching ""

    results matching ""

      No results matching ""