缘起

最近复习设计模式
拜读谭勇德的<<设计模式就该这样学>>
本系列笔记拟采用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

  1. package structural_patterns
  2. import (
  3. "bytes"
  4. "learning/gooop/structural_patterns/bridge"
  5. "testing"
  6. )
  7. func Test_BridgePattern(t *testing.T) {
  8. config := bridge.NewDBConfig("mysql", "root:pass@tcp(localhost:3306)/test?charset=utf8", "root", "pass")
  9. fetcher := bridge.NewMysqlDataFetcher(config)
  10. fnTestExporter := func(exporter bridge.IDataExporter) {
  11. var writer bytes.Buffer
  12. e := exporter.Export("select * from ims_stock", &writer)
  13. if e != nil {
  14. t.Error(e)
  15. }
  16. }
  17. fnTestExporter(bridge.NewCsvExporter(fetcher))
  18. fnTestExporter(bridge.NewJsonExporter(fetcher))
  19. }

测试输出

  1. $ go test -v bridge_pattern_test.go
  2. === RUN Test_BridgePattern
  3. CsvExporter.Export, got 1 rows
  4. 1 int-1=1, float-1=1.1, string-1="hello"
  5. CsvExporter.Export, got 1 rows
  6. 1 int-1=1, float-1=1.1, string-1="hello"
  7. --- PASS: Test_BridgePattern (0.00s)
  8. PASS
  9. ok command-line-arguments 0.001s

DBConfig.go

定义数据库连接配置信息

  1. package bridge
  2. type DBConfig struct {
  3. DBType string
  4. URL string
  5. UID string
  6. PWD string
  7. }
  8. func NewDBConfig(dbType string, url string, uid string, pwd string) *DBConfig {
  9. return &DBConfig{
  10. DBType: dbType,
  11. URL: url,
  12. UID: uid,
  13. PWD: pwd,
  14. }
  15. }

DataRow.go

表示导出数据行的中间结果

  1. package bridge
  2. import (
  3. "fmt"
  4. "strings"
  5. )
  6. type DataRow struct {
  7. FieldList []*DataField
  8. }
  9. func NewMockDataRow() *DataRow {
  10. it := &DataRow{
  11. make([]*DataField, 0),
  12. }
  13. it.FieldList = append(it.FieldList, NewMockDataField("int-1", DATA_TYPE_INT))
  14. it.FieldList = append(it.FieldList, NewMockDataField("float-1", DATA_TYPE_FLOAT))
  15. it.FieldList = append(it.FieldList, NewMockDataField("string-1", DATA_TYPE_STRING))
  16. return it
  17. }
  18. func (me *DataRow) FieldsString() string {
  19. lst := make([]string, 0)
  20. for _,f := range me.FieldList {
  21. lst = append(lst, fmt.Sprintf("%s=%s", f.Name, f.ValueString()))
  22. }
  23. return strings.Join(lst, ", ")
  24. }

DataField.go

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

  1. package bridge
  2. import (
  3. "fmt"
  4. "time"
  5. )
  6. type DataTypes string
  7. const DATA_TYPE_INT = "int"
  8. const DATA_TYPE_FLOAT = "float"
  9. const DATA_TYPE_STRING = "string"
  10. const DATA_TYPE_BOOL = "bool"
  11. const DATA_TYPE_DATETIME = "datetime"
  12. type DataField struct {
  13. Name string
  14. DataType DataTypes
  15. IntValue int
  16. FloatValue float64
  17. StringValue string
  18. BoolValue bool
  19. DateTimeValue *time.Time
  20. }
  21. func NewMockDataField(name string, dataType DataTypes) *DataField {
  22. it := &DataField {
  23. Name: name,
  24. DataType: dataType,
  25. IntValue: 0,
  26. FloatValue: 0,
  27. StringValue: "",
  28. BoolValue: false,
  29. DateTimeValue: nil,
  30. }
  31. switch dataType {
  32. case DATA_TYPE_INT:
  33. it.IntValue = 1
  34. break
  35. case DATA_TYPE_FLOAT:
  36. it.FloatValue = 1.1
  37. break
  38. case DATA_TYPE_STRING:
  39. it.StringValue = "hello"
  40. break
  41. case DATA_TYPE_DATETIME:
  42. t := time.Now()
  43. it.DateTimeValue = &t
  44. break
  45. case DATA_TYPE_BOOL:
  46. it.BoolValue = false
  47. break
  48. }
  49. return it
  50. }
  51. func (me *DataField) ValueString() string {
  52. switch me.DataType {
  53. case DATA_TYPE_INT:
  54. return fmt.Sprintf("%v", me.IntValue)
  55. case DATA_TYPE_FLOAT:
  56. return fmt.Sprintf("%v", me.FloatValue)
  57. case DATA_TYPE_STRING:
  58. return fmt.Sprintf("\"%s\"", me.StringValue)
  59. case DATA_TYPE_DATETIME:
  60. return fmt.Sprintf("\"%s\"", me.DateTimeValue.Format("2006-01-02T15:04:05"))
  61. case DATA_TYPE_BOOL:
  62. return fmt.Sprintf("%v", me.BoolValue)
  63. }
  64. return ""
  65. }

