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

Posted ioly

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了手撸golang 结构型设计模式 桥接模式相关的知识,希望对你有一定的参考价值。

手撸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 结构型设计模式 桥接模式的主要内容,如果未能解决你的问题,请参考以下文章

手撸golang 行为型设计模式 访问者模式

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

手撸golang 行为型设计模式 模板方法模式

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

手撸golang 行为型设计模式 委派模式

手撸golang 行为型设计模式 状态模式