手撸 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 &Product{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