缘起
最近复习设计模式
拜读谭勇德的<<设计模式就该这样学>>
本系列笔记拟采用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.Buffere := 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 rows1 int-1=1, float-1=1.1, string-1="hello"CsvExporter.Export, got 1 rows1 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 stringURL stringUID stringPWD 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 stringDataType DataTypesIntValue intFloatValue float64StringValue stringBoolValue boolDateTimeValue *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 = 1breakcase DATA_TYPE_FLOAT:it.FloatValue = 1.1breakcase DATA_TYPE_STRING:it.StringValue = "hello"breakcase DATA_TYPE_DATETIME:t := time.Now()it.DateTimeValue = &tbreakcase DATA_TYPE_BOOL:it.BoolValue = falsebreak}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("CsvExporter.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)
