桥接模式
类型
结构型设计模式
组合/聚合原则
优先使用组合/聚合原则,而不是继承。原因在于,当使用继承时,子类和父类有非常紧密的依赖关系。父类实现中的任何变化必然会导致子类发生变化。当复用子类时,如果继承下来的实现不适合解决新的问题,父类的重写或被其他类的替换会影响到子类,这种依赖关系会限制灵活性并最终限制复用性。
聚合
表示一种弱的“拥有”关系,体现为a对象可以包含b对象,但b对象不是a对象的一部分
组合
表示一种强的“拥有”关系,体现了严格的部分和整体的关系,部分和整体的生命周期一样
类比来看,一只大雁的两只翅膀是属于大雁的,相同的生命周期,这就是组合;一只大雁属于一个雁群,生命周期分离,这就是聚合。
为什么要优先使用聚合/组合?
这种方式有助于保持每个类被封装,并被集中在单个任务上(单一原则),使得类与类的继承层次保持较小规模。降低父类和大量子类或者大体积子类的出现的情况,避免增长成不可控制的庞然大物。
对比使用继承的紧耦合设计,聚合/组合是一种松耦合设计。
举例来看
对于不同手机的不同软件功能,比如品牌m、n,功能通讯录和游戏。 使用继承的紧耦合形式:
或者
以第一种为例,如果采用这种方式,当想要增加新的功能时,品牌m、n都要实现一个新功能类,增加新品牌时,该品牌要实现所有现存的功能,所以随着后期发展,子类只会越来越多,越来越臃肿
如果采用松耦合的方式
将品牌和软件的分为两个类,以聚合的方式,将手机软件作为品牌的一个成员,通过set来设置手机品牌的属性。这样就不用为每一个手机品牌都实现一次软件,只要实现一次软件,再通过set设置到各个品牌中即可。 如果用这种方法,当想要增加新的功能时,将通讯录和游戏作为成员属性set进去即可;当想增加新功能时,只需实现一次功能即可。
核心思想
将抽象部分与其具体实现部分分离,使它们可以独立地变化。
如何理解抽象部分和实现部分
抽象部分
抽象部分是指高层的抽象类或接口,它定义了高层的业务逻辑或功能,并通过持有实现部分的引用来调用底层的具体实现。
抽象部分不关心底层的具体实现细节,它只关注所需的功能,并将具体实现部分将进行该功能具体的实现。
抽象部分定义了高层接口,用于与客户端进行交互,客户端通过抽象接口使用功能。
实现部分
实现部分是指底层的具体实现类或接口,它负责实现抽象部分定义的接口或方法。
实现部分独立于抽象部分,它提供了具体的功能实现,并可以根据需要进行扩展和变化。
实现部分通常包含了底层的细节和算法,它通过被抽象部分引用,并根据抽象部分的需求进行具体实现。
换个说法
抽象部分更像是规定好的业务逻辑,以上面手机为例,手机的不同品牌应该都具备游戏和通讯录功能,但是这个功能具体是什么样子,是由实现部分来决定的。实现部分来做游戏和通讯录的具体实现。然后实现部分作为抽象部分的一个成员,可以让抽象部分来进行调用
结构图
场景
- 有多个维度上的变化,每个维度都需要独立扩展,想避免类爆炸。那么可以将每个维度抽象成独立的类,并使用桥接模式将它们连接起来。
- 想将抽象部分和具体实现建立稳定的联系,又不想在代码中写死进行静态的绑定,想在运行时进行抽象部分和实现部分的动态组合。
角色
- 抽象部分:定义了高层的抽象接口,并持有实现部分接口的成员。通过抽象接口与客户端进行交互。
- 具体抽象类:是抽象接口的实现,通过调用实现部分来完成具体的功能。
- 实现部分:是底层的具体实现的接口,定义了需要具体实现的功能。
- 具体实现类:是实现接口的具体实现,它实现了实现接口定义的功能。根据自身类的不同提供实现接口的实现细节。
优缺点
优点
- 解耦抽象和实现:桥接模式通过将抽象部分和实现部分分离,使它们可以独立地变化,从而实现了解耦。这使得系统的抽象部分和实现部分可以独立地扩展和演化,不会相互影响。
- 提高系统的灵活性:可以通过组合不同的抽象和实现对象来动态地各种功能需求。使系统更具适应性和可扩展性。
- 可以减少子类的个数:抽象部分和实现部分是独立的层次结构,它们通过桥接对象连接在一起。这使得在系统中不需要创建大量的子类来适应各种抽象和实现的组合,从而减少了子类的个数。
缺点
- 增加系统的复杂性:桥接模式引入了抽象部分和实现部分之间的桥接对象,增加了系统的复杂性和理解难度,增加开发和维护的成本。
- 额外的设计工作:在桥接模式中,抽象部分和实现部分是独立的,需要额外的设计工作来定义它们之间的接口和关系
demo
package main
import "fmt"
// 实现部分接口
type DrawingAPI interface {
DrawCircle(radius, x, y int)
}
// 具体实现类:绘制API的实现类A
type DrawingAPIA struct{}
func (d *DrawingAPIA) DrawCircle(radius, x, y int) {
fmt.Printf("API A: Drawing circle with radius %d at (%d, %d)\n", radius, x, y)
}
// 具体实现类:绘制API的实现类B
type DrawingAPIB struct{}
func (d *DrawingAPIB) DrawCircle(radius, x, y int) {
fmt.Printf("API B: Drawing circle with radius %d at (%d, %d)\n", radius, x, y)
}
// 抽象部分
type Shape interface {
Draw()
ResizeByPercentage(percentage float32)
}
// 具体抽象类:圆形
type Circle struct {
radius int
x, y int
drawing DrawingAPI
}
func (c *Circle) Draw() {
c.drawing.DrawCircle(c.radius, c.x, c.y)
}
func (c *Circle) ResizeByPercentage(percentage float32) {
c.radius = int(float32(c.radius) * percentage)
}
func main() {
// 使用实现类A绘制一个圆形
circleA := &Circle{
radius: 5,
x: 10,
y: 20,
drawing: &DrawingAPIA{},
}
circleA.Draw()
// 使用实现类B绘制一个圆形
circleB := &Circle{
radius: 8,
x: 30,
y: 40,
drawing: &DrawingAPIB{},
}
circleB.Draw()
// 调整圆形大小
circleA.ResizeByPercentage(0.5)
circleA.Draw()
}
总结
我个人感觉这模式超级nice,只要对这个模式比较熟,并且实现好。感觉带来的都是好处。这个模式符合开闭原则,并且有效体现了组合优于继承的问题。
大话设计模式里面说到桥接模式的理解,实现系统可能有多角度分类,每一种分类都有可能变化,那么就把这种多角度分离出来让它们独立的变化,减少它们之间的耦合。