今天开始我们进入结构型设计模式讲解。结构型是把类和对象组装成一个结构(组合在一起使用),从而使代码的运行效率更高、可扩展性更好。
首先我们了解下适配器模式。
听到“适配器”我们很容易联想到当我们买的港版手机等电子产品时,配置的充电头和行货的不一样。(不过现在手机已经不配置充电头了🤦♂️)如果购买了港版手机在内陆使用,这个时候我们需要再额外购买个转接头才可以使用这个充电器给手机充电。这个转接头就是“适配器”。
在我们的实际代码中也存在着各种适配场景,例如某个处理json格式参数的接口,而由于某个场景输入的是xml格式的参数,我们需要在接口外写一个适配函数将xml格式转为json,未来的输入可能还有yaml、toml等格式,都需要增加适配函数而无需对原有的逻辑进行处理。
我们先来看下一个简单的Go代码实现:
type Phone struct {
}
func (p Phone) Charge() {
fmt.Println("手机充电")
}
// 适配器
type HKAdaptor struct {
phone Phone
}
func (a HKAdaptor) Charge() {
fmt.Println("使用适配器给手机充电")
a.phone.Charge()
}
func main() {
// 在国内直接给国内版本手机充电
p := Phone{}
p.Charge()
// 在香港给国内版本手机充电
adaptor := HKAdaptor{
phone: p,
}
adaptor.Charge()
}
这里 HKAdaptor 就是给手机装上的适配器,从而在香港也可以给手机充电。这个时候我们可以再想一下,如果我们出差去香港还带有笔记本,按照上面的实现代码,还得带一个给笔记本充电头的适配器吗?
其实转换器是通用的,无需再给笔记本带一个另一种类型的适配器,代码的实现就是可以复用 HKAdaptor 结构,我们再看下更通用的适配代码需要怎样改动呢?
type Digital interface {
Charge()
}
type Phone struct {
}
func (p Phone) Charge() {
fmt.Println("手机充电")
}
type Computor struct {
}
func (c Computor) Charge() {
fmt.Println("电脑充电")
}
// 适配器
type HKAdaptor struct {
digital Digital
}
func (a HKAdaptor) Charge() {
fmt.Println("使用适配器给电子产品充电")
a.digital.Charge()
}
func main() {
// 在国内直接给国内版本手机充电
p := Phone{}
p.Charge()
// 在国内直接给国内版本电脑充电
c := Computor{}
c.Charge()
// 在香港给国内版本手机充电
adaptor := HKAdaptor{
digital: p,
}
adaptor.Charge()
// 手机充完电了给电脑充电
adaptor.digital = c
adaptor.Charge()
}
只需要定义一个含有Charge() 方法的接口 Digital,同时适配器中装载的就不是具体的Phone或者Computer了,而是一个更抽象的接口 Didital。这样无论是手机、电脑或者或者未来你买的各种有充电功能的电子产品(实现了Charge() 方法的类)都可以使用这个适配器充电了。
接下来我们再聊聊桥接模式。
桥接的概念是分别把两种类型的对象链接起来使用。例如有形状的类:圆形、矩形、三角形,颜色类:红、黄、蓝。如果需要两两组和使用,一共有3*3 = 9种组合的实际对象。那么为了实现这9个对象我们需要定义9个类来处理吗?我们看下在桥接模式中实现的代码:
type Color interface {
printer()
}
type Red struct {
}
func (r Red) printer() {
fmt.Println("with red")
}
type Shape interface {
printer()
setColor(Color)
}
type Circle struct {
color Color
}
func (c Circle) printer() {
fmt.Println("Circle")
c.color.printer()
}
func (c Circle) setColor(color Color) {
c.color = color
}
可以类似代码示例中的定义定义其他颜色和形状的类,从而实现了颜色和形状的桥接,避免过多的类定义。后续扩展新的颜色和形状,也只需定义当前对象就行,而无需关注两者与其他类型的所有类的组合使用问题。
我们再来看下组合模式。
这里紧接着讲组合模式是希望和上一个桥接模式模式概念区分开,因为在桥接模式中有两两组合的概念。组合模式的组合表示所有不同对象的处理组合在一起,不用单独的为每个对象定义单独的特殊处理。
模拟计算机的文件系统,有文件或者文件夹,我们需要查找某个关键字的内容时,可以将文件、文件夹同等对待处理。
type Component interface {
Search(w string)
}
type File struct {
Name string
}
type Fold struct {
Components []Component
Name string
}
func (f File) Search(w string) {
fmt.Println("search file")
}
func (f Fold) Search(w string) {
fmt.Println("search file")
for _, c := range f.Components {
c.Search(w)
}
}
这里 File 、Fold都实现了 componet 的 Search方法,所以在文件夹下无论是文件和文件夹都可以直接使用Search方法,真正的执行交给各自对象去处理,而在外层使用者无需关注具体是什么类型。
近期回顾: