1 引子
我们都知道,士兵根据作战性质可以分很多种,比如步兵、骑兵和弓箭手等;又会根据个人的资质不同,会将其分会不同的等级,这里可简单理解为等级A、等级B和等级C。所以此处三种士兵,分别有三个等级,应该共有9个实体类。大概的逻辑图如图所示:
上图很明显,反应出多层继承的问题。
①抽象士兵-步兵/骑兵/弓箭手:根据作战性质进行划分;
②步兵/骑兵/弓箭手-A等/ B等/C等:根据个人资质进行划分;
这种多层继承虽然很明了的构造出具体的类,但细想起来,每个人类的功能和职责却很模糊,例如“A等步兵”类,即反应作战性质,又反应个人资质(等级),这违背了类设计的“单一职责”原则。
另外如果我新增一个等级D,则我需要新增三个类;又或者新增一个“水兵”,则我需要新增四个类;最后的结果就是类的数量非常冗余,导致类的可扩展性差。
仔细分析以上两点可知,类的“作战性质”和“等级”两种属性耦合在一起是导致扩展性差的根本原因。我改变“作战性质”或者“等级”中的任意一个属性,另外一个属性也需要跟着一起变化,即属性紧紧的耦合在一起了。要想解决这个问题,就需要对两种属性进行解耦,使得任意属性的改变都是独立的。改造后的代码逻辑:
抽象士兵类Soldier:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 1/**
2 * 抽象士兵类
3 */
4public abstract class Soldier {
5 private String name;
6 Rank rank;
7 public Soldier(Rank rank, String name){
8 this.rank = rank;
9 this.name = name;
10 }
11
12 public String getName() {
13 return name;
14 }
15
16 public abstract void type();
17}
18
19
Soldier类包含属性name和属性rank。Rank是一个表示等级的接口。
等级接口类Rank:
1
2
3
4
5
6
7
8 1/**
2 * 等级接口
3 */
4public interface Rank {
5 void rankByAchieve(); // 根据成就来划分等级
6}
7
8
“等级”原本属于抽象类里面的一个属性,这里把等级抽象成接口,抽象类Soldier拥有接口引用,遵循类的“依赖倒置”原则。
等级A类:
1
2
3
4
5
6
7
8 1public class RankForA implements Rank {
2 @Override
3 public void rankByAchieve() {
4 System.out.println("等级A...");
5 }
6}
7
8
等级B类:
1
2
3
4
5
6
7
8 1public class RankForB implements Rank {
2 @Override
3 public void rankByAchieve() {
4 System.out.println("等级B...");
5 }
6}
7
8
等级C类:
1
2
3
4
5
6
7
8 1public class RankForC implements Rank {
2 @Override
3 public void rankByAchieve() {
4 System.out.println("等级C...");
5 }
6}
7
8
可以看出“等级”属性此时完全独立于“作战性质”属性,如果新增一个等级D,只需要新增一个实现RanK接口的RankForD类。
“作战性质”属性则继承父类Soldier类,生成不同的类。
步兵类Infantry:
1
2
3
4
5
6
7
8
9
10
11
12 1public class Infantry extends Soldier {
2 public Infantry(Rank rank, String name) {
3 super(rank, name);
4 }
5 @Override
6 public void type() {
7 System.out.print("我是步兵" + this.getName() + ",");
8 rank.rankByAchieve();
9 }
10}
11
12
骑兵类Cavalry:
1
2
3
4
5
6
7
8
9
10
11
12 1public class Cavalry extends Soldier {
2 public Cavalry(Rank rank, String name) {
3 super(rank, name);
4 }
5 @Override
6 public void type() {
7 System.out.print("我是骑兵" + this.getName() + ",");
8 rank.rankByAchieve();
9 }
10}
11
12
弓箭手类Bowman类:
1
2
3
4
5
6
7
8
9
10
11
12 1public class Bowman extends Soldier {
2 public Bowman(Rank rank, String name) {
3 super(rank, name);
4 }
5 @Override
6 public void type() {
7 System.out.print("我是弓箭手" + this.getName() + ",");
8 rank.rankByAchieve();
9 }
10}
11
12
以上三个继承Soldier类的具体类是根据“作战性质”而构造的,由于类封装的是“等级”的接口引用,因此并不涉及具体等级类,使得两者解耦。
客户端Client:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 1public class Client {
2 public static void main(String[] args) {
3 Rank rankA = new RankForA();
4 Infantry infantry = new Infantry(rankA, "xiaoMing"); // 步兵
5 infantry.type();
6 Cavalry cavalry = new Cavalry(rankA, "xiaoWu"); // 骑兵
7 cavalry.type();
8
9 Rank rankB = new RankForB();
10 Infantry infantry2 = new Infantry(rankB, "xiaoLi"); // 步兵
11 infantry2.type();
12 }
13}
14
15
运行结果:
1
2
3
4
5 1我是步兵xiaoMing,等级A...
2我是骑兵xiaoWu,等级A...
3我是步兵xiaoLi,等级B...
4
5
类似于这种把属性解耦的结构称为桥接模式。
2 桥接模式原理
《大话设计模式》中这样定义桥接模式:将抽象部分与它的实现部分分离,使它们都可以独立变化。通过仔细看上面的代码实例,在抽象实体类里面调用的是接口Rank类中的方法,根据客户端传入的参数不同,调用接口中不同的方法;这就是抽象部分与它的实现部分分离(抽象部分是指接口,实现部分是指继承抽象类的实体类)。抽象类起到一个连接实体类和接口的作用,类似于架起了一座沟通的桥梁,因此称为桥接模式。
桥接模式的UML图:
UML类图中各角色:
等级接口Rank:作为“等级”属性的抽象类,此处也可以作为一个行为的接口;
抽象士兵类Soldier类:抽象类,针对不同作战属性的士兵的抽象;内部含有等级接口Rank的一个引用,且含有一个抽象的业务方法;
等级类RankA/RankB/RankC:实现等级接口,实现接口中的方法;类完全独立;
士兵类Infantry/Cavalry/Bowman:继承抽象类的具体类,并实现抽象类中的抽象方法,因为类中含有接口的引用,实体类的方法可以调用接口中定义的方法,达到抽象与实现的分离;
按照最先的思路,“等级”和“作战类型”通过继承完全耦合在一起,而上面的UML图则反映桥接模式通过关联关系替代继承关系,实现两个属性的解耦。
3 桥接模式特点
桥接模式有哪些优点?
(1)
分离抽象接口及其实现部分。桥接模式使用“对象间的关联关系”解耦了抽象和实现之间固有的绑定关系,使得抽象和实现可以沿着各自的维度来变化。所谓抽象和实现沿着各自维度的变化,也就是说抽象和实现不再在同一个继承层次结构中,而是“子类化”它们,使它们各自都具有自己的子类,以便任何组合子类,从而获得多维度组合对象。
(2)在很多情况下,桥接模式可以取代多层继承方案,多层继承方案违背了“单一职责原则”,复用性较差,且类的个数非常多,
桥接模式是比多层继承方案更好的解决方法,它极大减少了子类的个数。
(3)
桥接模式提高了系统的可扩展性,在两个变化维度中任意扩展一个维度,都不需要修改原有系统,
符合“开闭原则”。
桥接模式有哪些缺点?
(1)桥接模式的使用会增加系统的理解与设计难度,由于关联关系建立在抽象层,要求开发者一开始就针对抽象层进行设计与编程。
(2)桥接模式要求正确识别出系统中两个独立变化的维度,因此其使用范围具有一定的局限性,如何正确识别两个独立维度也需要一定的经验积累。
PS:假如把抽象Soldier类变成实体类,则变成策略模式。实体类Soldier是策略模式中的环境类,等级类是策略模式中的策略类。
4 桥接模式使用场景
(1)如果一个系统需要在抽象化和具体化之间增加更多的灵活性,避免在两个层次之间建立静态的继承关系,通过桥接模式可以使它们在抽象层建立一个关联关系。
(2)“抽象部分”和“实现部分”可以以继承的方式独立扩展而互不影响,在程序运行时可以动态将一个抽象化子类的对象和一个实现化子类的对象进行组合,即系统需要对抽象化角色和实现化角色进行动态耦合。
(3)一个类存在两个(或多个)独立变化的维度,且这两个(或多个)维度都需要独立进行扩展。
(4)对于那些不希望使用继承或因为多层继承导致系统类的个数急剧增加的系统,桥接模式尤为适用。
5 参考资料
《大话设计模式》
https://www.cnblogs.com/lfxiao/p/6815760.html
https://www.cnblogs.com/lixiuyu/p/5923160.html