手撸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)
有疑问加站长微信联系(非本文作者)