桥接模式:指将抽象部分与具体实现部分分离,使它们都可以独立地变化。
桥接模式在系统多维度扩展和降低臃肿度上起作用。
某业务系统, 现需要开发数据库导出工具, 根据SQL语句导出表数据到文件,数据库类型有多种, 目前需要支持MySQL, Orache 未来可能支持 SQLServer。导出格式可能有多种, 目前需要支持CSV和JSON格式。
如果用常规的继承来实现这个数据库导出模块,模块中首先要有一个类似抽象基础类的基类,然后再用继承分别实现:MySQL-CSV导出类、MySQL- JSON导出类、Oracle-CSV导出类、Oracle-JSON导出类,如果以后模块再加一种支持的数据库SQLServer和导出格式XML,那么系统里实现类就更多了。如果有N个维度,每个维度有M种变化,则最少需要M * N个实现类,类非常多,并且实现类中有非常多的重复功能。
而将"导出工具"分离出"数据抓取"和"数据导出"两个维度, 以便自由扩展、互相组合,从而减少类数目。这便是使用桥接模式解决“需求多维度变化时系统会变臃肿的核心思想。
当我们把每个维度拆分开来,只需要M+N个类,并且由于每个维度独立变化,基本不会出现重复代码。此时如果增加一种支持的数据源,只需要增加一个IDataFetcher
的实现类即可。
首先定义出数据导出器和查询器的接口
// 数据导出器
type IDataExporter interface {
Fetcher(fetcher IDataFetcher)
Export(sql string, writer io.Writer) error
}
// 数据查询器
type IDataFetcher interface {
Fetch(sql string) []interface{}
}
目前数据器有两个具体实现MysqlDataFetcher
和OracleDataFetcher
,它们分别负责从MySQL
和Oracle
数据库中查询数据。后续要给导出工具扩展支持的数据库,就在新增对应的IDataFetcher
实现即可。
type MysqlDataFetcher struct {
Config string
}
func (mf *MysqlDataFetcher) Fetch(sql string) []interface{} {
fmt.Println("Fetch data from mysql source: " + mf.Config)
rows := make([]interface{}, 0)
// 插入两个随机数组成的切片,模拟查询要返回的数据集
rows = append(rows, rand.Perm(10), rand.Perm(10))
return rows
}
func NewMysqlDataFetcher(configStr string) IDataFetcher {
return &MysqlDataFetcher{
Config: configStr,
}
}
type OracleDataFetcher struct {
Config string
}
func (of *OracleDataFetcher) Fetch(sql string) []interface{} {
fmt.Println("Fetch data from oracle source: " + of.Config)
rows := make([]interface{}, 0)
// 插入两个随机数组成的切片,模拟查询要返回的数据集
rows = append(rows, rand.Perm(10), rand.Perm(10))
return rows
}
func NewOracleDataFetcher(configStr string) IDataFetcher {
return &OracleDataFetcher{
configStr,
}
}
再定义两个数据导出器IDataExporter
的实现:CsvExporter
和JsonExporter
,可以看到IDataExporter
的实现会通过一个内部属性持有对IDataFetcher
的引用,即通过组合的方式来完成我们的数据导出器在导出格式和数据源类型两个维度上的自由搭配。
type CsvExporter struct {
mFetcher IDataFetcher
}
func NewCsvExporter(fetcher IDataFetcher) IDataExporter {
return &CsvExporter{
fetcher,
}
}
func (ce *CsvExporter) Fetcher(fetcher IDataFetcher) {
ce.mFetcher = fetcher
}
func (ce *CsvExporter) Export(sql string, writer io.Writer) error {
rows := ce.mFetcher.Fetch(sql)
fmt.Printf("CsvExporter.Export, got %v rows\n", len(rows))
for i, v:= range rows {
fmt.Printf(" 行号: %d 值: %s\n", i + 1, v)
}
return nil
}
type JsonExporter struct {
mFetcher IDataFetcher
}
func NewJsonExporter(fetcher IDataFetcher) IDataExporter {
return &JsonExporter{
fetcher,
}
}
func (je *JsonExporter) Fetcher(fetcher IDataFetcher) {
je.mFetcher = fetcher
}
func (je *JsonExporter) Export(sql string, writer io.Writer) error {
rows := je.mFetcher.Fetch(sql)
fmt.Printf("JsonExporter.Export, got %v rows\n", len(rows))
for i, v:= range rows {
fmt.Printf(" 行号: %d 值: %s\n", i + 1, v)
}
return nil
}
func main() {
mFetcher := NewMysqlDataFetcher("mysql://127.0.0.1:3306")
csvExporter := NewCsvExporter(mFetcher)
var writer bytes.Buffer
// 从MySQL数据源导出 CSV
csvExporter.Export("select * from xxx", &writer)
oFetcher := NewOracleDataFetcher("mysql://127.0.0.1:1001")
csvExporter.Fetcher(oFetcher)
// 从 Oracle 数据源导出 CSV
csvExporter.Export("select * from xxx", &writer)
// 从 MySQL 数据源导出 JSON
jsonExporter := NewJsonExporter(mFetcher)
jsonExporter.Export("select * from xxx", &writer)
}
可以看到桥接器模式使用的原理是go语言的多态,它使用了双重多态实现了同一个接口的不同类可以通过该接口实例化不同的类。而go中实现一个接口就是实现该接口定义的全部方法。多态具体的可以看https://blog.csdn.net/zhaicheng55/article/details/129884134
参考公众号网管叨bi叨