手撸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_patternsimport (    "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)PASSok      command-line-arguments  0.002s

IDao.go

定义DAO对象的查问接口

package templateimport "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 templateimport (    "database/sql"    "errors")type FNBeforeQuery func() errortype FNScanRow func(rows *sql.Rows) errortype 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 templatetype UserInfo struct {    ID int    Name string    Pwd string    OrgID int    RoleID string}func NewUserInfo() *UserInfo {    return &UserInfo{        0, "", "", 0, "",    }}

IUserDAO.go

定义强类型的查问接口 - 用户信息查问

package templateimport "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 templateimport (    "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)