10

手撸golang 架构设计原则 合成复用原则

 3 years ago
source link: https://studygolang.com/articles/33106
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

手撸golang 架构设计原则 合成复用原则

缘起

最近复习设计模式

拜读谭勇德的<<设计模式就该这样学>>

该书以java语言演绎了常见设计模式

本系列笔记拟采用golang练习之

合成复用原则

  • 合成复用原则(Composite/Aggregate Reuse Principle, CARP)指尽量使用对象组合(has-a)或对象聚合(contanis-a)的方式实现代码复用,而不是用继承关系达到代码复用的目的。合成复用原则可以使系统更加灵活,降低类与类之间的耦合度,一个类的变化对其他类造成的影响相对较小。
  • 继承,又被称为白箱复用,相当于把所有实现细节暴露给子类。组合/聚合又被称为黑箱复用,对类以外的对象是无法获取实现细节的。

_

场景

  • 某订单业务系统, 需要连接数据库对产品信息进行CRUD操作
  • 不好的设计:

    • 定义DBConnection类, 实现对数据库的连接和SQL执行
    • 定义ProductDAO类, 继承DBConnection类, 并封装对产品资料的增删改查
    • 问题: ProductDAO对DBConnection的继承仅仅是为了代码复用, 不符合合成复用原则
  • 更好的设计:

    • 定义IDBConnection接口
    • 定义MysqlConnection类, 实现对mysql数据库的连接和SQL执行
    • 定义ProductDAO类, 通过Setter方法注入IDBConnection实例

_

Product.go

产品实体类

package composite_reuse

type Product struct {
    ID int
    Name string
    Price float64
}

func NewProduct(id int, name string, price float64) *Product {
    return ∏{
        id, name, price,
    }
}

_

BadDBConnection.go

BadDBConnection用于连接数据库并执行SQL语句

package composite_reuse

import "fmt"

type BadDBConnection struct {
    sURL string
    sUID string
    sPWD string
}

func NewBadDBConnection(url string, uid string, pwd string) *BadDBConnection {
    return &BadDBConnection{
        url, uid, pwd,
    }
}

func (me *BadDBConnection) Execute(sql string, args... interface{}) (error, int) {
    fmt.Printf("BadDBConnection.Execute, sql=%v, args=%v\n", sql, args)
    return nil,0
}

BadProductDAO.go

不好的设计. 直接从BadDBConnection继承, 以获取访问数据库的能力. 继承仅仅是为了代码复用, 而不是概念复用, 因此违反了合成复用原则.

package composite_reuse

type BadProductDAO struct {
    BadDBConnection
}

func NewBadProductDAO(url string, uid string, pwd string) *BadProductDAO {
    return &BadProductDAO{
        *NewBadDBConnection(url, uid, pwd),
    }
}

func (me *BadProductDAO) Insert(it *Product) (error, int) {
    return me.Execute("insert into product(id,name,price) values(?, ?, ?)", it.ID, it.Name, it.Price)
}

func (me *BadProductDAO) Update(it *Product) (error, int) {
    return me.Execute("update product set name=? price=? where id=?", it.Name, it.Price, it.ID)
}

func (me *BadProductDAO) Delete(id int) (error, int) {
    return me.Execute("delete from product where id=?", id)
}

IGoodDBConnection.go

更好的设计, 将数据库连接抽象为接口, 以支持多种数据库

package composite_reuse

type IGoodDBConnection interface {
    Execute(sql string, args... interface{}) (error, int)
}

GoodMysqlConnection.go

更好的设计, GoodMysqlConnection封装MYSQL数据库方言, 实现IGoodDBConnection接口

package composite_reuse

import "fmt"

type GoodMysqlConnection struct {
    sURL string
    sUID string
    sPWD string
}

func NewGoodMysqlConnection(url string, uid string, pwd string) IGoodDBConnection {
    return &GoodMysqlConnection{
        url, uid, pwd,
    }
}

