手撸golang 结构型设计模式 桥接模式

缘起

最近温习设计模式
拜读谭勇德的<<设计模式就该这样学>>
本系列笔记拟采纳golang练习之

桥接模式

桥接模式(Bridge Pattern)又叫作桥梁模式、接口(Interface)模式或柄体(Handle and Body)模式,指将形象局部与具体实现局部拆散,使它们都能够独立地变动,属于结构型设计模式。
桥接模式实用于以下几种业务场景。
(1)在形象和具体实现之间须要减少更多灵活性的场景。
(2)一个类存在两个(或多个)独立变动的维度,而这两个(或多个)维度都须要独立进行扩大。
(3)不心愿应用继承,或因为多层继承导致系统类的个数剧增。
_

场景

  • 某业务零碎, 现须要开发数据库导出工具, 依据SQL语句导出表数据到文件
  • 数据库类型有多种, 目前须要反对mysql, oracle
  • 导出格局可能有多种, 目前须要反对csv和json格局
  • 此场景下, 数据库类型是一种维度, 导出格局是另一种维度, 组合可能性是乘法关系
  • 应用桥接模式, 将"导出工具"拆散出"数据抓取"和"数据导出"两个维度, 以便扩大, 并缩小类数目

设计

  • DBConfig: 定义数据库连贯配置信息
  • DataRow: 示意导出数据行的两头后果
  • DataField: 示意导出数据行的某个字段
  • IDataFetcher: 数据抓取器接口, 执行SQL语句并转为数据行的汇合
  • MysqlDataFetcher: MYSQL数据抓取器, 实现IDataFetcher接口
  • OracleDataFetcher: Oracle数据抓取器, 实现IDataFetcher接口
  • IDataExporter: 数据导出器接口, 承受IDataFetcher注入, 并导出指定格局的数据
  • CsvExporter: CSV数据导出器, 实现IDataExporter接口, 导出csv格式文件
  • JsonExporter: JSON数据导出器, 实现IDataExporter接口, 导出json格式文件

单元测试

bridge_pattern_test.go

package structural_patternsimport (    "bytes"    "learning/gooop/structural_patterns/bridge"    "testing")func Test_BridgePattern(t *testing.T) {    config := bridge.NewDBConfig("mysql", "root:pass@tcp(localhost:3306)/test?charset=utf8", "root", "pass")    fetcher := bridge.NewMysqlDataFetcher(config)    fnTestExporter := func(exporter bridge.IDataExporter) {        var writer bytes.Buffer        e := exporter.Export("select * from ims_stock", &writer)        if e != nil {            t.Error(e)        }    }    fnTestExporter(bridge.NewCsvExporter(fetcher))    fnTestExporter(bridge.NewJsonExporter(fetcher))}

测试输入

$ go test -v bridge_pattern_test.go === RUN   Test_BridgePatternCsvExporter.Export, got 1 rows  1 int-1=1, float-1=1.1, string-1="hello"JsonExporter.Export, got 1 rows  1 int-1=1, float-1=1.1, string-1="hello"--- PASS: Test_BridgePattern (0.00s)PASSok      command-line-arguments  0.001s

DBConfig.go

定义数据库连贯配置信息

package bridgetype DBConfig struct {    DBType string    URL string    UID string    PWD string}func NewDBConfig(dbType string, url string, uid string, pwd string) *DBConfig {    return &DBConfig{        DBType: dbType,        URL: url,        UID: uid,        PWD: pwd,    }}

DataRow.go

示意导出数据行的两头后果

package bridgeimport (    "fmt"    "strings")type DataRow struct {    FieldList []*DataField}func NewMockDataRow() *DataRow {    it := &DataRow{        make([]*DataField, 0),    }    it.FieldList = append(it.FieldList, NewMockDataField("int-1", DATA_TYPE_INT))    it.FieldList = append(it.FieldList, NewMockDataField("float-1", DATA_TYPE_FLOAT))    it.FieldList = append(it.FieldList, NewMockDataField("string-1", DATA_TYPE_STRING))    return it}func (me *DataRow) FieldsString() string {    lst := make([]string, 0)    for _,f := range me.FieldList {        lst = append(lst, fmt.Sprintf("%s=%s", f.Name, f.ValueString()))    }    return strings.Join(lst, ", ")}

DataField.go

示意导出数据行的某个字段

