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

Posted ioly

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了手撸golang 架构设计原则 合成复用原则相关的知识,希望对你有一定的参考价值。

手撸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

以上是关于手撸golang 架构设计原则 合成复用原则的主要内容,如果未能解决你的问题,请参考以下文章

手撸golang 架构设计原则 依赖倒置原则

手撸golang 架构设计原则 接口隔离原则

手撸golang 架构设计原则 里氏替换原则

手撸golang 架构设计原则 开闭原则

手撸golang 架构设计原则 迪米特法则

设计模式软件设计七大原则 ( 合成复用原则 | 代码示例 )