手撸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_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.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_BridgePatternCsvExporter.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)PASSok command-line-arguments 0.001s
DBConfig.go
定义数据库连贯配置信息
package bridgetype 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 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 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 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("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)