缘起
最近复习设计模式
拜读谭勇德的<<设计模式就该这样学>>
本系列笔记拟采用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_reusetype Product struct {ID intName stringPrice float64}func NewProduct(id int, name string, price float64) *Product {return &Product{id, name, price,}}
BadDBConnection.go
BadDBConnection用于连接数据库并执行SQL语句
package composite_reuseimport "fmt"type BadDBConnection struct {sURL stringsUID stringsPWD 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_reusetype 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_reusetype IGoodDBConnection interface {Execute(sql string, args... interface{}) (error, int)}
GoodMysqlConnection.go
更好的设计, GoodMysqlConnection封装MYSQL数据库方言, 实现IGoodDBConnection接口
package composite_reuseimport "fmt"type GoodMysqlConnection struct {sURL stringsUID stringsPWD 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_reusetype 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 mainimport "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_CARPBadDBConnection.Execute, sql=insert into product(id,name,price) values(?, ?, ?), args=[1 手机 1000]composite_reuse_test.go:13: affected rows = 0BadDBConnection.Execute, sql=update product set name=? price=? where id=?, args=[手机 1000 1]composite_reuse_test.go:13: affected rows = 0BadDBConnection.Execute, sql=delete from product where id=?, args=[1]composite_reuse_test.go:13: affected rows = 0GoodMysqlConnection.Execute, sql=insert into product(id,name,price) values(?, ?, ?), args=[1 手机 1000]composite_reuse_test.go:13: affected rows = 0GoodMysqlConnection.Execute, sql=update product set name=? price=? where id=?, args=[手机 1000 1]composite_reuse_test.go:13: affected rows = 0GoodMysqlConnection.Execute, sql=delete from product where id=?, args=[1]composite_reuse_test.go:13: affected rows = 0--- PASS: Test_CARP (0.00s)PASSok command-line-arguments 0.004s
