共计 4817 个字符,预计需要花费 13 分钟才能阅读完成。
手撸 golang 行为型设计模式 模板办法模式
缘起
最近温习设计模式
拜读谭勇德的 << 设计模式就该这样学 >>
本系列笔记拟采纳 golang 练习之
模板办法模式
模板办法模式(Template Method Pattern)又叫作模板模式,指定义一个操作中的算法的框架,而将一些步骤提早到子类中,使得子类能够不扭转一个算法的构造即可重定义该算法的某些特定步骤,属于行为型设计模式。模板办法模式次要蕴含 2 个角色。(1)形象模板(AbstractClass):形象模板类,定义了一套算法框架 / 流程。(2)具体实现(ConcreteClass):具体实现类,对算法框架 / 流程的某些步骤进行了实现。(摘自 谭勇德 << 设计模式就该这样学 >>)
_
场景
- 某业务零碎, 数据拜访层须要编写大量 DAO 代码
- 数据查问基本上遵循如下流程: 1- 执行 SQL, 2- 遍历后果集, 3- 将数据行映射为强类型对象
- 现依据 模板办法模式, 将步骤 1 和 2 的专用代码抽取到基类中, 子类仅须要实现后果映射办法即可
设计
- IDao: 定义 DAO 对象的查问接口
- tBaseDAO: 实现 IDao 接口, 依据 模板办法模式, 定义了查问数据的残缺流程, 并承受数据行映射函数注入
- UserInfo: 用户信息实体
- IUserDAO: 定义强类型的查问接口 – 用户信息查问
- UserDAO: 继承 tBaseDAO 并注入数据行映射函数, 同时实现 IUserDAO 接口, 提供用户信息查问性能
单元测试
template_pattern_test.go, 引入 sqlmock 虚构数据库连贯
package behavioral_patterns
import (
"github.com/DATA-DOG/go-sqlmock"
"learning/gooop/behavioral_patterns/template"
"strings"
"testing"
)
func Test_TemplatePattern(t *testing.T) {
// setup sqlmock ///////////////////////////////////////////
db, mock, err := sqlmock.New()
if err != nil {t.Fatalf("mock error:'%s'", err)
}
defer func() {_ = db.Close()
}()
// end setup sqlmock ///////////////////////////////////////////
// test UserDAO.GetUserByID ////////////////////////////////////
mock.ExpectQuery("select").WillReturnRows(
mock.
NewRows(strings.Split("id,name,pwd,org_id,role_id", ",")).
AddRow(1, "John", "abcdefg", 11, "guest"))
ud := template.NewUserDAO()
e, u := ud.GetUserByID(db, 1)
if e != nil {t.Error(e)
} else {t.Logf("user = %v", u)
}
// end test UserDAO.GetUserByID ////////////////////////////////
// test UserDAO.GetUsersByOrgID ///////////////////////////////
mock.ExpectQuery("select").WillReturnRows(
mock.
NewRows(strings.Split("id,name,pwd,org_id,role_id", ",")).
AddRow(1, "John", "abcdefg", 11, "guest").
AddRow(2, "Mike", "aaaaaa", 11, "admin"))
e,ul := ud.GetUsersByOrgID(db, 11)
if e != nil {t.Error(e)
} else {
for i,it := range ul {t.Logf("users[%d] = %v", i, it)
}
}
// end test UserDAO.GetUsersByOrgID ///////////////////////////
}
测试输入
$ go test -v template_pattern_test.go
=== RUN Test_TemplatePattern
template_pattern_test.go:37: user = &{1 John abcdefg 11 guest}
template_pattern_test.go:53: users[0] = &{1 John abcdefg 11 guest}
template_pattern_test.go:53: users[1] = &{2 Mike aaaaaa 11 admin}
--- PASS: Test_TemplatePattern (0.00s)
PASS
ok command-line-arguments 0.002s
IDao.go
定义 DAO 对象的查问接口
package template
import "database/sql"
type IDao interface {QueryOne(db *sql.DB, sql string, args... interface{}) error
QueryMulti(db *sql.DB, sql string, args... interface{}) error
}
tBaseDAO.go
实现 IDao 接口, 依据 模板办法模式, 定义了查问数据的残缺流程, 并承受数据行映射函数注入
package template
import (
"database/sql"
"errors"
)
type FNBeforeQuery func() error
type FNScanRow func(rows *sql.Rows) error
type tBaseDAO struct {
fnBeforeQuery FNBeforeQuery
fnScanRow FNScanRow
}
func newBaseDAO(fq FNBeforeQuery, fs FNScanRow) *tBaseDAO {
return &tBaseDAO{
fnBeforeQuery: fq,
fnScanRow: fs,
}
}
func (me *tBaseDAO) QueryOne(db *sql.DB, sql string, args... interface{}) error {
if me.fnScanRow == nil {return errors.New("tBaseDAO.fnScanRow is nil")
}
if me.fnBeforeQuery != nil {e := me.fnBeforeQuery()
if e != nil {return e}
}
rows, e := db.Query(sql, args...)
defer func() {
if rows != nil {_ = rows.Close()
}
}()
if e != nil {return e}
if rows.Next() {return me.fnScanRow(rows)
} else {return errors.New("no rows found")
}
}
func (me *tBaseDAO) QueryMulti(db *sql.DB, sql string, args... interface{}) error {
if me.fnScanRow == nil {return errors.New("tBaseDAO.fnScanRow is nil")
}
if me.fnBeforeQuery != nil {e := me.fnBeforeQuery()
if e != nil {return e}
}
rows, e := db.Query(sql, args...)
defer func() {
if rows != nil {_ = rows.Close()
}
}()
if e != nil {return e}
for rows.Next() {e = me.fnScanRow(rows)
if e != nil {return e}
}
return nil
}
UserInfo.go
用户信息实体
package template
type UserInfo struct {
ID int
Name string
Pwd string
OrgID int
RoleID string
}
func NewUserInfo() *UserInfo {
return &UserInfo{0, "","", 0, "",}
}
IUserDAO.go
定义强类型的查问接口 – 用户信息查问
package template
import "database/sql"
type IUserDAO interface {GetUserByID(db *sql.DB, id int) (error, *UserInfo)
GetUsersByOrgID(db *sql.DB, orgID int) (error, []*UserInfo)
}
UserDAO.go
继承 tBaseDAO 并注入数据行映射函数, 同时实现 IUserDAO 接口, 提供用户信息查问性能
package template
import ("database/sql")
type UserDAO struct {
tBaseDAO
mItems []*UserInfo}
func NewUserDAO() IUserDAO {it := &UserDAO{ *newBaseDAO(nil, nil), nil }
it.fnBeforeQuery = it.BeforeQuery
it.fnScanRow = it.ScanRow
return it
}
func (me *UserDAO) BeforeQuery() error {me.mItems = make([]*UserInfo, 0)
return nil
}
func (me *UserDAO) ScanRow(rows *sql.Rows) error {user := NewUserInfo()
e := rows.Scan(&user.ID, &user.Name, &user.Pwd, &user.OrgID, &user.RoleID)
if e != nil {return e}
me.mItems = append(me.mItems, user)
return nil
}
func (me *UserDAO) GetUserByID(db *sql.DB, id int) (error, *UserInfo) {e := me.QueryOne(db, "select id,name,pwd,org_id,role_id from user_info where id=?", id)
if e != nil {return e, nil}
return nil, me.mItems[0]
}
func (me *UserDAO) GetUsersByOrgID(db *sql.DB, orgID int) (error, []*UserInfo) {e := me.QueryMulti(db, "select id,name,pwd,org_id,role_id from user_info where org_id=?", orgID)
if e != nil {return e, nil}
return nil, me.mItems
}
模板办法小结
模板办法模式的长处(1)利用模板办法将雷同解决逻辑的代码放到形象父类中,能够进步代码的复用性。(2)将不同的算法逻辑拆散到不同的子类中,通过对子类的扩大减少新的行为,进步代码的可扩展性。(3)把不变的行为写在父类上,去除子类的反复代码,提供了一个很好的代码复用平台,合乎开闭准则。模板办法模式的毛病(1)每一个抽象类都须要一个子类来实现,这样导致类数量减少。(2)类数量的减少,间接地减少了零碎实现的复杂度。(3)因为继承关系本身的毛病,如果父类增加新的形象办法,则所有子类都要改一遍。(摘自 谭勇德 << 设计模式就该这样学 >>)
(end)
正文完