package bridgeimport (    "fmt"    "time")type DataTypes stringconst DATA_TYPE_INT = "int"const DATA_TYPE_FLOAT = "float"const DATA_TYPE_STRING = "string"const DATA_TYPE_BOOL = "bool"const DATA_TYPE_DATETIME = "datetime"type DataField struct {    Name string    DataType DataTypes    IntValue int    FloatValue float64    StringValue string    BoolValue bool    DateTimeValue *time.Time}func NewMockDataField(name string, dataType DataTypes) *DataField {    it := &DataField {        Name: name,        DataType: dataType,        IntValue: 0,        FloatValue: 0,        StringValue: "",        BoolValue: false,        DateTimeValue: nil,    }    switch dataType {    case DATA_TYPE_INT:        it.IntValue = 1        break    case DATA_TYPE_FLOAT:        it.FloatValue = 1.1        break    case DATA_TYPE_STRING:        it.StringValue = "hello"        break    case DATA_TYPE_DATETIME:        t := time.Now()        it.DateTimeValue = &t        break    case DATA_TYPE_BOOL:        it.BoolValue = false        break    }    return it}func (me *DataField) ValueString() string {    switch me.DataType {    case DATA_TYPE_INT:        return fmt.Sprintf("%v", me.IntValue)    case DATA_TYPE_FLOAT:        return fmt.Sprintf("%v", me.FloatValue)    case DATA_TYPE_STRING:        return fmt.Sprintf("\"%s\"", me.StringValue)    case DATA_TYPE_DATETIME:        return fmt.Sprintf("\"%s\"", me.DateTimeValue.Format("2006-01-02T15:04:05"))    case DATA_TYPE_BOOL:        return fmt.Sprintf("%v", me.BoolValue)    }    return ""}

IDataFetcher.go

数据抓取器接口, 执行SQL语句并转为数据行的汇合

package bridgetype IDataFetcher interface {    Fetch(sql string) []*DataRow}

MysqlDataFetcher.go

MYSQL数据抓取器, 实现IDataFetcher接口

package bridgetype MysqlDataFetcher struct {    Config *DBConfig}func NewMysqlDataFetcher(config *DBConfig) IDataFetcher {    return &MysqlDataFetcher{        config,    }}func (me *MysqlDataFetcher) Fetch(sql string) []*DataRow {    rows := make([]*DataRow, 0)    rows = append(rows, NewMockDataRow())    return rows}

OracleDataFetcher.go

Oracle数据抓取器, 实现IDataFetcher接口

package bridgetype OracleDataFetcher struct {    Config *DBConfig}func NewOracleDataFetcher(config *DBConfig) IDataFetcher {    return &OracleDataFetcher{        config,    }}func (me *OracleDataFetcher) Fetch(sql string) []*DataRow {    rows := make([]*DataRow, 0)    rows = append(rows, NewMockDataRow())    return rows}

IDataExporter.go

数据导出器接口, 承受IDataFetcher注入, 并导出指定格局的数据

package bridgeimport "io"type IDataExporter interface {    Fetcher(fetcher IDataFetcher)    Export(sql string, writer io.Writer) error}

CsvExporter.go

CSV数据导出器, 实现IDataExporter接口, 导出csv格式文件

package bridgeimport (    "fmt"    "io")type CsvExporter struct {    mFetcher IDataFetcher}func NewCsvExporter(fetcher IDataFetcher) IDataExporter {    return &CsvExporter{        fetcher,    }}func (me *CsvExporter) Fetcher(fetcher IDataFetcher) {    me.mFetcher = fetcher}func (me *CsvExporter) Export(sql string, writer io.Writer) error {    rows := me.mFetcher.Fetch(sql)    fmt.Printf("CsvExporter.Export, got %v rows\n", len(rows))    for i,it := range rows {        fmt.Printf("  %v %s\n", i + 1, it.FieldsString())    }    return nil}

JsonExporter.go

JSON数据导出器, 实现IDataExporter接口, 导出json格式文件

package bridgeimport (    "fmt"    "io")type JsonExporter struct {    mFetcher IDataFetcher}func NewJsonExporter(fetcher IDataFetcher) IDataExporter {    return &JsonExporter{        fetcher,    }}func (me *JsonExporter) Fetcher(fetcher IDataFetcher) {    me.mFetcher = fetcher}func (me *JsonExporter) Export(sql string, writer io.Writer) error {    rows := me.mFetcher.Fetch(sql)    fmt.Printf("JsonExporter.Export, got %v rows\n", len(rows))    for i,it := range rows {        fmt.Printf("  %v %s\n", i + 1, it.FieldsString())    }    return nil}

桥接模式小结

桥接模式的长处
(1)拆散形象局部及其具体实现局部。
(2)进步了零碎的扩展性。
(3)合乎开闭准则。
(4)合乎合成复用准则。
桥接模式的毛病
(1)减少了零碎的了解与设计难度。
(2)须要正确地识别系统中两个独立变动的维度。

(end)