美文网首页程序员设计模式
手撸golang 结构型设计模式 桥接模式

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

作者: 老罗话编程 | 来源:发表于2021-02-04 16:30 被阅读0次

    手撸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_patterns
    
    import (
        "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_BridgePattern
    CsvExporter.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)
    PASS
    ok      command-line-arguments  0.001s
    

    DBConfig.go

    定义数据库连接配置信息

    package bridge
    
    type 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 bridge
    
    import (
        "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 bridge
    
    import (
        "fmt"
        "time"
    )
    
    type DataTypes string
    const 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 bridge
    
    type IDataFetcher interface {
        Fetch(sql string) []*DataRow
    }
    

    MysqlDataFetcher.go

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

    package bridge
    
    type 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 bridge
    
    type 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 bridge
    
    import "io"
    
    type IDataExporter interface {
        Fetcher(fetcher IDataFetcher)
        Export(sql string, writer io.Writer) error
    }
    

    CsvExporter.go

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

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

    相关文章

      网友评论

        本文标题:手撸golang 结构型设计模式 桥接模式

        本文链接:https://www.haomeiwen.com/subject/xpibtltx.html