老生常谈:桥接模式
对于B/S架构来说,我一直想结合实际开发环境来学习设计模式,感觉这样印象会更加深,起码你知道在什么场合下应用它。之前也写过十多篇设计模式的文章,之所以没有写完,主要因为经验不够,感觉不到其它设计模式在B/S架构中的应用场合和威力,这篇我用桥接模式实现一个图书销售后计算最终售价的模块。
假想案例说明:出版社在出版的书后会在多家书店销售,假如所有的书的销售都由一个总公司负责销售,它可以控制书的销售方式,例如:计算机书打八折,生活类的打七折等。同时公司可以选择地区经销商做为二级代理,二级代理都必须服从公司的促销方式,如果公司打折,二级代理也要打折,否则二级代理不能私自打折。同时总公司会根据二级代理的销售业绩分为不同的二级代理,例如:钻石,黄金,VIP等,它们在得到的回扣上会有不同。
需要的知识:"开-闭"原则(OCP)以及组合/聚合复用原则(CARP)。至于组合模式可以参考这篇文章:老生常谈:组合模式
结构图:这里我用图书销售价格计算模块来具体说明,并不是标准图。
桥接模式的定义:【GOF95】桥梁模式的用意是"将抽象化(Abstraction)与实现化(Implementation)脱耦,使得二者可以独立地变化"。这句话有三个关键词,也就是抽象化、实现化和脱耦。
1:抽象化:存在于多个实体中的共同的概念性联系,就是抽象化。作为一个过程,抽象化就是忽略一些信息,从而把不同的实体当做同样的实体对待【LISKOV94】。可以是接口也可以是抽象类,用来定义一系列的操作定义。
2:实现化:抽象化给出的具体实现,就是实现化。也是平常说的具体类,负责实现抽象类或者是接口定义的方法等。
3:脱耦:所谓耦合,就是两个实体的行为的某种强关联。而将它们的强关联去掉,就是耦合的解脱,或称脱耦。在这里,脱耦是指将抽象化和实现化之间的耦合解脱开,或者说是将它们之间的强关联改换成弱关联。将两个角色之间的继承关系改为聚合关系,就是将它们之间的强关联改换成为弱关联。因此,桥梁模式中的所谓脱耦,就是指在一个软件系统的抽象化和实现化之间使用组合/聚合关系而不是继承关系,从而使两者可以相对独立地变化。可以看出,这个系统含有两个等级结构,也就是:
1):由抽象化角色和修正抽象化角色组成的抽象化等级结构。 在结构图上由抽象化角色book,修正抽象化角色lifebook和computerbook构成。
2):由实现化角色和两个具体实现化角色所组成的实现化等级结构。在结构图上由diamondProxy,goldProxy,VIPProxy构成。
桥梁模式所涉及的角色有:这里为了看的清楚些,我加上各部分的示例代码。
1:抽象化(Abstraction)角色:抽象化给出的定义,并保存一个对实现化对象的引用。
{
abstract public double promotionPrice();
public proxy _proxy;
/// <summary>
/// 图书名称
/// </summary>
public string bookName
{
get;
set;
}
}
2:修正抽象化(Refined Abstraction)角色:扩展抽象化角色,改变和修正父类对抽象化的定义。
{
/// <summary>
/// 计算机图书打八折
/// </summary>
/// <param name="bookPrice">图书原价</param>
/// <returns>经过图书正常打折以及代理商后的价格</returns>
public override double promotionPrice()
{
//throw new NotImplementedException();
bookPrice = bookPrice * 0.8;
bookPrice = base._proxy.proxyPrice(bookPrice);
return bookPrice;
}
/// <summary>
/// 图书价格
/// </summary>
double bookPrice;
/// <summary>
/// 构造函数
/// </summary>
/// <param name="_bookPrice"></param>
public computerBook(double _bookPrice)
{
this.bookPrice = _bookPrice;
base.bookName = "C#入门经典";
}
}
3:实现化(Implementor)角色:这个角色给出实现化角色的接口,但不给出具体的实现。必须指出的是,这个接口不一定和抽象化角色的接口定义相同,实际上,这两个接口可以非常不一样。实现化角色应当只给出底层操作,而抽象化角色应当只给出基于底层操作的更高一层的操作。
{
abstract public double proxyPrice(double promotionPrice);
/// <summary>
/// 代理商名称
/// </summary>
public string proxyName
{
get;
set;
}
/// <summary>
/// 代理商级别
/// </summary>
public string proxyTye
{
get;
set;
}
}
4:具体实现化(Concrete Implementor)角色:这个角色给出实现化角色接口的具体实现。
{
/// <summary>
/// 钻石级代理商的价格为图书正常打完折后的价格的八折
/// </summary>
/// <param name="promotionPrice"></param>
/// <returns></returns>
public override double proxyPrice(double promotionPrice)
{
return promotionPrice * 0.8;
}
//decimal promotionPrice;
public diamondProxy()
{
base.proxyName = "北京代理";
base.proxyTye = "钻石代理";
}
}
代码以及结构图分析:可以看出在桥接模式中使用了组合模式,如果不用组合模式呢,当然也就不是桥接了,下面的结构图是用继承实现的方案,在结构图中有一个总的计算价格的接口IBook,下面是两种打折方案的子类接IPromotion,IProxy。
使用继承的结构图如下:
使用继承的代码如下:
{
/// <summary>
/// 图书的价格接口
/// </summary>
/// <returns></returns>
double price();
}
interface IPromotion:IBook
{
/// <summary>
/// 图书正常打折后的价格接口
/// </summary>
/// <returns></returns>
double promotionPrice();
}
interface IProxy:IBook
{
/// <summary>
/// 代理商打折后的价格接口
/// </summary>
/// <returns></returns>
double proxyPrice();
}
public class computerBook:IPromotion
{
/// <summary>
/// 计算机图书打八折后的价格
/// </summary>
/// <returns></returns>
public double promotionPrice()
{
return bookprice * 0.8;
}
double bookprice;
public computerBook(double _bookprice)
{
this.bookprice = _bookprice;
}
/// <summary>
/// 返回打折后的图片价格
/// </summary>
/// <returns></returns>
public double price()
{
return this.promotionPrice();
}
}
public class diamondProxy:IProxy
{
/// <summary>
/// 钻石代理商打八折后的价格
/// </summary>
/// <returns></returns>
public double proxyPrice()
{
return bookprice * 0.8;
}
double bookprice;
public diamondProxy(double _bookprice)
{
this.bookprice = _bookprice;
}
/// <summary>
/// 返回打折后的图片价格
/// </summary>
/// <returns></returns>
public double price()
{
return this.proxyPrice();
}
}
桥接与使用继承的比较:
1:在接口数量上来看,桥接要少一个,继承用了三个;
2:从客户端调用程序来看:可以看出桥接模式要简洁的多。
1):使用桥接的客户端调用:
//原始价格:100RMB
//代理商级别:钻石,打折为八折
//计算最后实际等到的金额
book _Book = new computerBook(100.0);
_Book._proxy = new diamondProxy();
double proxyPrice = _Book.promotionPrice();
2):使用继承的客户端调用:
IBook _Book = new computerBook(100.0);
//得到计算机打完折后的价格
double price = _Book.price();
//实例化一位钻石代理商
IBook _BookProxy = new diamondProxy(price);
//把正常打折后的图片价格做为基数来计算最终价格
price = _BookProxy.price();
3:从两者的业务逻辑代码来看,显然是桥接模式精简不少,起码在代码量上是这样。
桥梁模式适用场合:根据上面的分析,在以下的情况下应当使用桥梁模式:
1:如果一个系统需要在构件的抽象化角色和具体化角色之间增加更多的灵活性,避免在两个层次之间建立静态的联系。
2:设计要求实现化角色的任何改变不应当影响客户端,或者说实现化角色的改变对客户端是完全透明的。
3:一个构件有多于一个的抽象化角色和实现化角色,系统需要它们之间进行动态耦合。
总结:本文通过一个具体的书店销售图书中计算图书最终价格模块来说明桥接模式的应用,系统可以轻松的在图书打折方式和代理商之间扩展及维护。
注:
本文引用:《Java与模式》