引你入坑的程序
程序1:用简单的几个类表示出手机系统分为Android与IOS。
一共需要三个类:手机系统抽象类PhoneOS、Android系统与IOS系统两个子类。
PhoneOS手机系统抽象类,提供一个install()安装软件的方法
/**
* @create on 2020/6/8 23:32
* @description 手机系统
* @author mrdonkey
*/
abstract class PhoneOS {
/**
* 安装方法
*/
open fun install(){}
}
Android
/**
* @create on 2020/6/8 23:34
* @description android品牌
* @author mrdonkey
*/
open class Android : PhoneOS() {
}
IOS
/**
* @create on 2020/6/8 23:35
* @description IOS品牌
* @author mrdonkey
*/
open class IOS : PhoneOS() {
}
完成!
程序2:在程序1的基础下:两种系统的手机都有游戏与学习两类软件,学习包括B站,游戏有:王者荣耀。
实现的UML图如下
Android与IOS增加两个子类
AndriodBilibili
/**
* @create on 2020/6/8 23:41
* @description android版Bilibili
* @author mrdonkey
*/
class AndroidBilibili :Android(){
override fun install() {
println("----->安装Android版Bilibili")
}
}
AndroidLOL
/**
* @create on 2020/6/8 23:44
* @description android版LOL
* @author mrdonkey
*/
class AndroidLOL : Android() {
override fun install() {
println("----->安装Android版LOL")
}
}
IOS的代码同上。
程序3:在程序2的基础下,如果再加上效率的软件,并且再加一个Harmony系统的手机,该如何做?
若在上面的程序再加一个效率软件,那么分别得在Android和IOS下新增。若是增加一个新的手机系统,那么就得copy现有系统下的各个软件分支。目前看起来还不算复杂,若软件分支一多、手机系统增加的话,修改就过于麻烦。
换一种继承结果怎样?
按手机软件进行分类UML,这样的做法其实与程序2的结构差别不大,增加手机软件的分支或手机系统,也难免多而烦的修改。
谨慎使用继承
之前的设计: 用面向对象的继承进行设计,先有一种手机系统,多种系统就抽象出一个手机系统抽象类PhoneOS,对于每一类软件,就都各自继承各自的系统。再就是从手机的软件分类角度去划分。
分析:很多情况一味的使用继承会带来麻烦。对象的继承关系是在编译时就定义好了,所以无法在运行时改变从父类继承的实现。子类的实现与它父类的实现有非常紧密的依赖关系,以至于父类实现中的任何变化必然会导致子类发生变化。让你需要复用子类时,如果继承下来的实现不适合解决新的问题。,则父类必须重写或被其他更合适的类替换。这种依赖关系限制了灵活性与复用性。对于上述的继承结构,如果不断新增软件分类或系统,类的越来越多。使用继承的条件:继承关系,是一种强耦合的结构,父类变子类必须变。所以一定要是‘is a’关系时再考虑使用继承,而不是任何时候都去使用。改进:学会运用合成(组合)/聚合原则,即优先使用对象合成/聚合,而不是类继承。
合成/聚合复用原则
合成(也叫组合)与聚合都是关联的特色种类。
合成:表示一种强的‘拥有’关系,体现了部分和整体部分的关系,部分和整体的生命周期一样。
聚合: 表示一种弱的‘拥有’关系,体现的是A对象可以包含B对象,但B对象不一定是A对象的一部分。
简单示例
- 大雁有两个翅膀,翅膀与大雁是部分与整体的关系,并且生命周期是相同的,所以它们是
合成关系。 - 大雁是群居动物,每只大雁都属于一个雁群,一个雁群可以有多只大雁,所有大雁与雁群是
聚合关系。
使用优势
优先使用对象的合成/聚合将有助于保持每个类被封装,并被集中在单个任务上。这样的类和类的层次会保持比较小的规模,并不太可能增长为不可控制的庞然大物。学会使用对象的职责,而不是结构来考虑问题。
应用合成/聚合复用原则改写程序3
分析
在之前的程序中,利用有手机系统、手机软件分类两块;我们就可以将这两块进行拆分成一个单元,通过聚合关系将手机系统关联手机软件 分类(因为一个系统拥有多种软件分类),它们是聚合关系;拆成小单元后,每一个模块可以各自变化,不影响其他的模块。
具体结构与代码
- 将继承层次利用聚合关系拆分
- 系统与软件是一种聚合关系,即便新增系统或软件,都相互不影响。
应用现在的结构设计,即便新增,也不会修改原来的代码(开放-封闭原则)。
它们之间有一条聚合线,像一座桥!这种设计模式就叫做桥接模式。
桥接模式
定义
桥接模式(Bridge),将抽象部分与它的实现部分分类,使它们都可以独立变化。 这里说的抽象与它的实现分离并不是让抽象类与其子类分离,而是指抽象类用来实现自己的对象(手机系统)和子类用来实现自己对象(软件分类),拆分原本的继承层次,就是分离。核心意图:把每个实现(如软件分类)都独立出来,让他们各自变化。这使得每种实现的变化不影响其他的实现,从而达到应对变化的目的。
基本代码
桥接模式UML图
Implementor 实现类
/**
* @create on 2020/6/9 23:26
* @description 实现部分
* @author mrdonkey
*/
abstract class Implementor {
abstract fun operation()
}
ConcreteImplementorA和ConcreteImplementorB具体实现类
/**
* @create on 2020/6/9 23:30
* @description 具体实现A
* @author mrdonkey
*/
class ConcreteImplementorA:Implementor() {
override fun operation() {
println("----->具体实现A的方法执行")
}
}
ConcreteImplementorB 如上,省略了。
Abstraction抽象类
/**
* @create on 2020/6/9 23:28
* @description 抽象部分
* @author mrdonkey
*/
open class Abstraction {
lateinit var implementor: Implementor
open fun operation() {
if (this::implementor.isInitialized) {//如果被初始化才执行
implementor.operation()
}
}
}
RefinedAbstraction 被提炼的抽象类
/**
* @create on 2020/6/9 23:29
* @description 被提炼的抽象
* @author mrdonkey
*/
class RefinedAbstraction:Abstraction() {
override fun operation() {
implementor.operation()
}
}
Client 客户端
/**
* @create on 2020/6/9 23:35
* @description 客户端类
* @author mrdonkey
*/
class Client {
companion object {
@JvmStatic
fun main(args: Array<String>) {
val abstraction = RefinedAbstraction()
abstraction.implementor = ConcreteImplementorA()
abstraction.operation()
abstraction.implementor = ConcreteImplementorB()
abstraction.operation()
}
}
}
### 小结
桥接模式:`实际就是实现系统可能有多角度分类。每一种分类都可能变化,那么就把这种多角度分离出来让它们独立变化,减少他们的耦合。`