func (me *GoodMysqlConnection) Execute(sql string, args... interface{}) (error, int) {
    fmt.Printf("GoodMysqlConnection.Execute, sql=%v, args=%v\n", sql, args)
    return nil, 0
}

GoodProductDAO.go

更好的设计, 通过Setter方法注入数据库方言实例(遵循了合成复用原则), 实现产品的CRUD

package composite_reuse


type GoodProductDAO struct {
    mDBConnection IGoodDBConnection
}

func NewGoodProductDAO() *GoodProductDAO {
    return &GoodProductDAO{}
}

func (me *GoodProductDAO) SetDBConnection(it IGoodDBConnection) {
    me.mDBConnection = it
}

func (me *GoodProductDAO) Insert(it *Product) (error, int) {
    return me.mDBConnection.Execute("insert into product(id,name,price) values(?, ?, ?)", it.ID, it.Name, it.Price)
}

func (me *GoodProductDAO) Update(it *Product) (error, int) {
    return me.mDBConnection.Execute("update product set name=? price=? where id=?", it.Name, it.Price, it.ID)
}

func (me *GoodProductDAO) Delete(id int) (error, int) {
    return me.mDBConnection.Execute("delete from product where id=?", id)
}

composite_reuse_test.go

单元测试

package main

import "testing"
import (carp "learning/gooop/principles/composite_reuse")

func Test_CARP(t *testing.T) {
    p := carp.NewProduct(1, "手机", 1000)
    fnCallAndLog := func(fn func() (error, int)) {
        e,rows := fn()
        if e != nil {
            t.Errorf("error = %s", e.Error())
        } else {
            t.Logf("affected rows = %v", rows)
        }
    }

    // begin testing bad //////////////////////////////////////////////////////////////
    bd := carp.NewBadProductDAO("database connection url", "sa", "123")
    fnCallAndLog(func() (error, int) {
        return bd.Insert(p)
    })
    fnCallAndLog(func() (error, int) {
        return bd.Update(p)
    })
    fnCallAndLog(func() (error, int) {
        return bd.Delete(p.ID)
    })
    // end testing bad //////////////////////////////////////////////////////////////


    // begin testing good //////////////////////////////////////////////////////////////
    con := carp.NewGoodMysqlConnection("database connection url", "sa", "123")
    gd := carp.NewGoodProductDAO()
    gd.SetDBConnection(con)

    fnCallAndLog(func() (error, int) {
        return gd.Insert(p)
    })
    fnCallAndLog(func() (error, int) {
        return gd.Update(p)
    })
    fnCallAndLog(func() (error, int) {
        return gd.Delete(p.ID)
    })
    // end testing good //////////////////////////////////////////////////////////////
}

测试输出

$ go test -v composite_reuse_test.go 
=== RUN   Test_CARP
BadDBConnection.Execute, sql=insert into product(id,name,price) values(?, ?, ?), args=[1 手机 1000]
    composite_reuse_test.go:13: affected rows = 0
BadDBConnection.Execute, sql=update product set name=? price=? where id=?, args=[手机 1000 1]
    composite_reuse_test.go:13: affected rows = 0
BadDBConnection.Execute, sql=delete from product where id=?, args=[1]
    composite_reuse_test.go:13: affected rows = 0
GoodMysqlConnection.Execute, sql=insert into product(id,name,price) values(?, ?, ?), args=[1 手机 1000]
    composite_reuse_test.go:13: affected rows = 0
GoodMysqlConnection.Execute, sql=update product set name=? price=? where id=?, args=[手机 1000 1]
    composite_reuse_test.go:13: affected rows = 0
GoodMysqlConnection.Execute, sql=delete from product where id=?, args=[1]
    composite_reuse_test.go:13: affected rows = 0
--- PASS: Test_CARP (0.00s)
PASS
ok      command-line-arguments  0.004s

有疑问加站长微信联系(非本文作者)

eUjI7rn.png!mobile

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK