设计模式大赏(一):桥接模式,组合模式
导言
本篇文章是设计模式大赏中的第一篇文章,这个系列的文章中我们主要将介绍一些常见的设计模式,主要是我在看Android源码中发现用到的一些设计模式。本篇文章将主要介绍桥接模式 和
组合模式 这两种设计模式。
本篇文章主要参考:《大话设计模式》
桥接模式
Android中的桥接模式
我一开始接触到桥接模式是在学习Android的WindowManager相关的源码过程中,具体来说,WindowManager
的实现类WindowManagerImpl
实际上并不直接实现相关的请求,而是将其委托给WindowManagerGlobal
这个单例类来实现。这样将实现分为抽象和具体两部分的模式就叫做桥接模式。
合成/聚合复用原则
在正式介绍到桥接模式之前我们需要先介绍一下合成聚合复用原则,该原则的核心思想就是:尽量使用合成和聚合,而不要
去使用类的继承。
聚合表示一种弱的拥有关系
,即A对象可以包含B对象,但B对象不是A对象的一部分。比如说整个班级中可以包含我们个人,但是我们个人并不是班级的一部分,我们脱离开班级还是可以独立存在的,班级脱离我们也可以独立存在。
合成则表示一种强的拥有关系
,比如说鸡翅对于鸡一样,显然鸡翅是鸡的一部分,他们的生命周期是一致的,谁也脱离不开谁。
但无论是聚合还是合成关系,他们的耦合强度都要低于继承,继承是一种强耦合的关系,遵循合成/聚合复用原则可以防止类发展成一个难以管理的庞然大物。
桥接模式的概念
桥接模式(Bridge),是一种将抽象部分与它的实现部分分离,使他们都可以独立变化的一种设计模式。
下面是一个标准的桥接模式的基本模板:
实际上这样说还是很抽象,我们可以借用大话设计模式中的例子来说明:
比如说对手机来说,我们可以将其拆分出两个抽象的概念:手机的硬件和手机的软件,对于我们Android端来说,各个手机厂商的硬件之间都是高度定制化的,所以我们可以将其简化为手机厂商的品牌和手机的软件这两个抽象:
显然各个品牌手机的手机都会持有各自的手机软件,但是手机软件并不是属于手机的一部分,这是一种聚合持有的关系。这样分类的原因就是为了方便手机品牌和手机软件之间各自的拓展:即为每个抽象添加功能不涉及到其他类的修改。
实际上,我觉得桥接模式强调的就一点:通过多个角度将一个系统进行切分,使各个部分在变化的时候不影响其他部分的变化。
这样做的好处有:
- 使系统更加灵活,能够独立地扩展和变化抽象部分和实现部分。
- 提高了系统的可维护性和可扩展性,因为可以通过添加新的抽象或实现类来扩展系统。
桥接模式的具体示例
为了更好地理解,接下来引入一个具体的实例:比如说我们想要生产瓶装饮料的话,就可以将这整个罐装饮料抽象成两个部分:装饮料的瓶子和具体的饮料
下面是我的示例代码:
fun main() {
val Glass:Bottle = GlassBottle(Cola())
val Plastic:Bottle = PlasticBottle(Sprite())
val Bridge = BottleBridgeImpl()
Bridge.LoadDrink(Glass)
Bridge.LoadDrink(Plastic)
}
//抽象类-瓶子
abstract class Bottle(var mDrinks:Drinks) {
//制作瓶子的方法
abstract fun MakeBottle()
}
//抽象类-饮料
abstract class Drinks{
//生产饮料的方法
abstract fun MakeDrinks()
}
//抽象出连接上面两个抽象类的桥接方法
interface BottleBridge{
//装瓶的方法
fun LoadDrink(bottle:Bottle)
}
class BottleBridgeImpl():BottleBridge{
override fun LoadDrink(bottle: Bottle) {
println("压缩装瓶,倒入${bottle.mDrinks}")
}
}
class GlassBottle(mDrinks: Drinks):Bottle(mDrinks) {
override fun MakeBottle() {
println("生产玻璃瓶")
}
}
class PlasticBottle(mDrinks: Drinks):Bottle(mDrinks) {
override fun MakeBottle() {
println("生产塑料瓶")
}
}
class Cola():Drinks(){
override fun MakeDrinks() {
println("生产可乐")
}
override fun toString(): String {
return "可乐"
}
}
class Sprite():Drinks() {
override fun MakeDrinks() {
println("生产雪碧")
}
override fun toString(): String {
return "雪碧"
}
}
此处我将饮料和瓶子这两部分抽象出来,并引入了一个连接这两个部分的桥接接口,这样饮料和瓶子这两个抽象概念在变化的过程只需要维护自身的制造方法即可而不需要关注其他部分。
总而言之,我觉得桥接模式的核心就是从多个维度将一个系统进行拆分,抽象出各个抽象部分,然后各个抽象部分各自有自己的实现,使得一个抽象部分的具体实现变化时不影响其他的部分。
组合模式
组合模式的概念
组合模式,是将对象组合成树形结构以表示
整体-部分
的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性。
这整个描述里有两个关键字:树形结构以及一致性。这里我们可以以一个学校的管理为例,学校的管理系统首先是一个大的全局的学校管理系统,向下细分又可以分为学院管理系统,再向下还可以分为各个专业的管理系统,这整个结构是一个树形的结构;而对于各个层级的管理系统我们肯定希望他们对具体信息的操作是一致的,比如说我们可以在学院管理系统对学生A进行加分操作,也可以在专业管理系统中对学生A进行加分操作,这个加分操作无论是在哪个层级的管理系统中发起的都应该有一致的效果,这就是一致性。
下面是组合模式的典型结构图:
Android中的组合模式
说到这里是不是觉得这个组合模式很像Android中ViewGroup和View的组合关系,实际上确实如此,通过使用组合模式,Android开发人员可以更容易地管理和操作UI元素,以便以可扩展和灵活的方式构建用户界面。每个 ViewGroup 作为容器可以包含多个子 View,从而形成了整个布局的层次结构。
何时使用组合模式
使用组合模式的最佳场景就是当你发现需求中体现的是整体与部分层次的结构时,以及你希望用户可以忽略组合对象与单个对象的不同,统一的使用组合结构中的所有对象时,此时就应该考虑使用组合模式了,在上面说到了Android整个视图层次就是用到了组合模式,实际上Java上的UI层次也类似。
这个组合模式如果理解了Android中View的视图层次应该就很形象了,此处就不再另加例子,大家可以去看看Android中View的视图层次。
使用组合模式的好处
使用组合模式的好处,首先我觉得如果体现的是整体与部分的层次结构时,使用组合模式显然可以让整个需求的层次更加清晰。
其次,用户不用关心这到底是一个组件组还是一个组件,简单来说组合模式让用户可以一致地使用组合结构和单个对象。