学点设计模式-桥梁模式

桥梁模式

今天我要说说我自己,梦想中的我自己,我身价过亿,有两个大公司,一个是房地产公司,一个是服装制造
业,这两个公司都很赚钱,天天帮我在累加财富,其实是什么公司我倒是不关心,我关心的是是不是在赚钱,赚
了多少,这才是我关心的,我是商人呀,唯利是图是我的本性,偷税漏税是我的方法,欺上瞒下、压榨员工血汗
我是的手段嘛,我先用类图表示一下我这两个公司:

类图很简单,声明了一个Corp 抽象类,定义一个公司的抽象模型,公司首要是赚钱的,不赚钱谁开公司,
做义务或善举那也是有背后利益支撑的,我还是赞成这句话“天下熙熙,皆为利来;天下壤壤,皆为利往”,那
我们先看Corp 类的源代码:

package com.cbf4life.BridgePattern;

/**
 * @author cbf4Life cbf4life@126.com I'm glad to share my knowledge with you
 *         all. 定义一个公司的抽象类
 */
public abstract class Corp {

	/*
	 * 是公司就应该有生产把,甭管是什么软件公司还是制造业公司 那每个公司的生产的东西都不一样,所以由实现类来完成
	 */
	protected abstract void produce();

	/*
	 * 有产品了,那肯定要销售呀,不销售你公司怎么生存
	 */
	protected abstract void sell();

	// 公司是干什么的?赚钱的呀,不赚钱傻子才干
	public void makeMoney() {
		// 每个公司都是一样,先生产
		this.produce();
		// 然后销售
		this.sell();
	}
}


合适的方法存在合适的类中,这个基本上是每本Java 基础书上都会讲的,但是到实际的项目中应用的时候
就不是这么回事儿了,正常的很。我们继续看两个实现类如何实现的,先看HouseCorp 类,这个是我最赚钱的
公司:

package com.cbf4life.BridgePattern;

/**
 * @author cbf4Life cbf4life@126.com I'm glad to share my knowledge with you
 *         all. 房地产公司,按照翻译来说应该叫realty corp,这个是比较准确的翻译
 *         但是我问你房地产公司翻译成英文,你第一反应什么?对嘛还是house corp!
 */
public class HouseCorp extends Corp {
	// 房地产公司就是盖房子
	protected void produce() {
		System.out.println("房地产公司盖房子...");
	}

	// 房地产卖房子,自己住那可不赚钱
	protected void sell() {
		System.out.println("房地产公司出售房子...");
	}

	// 房地产公司很High了,赚钱,计算利润
	public void makeMoney() {
		super.makeMoney();
		System.out.println("房地产公司赚大钱了...");
	}
}


然后看服装公司,虽然不景气,但好歹也是赚钱的:

package com.cbf4life.BridgePattern;

/**
 * @author cbf4Life cbf4life@126.com I'm glad to share my knowledge with you
 *         all. 服装公司,这个行当现在不怎么样
 */
public class ClothesCorp extends Corp {
	// 服装公司生产的就是衣服了
	protected void produce() {
		System.out.println("服装公司生产衣服...");
	}

	// 服装公司买服装,可只卖服装,不买穿衣服的模特
	protected void sell() {
		System.out.println("服装公司出售衣服...");
	}

	// 服装公司不景气,但怎么说也是赚钱行业也
	public void makeMoney() {
		super.makeMoney();
		System.out.println("服装公司赚小钱...");
	}
}


两个公司都有了,那肯定有人会关心两个公司的运营情况呀,我自己怎么也要知道它是生产什么的,赚多少
钱吧,那看看这个Client.java 是什么样子的:

package com.cbf4life.BridgePattern;

/**
 * @author cbf4Life cbf4life@126.com I'm glad to share my knowledge with you
 *         all. 我要关心我自己的公司了
 */
public class Client {
	public static void main(String[] args) {
		System.out.println("-------房地产公司是这个样子运行的-------");
		// 先找到我的公司
		HouseCorp houseCorp = new HouseCorp();
		// 看我怎么挣钱
		houseCorp.makeMoney();
		System.out.println("\n");
		System.out.println("-------服装公司是这样运行的-------");
		ClothesCorp clothesCorp = new ClothesCorp();
		clothesCorp.makeMoney();
	}
}

 

看,这个代码很简单,运行结果如下:


上述代码完全可以描述我现在的公司,但是你要知道万物都是运动的,你要用运动的眼光看问题,我公司也
会发展,终于在有一天我觉得赚钱速度太慢,于是我上下疏通,左右打关系,终于开辟了一条赚钱的康庄大道:
生产山寨产品,什么产品呢?就是市场上什么牌子的东西火爆我生产什么牌子的东西,甭管是打火机还是电脑,
只要它火爆,我就生产,赚过了高峰期就换个产品,打一枪换一个牌子,不承担售后成本、也不担心销路问题,
我只有正品的十分之一的价格,你买不买?哈哈,赚钱呀!
企业的方向定下来了,通过调查,市场上前段时间比较火爆的是苹果iPod,那咱就生产这个,把服装厂该
成iPod 生产厂,看类图的变化:

好,我的企业改头换面了,开始生产iPod 产品了,看我IPodCorp 类的实现:

package com.cbf4life.BridgePattern;

/**
 * @author cbf4Life cbf4life@126.com I'm glad to share my knowledge with you
 *         all. 我是山寨老大,你流行啥我就生产啥 现在流行iPod
 */
public class IPodCorp extends Corp {
	// 我开始生产iPod了
	protected void produce() {

		System.out.println("我生产iPod...");
	}

	// 山寨的iPod很畅销,便宜呀
	protected void sell() {
		System.out.println("iPod畅销...");
	}

	// 狂赚钱
	public void makeMoney() {
		super.makeMoney();
		System.out.println("我赚钱呀...");
	}
}


这个厂子修改完毕了,我这个董事长还要去看看到底生产什么的,看这个Client.java 程序:

package com.cbf4life.BridgePattern;

/**
 * @author cbf4Life cbf4life@126.com I'm glad to share my knowledge with you
 *         all. 我要关心我自己的公司了
 */
public class Client2 {
	public static void main(String[] args) {
		House house = new House();
		System.out.println("-------房地产公司是这个样子运行的-------");
		// 先找到我的公司
		HouseCorpModify houseCorp = new HouseCorpModify(house);
		// 看我怎么挣钱
		houseCorp.makeMoney();
		System.out.println("\n");
		// 山寨公司生产的产品很多,不过我只要指定产品就成了
		System.out.println("-------山寨公司是这样运行的-------");
		ShanZhaiCorp shanZhaiCorp = new ShanZhaiCorp(new Clothes());
		shanZhaiCorp.makeMoney();
	}
}


确实,只用修改了几句话,我的服装厂就开始变成山寨iPod 生产车间,然后就看我的财富在积累积累,你
想呀山寨的东西不需要特别的销售渠道(正品到哪里我就到哪里),不需要维修成本(大不了给你换个,你还想
咋地,过了高峰期我就改头换面了你找谁维修去,投诉?投诉谁呢?),不承担广告成本(正品在打广告,我还
需要吗?需要吗?),但是我也有犯愁的时候,我这是个山寨工厂,要及时的生产出市场上流行产品,转型要快,
要灵活,今天从生产iPod 转为生产MP4,明天再转为生产上网本,这个都需要灵活的变化,不要限制的太死,
那问题来了,每次我的厂房,我的工人,我的设备都在,不可能每次我换个山寨产品我的厂子就彻底不要了,这
不行,成本忒高了点,那怎么办?
Thinking,Thinking….,I got an idea!,这样设计:

Corp 类和Product 类建立一个关联关系,可以彻底解决我以后山寨公司生产产品的问题,看程序说话,
先看Product 抽象类:

package com.cbf4life.BridgePattern;

/**
 * @author cbf4Life cbf4life@126.com I'm glad to share my knowledge with you
 *         all. 这是我整个集团公司的产品类
 */
public abstract class Product {
	// 甭管是什么产品它总要是能被生产出来
	public abstract void beProducted();

	// 生产出来的东西,一定要销售出去,否则扩本呀
	public abstract void beSelled();
}


 

package com.cbf4life.BridgePattern;

/**
 * @author cbf4Life cbf4life@126.com I'm glad to share my knowledge with you
 *         all. 这是我集团公司盖的房子
 */
public class House extends Product {
	// 豆腐渣就豆腐渣呗,好歹也是个房子
	public void beProducted() {
		System.out.println("生产出的房子是这个样子的...");
	}

	// 虽然是豆腐渣,也是能够销售出去的
	public void beSelled() {
		System.out.println("生产出的房子卖出去了...");
	}
}


 

package com.cbf4life.BridgePattern;

/**
 * @author cbf4Life cbf4life@126.com I'm glad to share my knowledge with you
 *         all. 我集团公司生产的衣服
 */
public class Clothes extends Product {
	public void beProducted() {

		System.out.println("生产出的衣服是这个样子的...");
	}

	public void beSelled() {
		System.out.println("生产出的衣服卖出去了...");
	}
}


下面是 iPod 产品类:

package com.cbf4life.BridgePattern;

/**
 * @author cbf4Life cbf4life@126.com I'm glad to share my knowledge with you
 *         all. 生产iPod了
 */
public class IPod extends Product {
	public void beProducted() {
		System.out.println("生产出的iPod是这个样子的...");
	}

	public void beSelled() {
		System.out.println("生产出的iPod卖出去了...");
	}
}


产品类是有了,那我们再看Corp 抽象类:

package com.cbf4life.BridgePattern;

/**
 * @author cbf4Life cbf4life@126.com I'm glad to share my knowledge with you
 *         all. 定义一个公司的抽象类
 */
public abstract class CorpModify {
	// 定义一个产品对象,抽象的了,不知道具体是什么产品
	private Product product;

	// 构造函数,由子类定义传递具体的产品进来
	public CorpModify(Product product) {
		this.product = product;
	}

	// 公司是干什么的?赚钱的呀,不赚钱傻子才干
	public void makeMoney() {
		// 每个公司都是一样,先生产
		this.product.beProducted();
		// 然后销售
		this.product.beSelled();
	}
}


这里多了个有参构造,其目的是要继承的子类都必选重写自己的有参构造函数,把产品类传递进来,再看子
类HouseCorp 的实现:

package com.cbf4life.BridgePattern;

/**
 * @author cbf4Life cbf4life@126.com I'm glad to share my knowledge with you
 *         all. 房地产公司,按照翻译来说应该叫realty corp,这个是比较准确的翻译
 *         但是我问你房地产公司翻译成英文,你第一反应什么?对嘛还是house corp!
 */
public class HouseCorpModify extends CorpModify {
	// 定义传递一个House产品进来
	public HouseCorpModify(House house) {
		super(house);
	}

	// 房地产公司很High了,赚钱,计算利润
	public void makeMoney() {
		super.makeMoney();
		System.out.println("房地产公司赚大钱了...");
	}
}


 

package com.cbf4life.BridgePattern;

/**
 * @author cbf4Life cbf4life@126.com I'm glad to share my knowledge with you
 *         all. 我是山寨老大,你流行啥我就生产啥
 */
public class ShanZhaiCorp extends CorpModify {
	// 产什么产品,不知道,等被调用的才知道
	public ShanZhaiCorp(Product product) {
		super(product);
	}

	// 狂赚钱
	public void makeMoney() {
		super.makeMoney();
		System.out.println("我赚钱呀...");
	}
}


HouseCorp 类和ShanZhaiCorp 类的区别是在有参构造的参数类型上,HouseCorp 类比较明确,我就是
只要House 类,所以直接定义传递进来的必须是House 类, 一个类尽可能少的承担职责,那方法也是一样,
既然HouseCorp 类已经非常明确只生产House 产品,那为什么不定义成House 类型呢?ShanZhaiCorp 就
不同了,它是确定不了生产什么类型。
好了,两大对应的阵营都已经产生了,那我们再看Client 程序

package com.cbf4life.BridgePattern;

/**
 * @author cbf4Life cbf4life@126.com I'm glad to share my knowledge with you
 *         all. 我要关心我自己的公司了
 */
public class Client3 {
	public static void main(String[] args) {
		House house = new House();
		System.out.println("-------房地产公司是这个样子运行的-------");
		// 先找到我的公司
		HouseCorpModify houseCorp = new HouseCorpModify(house);
		// 看我怎么挣钱
		houseCorp.makeMoney();
		System.out.println("\n");
		// 山寨公司生产的产品很多,不过我只要制定产品就成了
		System.out.println("-------山寨公司是这样运行的-------");
		// ShanZhaiCorp shanZhaiCorp = new ShanZhaiCorp(new Clothes());
		ShanZhaiCorp shanZhaiCorp = new ShanZhaiCorp(new IPod());
		shanZhaiCorp.makeMoney();
	}
}


这个山寨公司的前身是生产衣服的,那我现在要修改一下,生产iPod,看如下的变化:

package com.cbf4life.BridgePattern;

/**
 * @author cbf4Life cbf4life@126.com I'm glad to share my knowledge with you
 *         all. 我要关心我自己的公司了
 */
public class ClientIpad {
	public static void main(String[] args) {
		System.out.println("-------房地产公司是这个样子运行的-------");
		// 先找到我的公司
		HouseCorp houseCorp = new HouseCorp();
		// 看我怎么挣钱
		houseCorp.makeMoney();
		System.out.println("\n");
		System.out.println("-------山寨公司是这样运行的-------");
		IPodCorp iPodCorp = new IPodCorp();
		iPodCorp.makeMoney();
	}
}


看代码上的黄色底色的代码,就修改了这一一句话代码就完成了生产产品的转换。那我们深入的思考一下,
既然万物都是运动的,我现在只有房地产公司和山寨公司,那以后我会不会增加一些其他的公司呢?或者房地产
公司会不会对业务进行细化,比如分为公寓房公司,别墅公司,以及商业房公司等等呢?那我告诉你,会的,绝
对的会的,但是你发觉没,这种变化对我们上面的类图没有大的修改,充其量是扩展,你看呀:
增加公司,你要么继承Corp 类,要么继承HouseCorp 或ShanZhaiCorp,不用再修改原有的类了;
增加产品,继承Product 类,或者继承House 类,你要把房子分为公寓房、别墅、商业用房等等;
你都是在扩展,唯一你要修改的就是Client 类,你类都增加了哪能不修改调用呢,也就是说Corp 类和
Product 类都可以自由的扩展,而不会对整个应用产生太的变更,这就是桥梁模式。
为什么叫桥梁模式?我们看一下桥梁模式的通用类图:

看到中间那根带箭头的线了吗?是不是类似一个桥,连接了两个类?所以就叫桥梁模式。我们再把桥梁模式
的几个概念熟悉一下,大家有没有注意到我把Corp类以及它的两个实现类放到了Abstraction包中,把House
以及相关的三个实现类放到了Implementor 包中, 这两个包分别对应了桥梁模式的业务抽象角色
(Abstraction)和业务实现角色(Implementor),这两个角色我估计没几个人能说的明白,特别是看了“四
人帮”的书或者是那本非常有名的、比砖头还要厚的书,你会越看越糊涂,忒专业化,有点像看政府的红头文件,
什么都说了,可好像又什么都没有说。这两个角色大家只要记住一句话就成:业务抽象角色引用业务实现角色,
或者说业务抽象角色的部分实现是由业务实现角色完成的,很简单,别想那么复杂了。
桥梁模式的优点就是类间解耦,我们上面已经提到,两个角色都可以自己的扩展下去,不会相互影响,这个
也符合OCP 原则。
今天说到桥梁模式,那就多扯几句,大家对类的继承有什么看法吗?继承的优点有很多,可以把公共的方法
或属性抽取,父类封装共性,子类实现特性,这是继承的基本功能,缺点有没有?有,强关联关系,父类有个方
法,你子类也必须有这个方法,是不可选择的,那这会带来扩展性的问题,我举个简单的例子来说明这个问题:
Father 类有一个方法A,Son 继承了这个方法,然后GrandSon 也继承了这个方法,问题是突然有一天Son
要重写父类的这个方法,他敢做吗?绝对不敢!GrandSon 可是要用从Father 继承过来的方法A,你修改了,
那就要修改Son 和GrandSon 之间的关系,那这个风险就大了去。
今天讲的这个桥梁模式就是这一问题的解决方法,桥梁模式描述了类间弱关联关系,还说上面的那个例子,
Fater 类完全可以把可能会变化的方法放出去,Son 子类要有这个方法很简答,桥梁搭过去,获得这个方法,
GrandSon 也一样,即使你Son 子类不想使用这个方法了,也没关系,对GrandSon 不产生影响,他不是从你
Son 中继承来的方法!
继承不能说它不好,非常好,但是有缺点的,我们可以扬长避短,对于比较明确不发生变化的,则通过继承
来完成,若不能确定是否会发生变化的,那就认为是会发生变化,则通过桥梁模式来解决,这才是一个完美的世
界。

 