IDataFetcher.go

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

  1. package bridge
  2. type IDataFetcher interface {
  3. Fetch(sql string) []*DataRow
  4. }

MysqlDataFetcher.go

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

  1. package bridge
  2. type MysqlDataFetcher struct {
  3. Config *DBConfig
  4. }
  5. func NewMysqlDataFetcher(config *DBConfig) IDataFetcher {
  6. return &MysqlDataFetcher{
  7. config,
  8. }
  9. }
  10. func (me *MysqlDataFetcher) Fetch(sql string) []*DataRow {
  11. rows := make([]*DataRow, 0)
  12. rows = append(rows, NewMockDataRow())
  13. return rows
  14. }

OracleDataFetcher.go

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

  1. package bridge
  2. type OracleDataFetcher struct {
  3. Config *DBConfig
  4. }
  5. func NewOracleDataFetcher(config *DBConfig) IDataFetcher {
  6. return &OracleDataFetcher{
  7. config,
  8. }
  9. }
  10. func (me *OracleDataFetcher) Fetch(sql string) []*DataRow {
  11. rows := make([]*DataRow, 0)
  12. rows = append(rows, NewMockDataRow())
  13. return rows
  14. }

IDataExporter.go

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

  1. package bridge
  2. import "io"
  3. type IDataExporter interface {
  4. Fetcher(fetcher IDataFetcher)
  5. Export(sql string, writer io.Writer) error
  6. }

CsvExporter.go

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

  1. package bridge
  2. import (
  3. "fmt"
  4. "io"
  5. )
  6. type CsvExporter struct {
  7. mFetcher IDataFetcher
  8. }
  9. func NewCsvExporter(fetcher IDataFetcher) IDataExporter {
  10. return &CsvExporter{
  11. fetcher,
  12. }
  13. }
  14. func (me *CsvExporter) Fetcher(fetcher IDataFetcher) {
  15. me.mFetcher = fetcher
  16. }
  17. func (me *CsvExporter) Export(sql string, writer io.Writer) error {
  18. rows := me.mFetcher.Fetch(sql)
  19. fmt.Printf("CsvExporter.Export, got %v rows\n", len(rows))
  20. for i,it := range rows {
  21. fmt.Printf(" %v %s\n", i + 1, it.FieldsString())
  22. }
  23. return nil
  24. }

JsonExporter.go

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

  1. package bridge
  2. import (
  3. "fmt"
  4. "io"
  5. )
  6. type JsonExporter struct {
  7. mFetcher IDataFetcher
  8. }
  9. func NewJsonExporter(fetcher IDataFetcher) IDataExporter {
  10. return &JsonExporter{
  11. fetcher,
  12. }
  13. }
  14. func (me *JsonExporter) Fetcher(fetcher IDataFetcher) {
  15. me.mFetcher = fetcher
  16. }
  17. func (me *JsonExporter) Export(sql string, writer io.Writer) error {
  18. rows := me.mFetcher.Fetch(sql)
  19. fmt.Printf("CsvExporter.Export, got %v rows\n", len(rows))
  20. for i,it := range rows {
  21. fmt.Printf(" %v %s\n", i + 1, it.FieldsString())
  22. }
  23. return nil
  24. }

桥接模式小结

桥接模式的优点
(1)分离抽象部分及其具体实现部分。
(2)提高了系统的扩展性。
(3)符合开闭原则。
(4)符合合成复用原则。
桥接模式的缺点
(1)增加了系统的理解与设计难度。
(2)需要正确地识别系统中两个独立变化的维度。

(end)