头图

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

ioly

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

阅读 681

想当将军 首先要肯打

42 声望
18 粉丝
0 条评论

想当将军 首先要肯打

42 声望
18 粉丝
文章目录
宣传栏