定义与结构
GOF 在《设计模式》中给桥梁模式的定义为:将抽象部分与它的实现部分分离,使它
们都可以独立地变化。这里的抽象部分和实现部分不是我们通常认为的父类与子类、接口与
实现类的关系,而是组合关系。也就是说,实现部分是被抽象部分调用,以用来完成(实现)
抽象部分的功能。
在《Thinking in Patterns with Java》一书中,作者将抽象部分叫做“front-end”(权且
翻译为“前端”),而实现部分叫做“back-end”(后端)。这种叫法要比抽象实现什么的好理解
多了。
系统设计中,总是充满了各种变数,这是防不慎防的。面对这样那样的变动,你只能去
不停的修改设计和代码,并且要开始新的一轮测试……。
那采取什么样的方式可以较好的解决变化带给系统的影响?你可以分析变化的种类,将
不变的框架使用抽象类定义出来,然后再将变化的内容使用具体的子类来分别实现。这样面
向客户的只是一个抽象类,这种方式可以较好的避免为抽象类中现有接口添加新的实现所带
来的影响,缩小了变化带来的影响。但是这可能会造成子类数量的爆炸,并且在某些时候不
是很灵活。
当这颗继承树上一些子树存在了类似的行为。这意味着这些子树中存在了几乎重复的功
能代码。这时我们不妨将这些行为提取出来,也采用接口的方式提供出来,然后以组合的方
式将服务提供给原来的子类。这样就达到了前端和被使用的后端独立的变化,而且还达到了
后端的重用。

桥梁模式由如下四种角色组成:
1) 抽 象 ( Abstraction ) 角色: 它定义了抽象类的接口而且维护着一个指向实现
(Implementor)角色的引用。
2) 精确抽象(RefinedAbstraction)角色:实现并扩充由抽象角色定义的接口。
3) 实现(Implementor)角色:给出了实现类的接口,这里的接口与抽象角色中的接口可
以不一致。
4) 具体实现(ConcreteImplementor)角色:给出了实现角色定义接口的具体实现。

使用环境与优势
由上面我们分析得来的桥梁模式,可以看出来桥梁模式应该适用于以下环境:
1) 当你的系统中有多个地方要使用到类似的行为,或者是多个类似行为的组合时,可以考
虑使用桥梁模式来提高重用,并减少因为行为的差异而产生的子类。
2) 系统中某个类的行为可能会有几种不同的变化趋势,为了有效的将变化封装,可以考虑
将类的行为抽取出来。
3) 当然上面的情况也可以是这样,行为可能要被不同相似类使用,也可以考虑使用桥梁模
式来实现。
桥梁模式使用了低耦合性的组合代替继承,使得它具备了不少好处:
1) 将可能变化的部分单独封装起来,使得变化产生的影响最小,不用编译不必要的代码。
2) 抽象部分和实现部分可以单独的变动,并且每一部分的扩充都不会破坏桥梁模式搭起来
架子。
3) 对于客户程序来说,你的实现细节是透明的。
Bruce Eckel 在《Thinking in patterns with Java》中提到,可以把桥梁模式当作帮助你
编码前端和后端独立变化的框架。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值