设计模式(三)-结构型模式(2)-桥接模式
一、为何需要桥接模式(Bridge)?
在软件设计中,存在有两个或多个不同维度的模块时,我们需要将这些模块使用到在一起,来实现一个完整的功能。所谓不同维度的意思就是这些模块所负责的职责是不同的,并且它们之间没有多大的关联性。
根据设计原则中的单一职责原则:各自模块应当保持各自的分工任务。比如在画图系统中存在形状和画笔的两个维度。形状负责的是它的模样(矩形、圆形、三角形等),而画笔负责的是铅笔、毛笔、圆珠笔之类的选型。
倘若在某个维度的类里对另一维度的类有改写的痕迹,就会对这个功能的扩展性以及维护性受到影响。
比如设计一个抽奖系统。员工、员工家属都参与抽奖,奖金分为一二三等奖,最后公布抽奖名单。其中中奖者和奖金是两个不同维度的模块。
以下例子就是个错误的设计,暴露了对代码的扩展和维护困难的弊端。
// 方式一:将中奖者类和奖金类作为继承关系,互相受到影响
public abstract class Bounds
{
public abstract string getOneGift();//一等奖礼品
public abstract string getTwoGift();//二等奖礼品
//扩展定义三等奖、安慰奖礼品等...
}
public class LuckyStaff:Bounds
{
public override string getOneGift()
{
return "iPhone 15 Pro";
}
public override string getTwoGift()
{
return "HUAWEI MatePad Pro 13.2";
}
//扩展实现 n 个 getXXXGift...
}
//扩展其他中奖者:public class LuckyFamilyMember:Bounds...
//方式一:违反了单一职责原则:
//LuckyStaff 类重写或实现了 Bounds 类的方法,不应该直接处理 Bounds 的事情。
//方式二:将中奖者和奖金具体类直接作为对象组合关系,互相受到影响
public class LuckyStaff
{
private Bounds onebounds;
private Bounds twobounds;
//...
public string getOneGift()
{
return onebounds.getOneGift();
}
public string getTwoGift()
{
return twobounds.getTwoGift();
}
//...
}
//扩展其他中奖者:public class LuckyFamilyMember...
//方式二违反了依赖倒置原则和迪米特法则:
//违反依赖倒置原则:当修改 Bounds 类的方法(如移除方法),就会影响 LuckyStaff
//违反迪米特法则:Bounds 和 LuckyStaff 没有多大关联性,却在代码中 LuckyStaff 过于依赖 Bounds
从以上代码可看出,如果以后再扩展不同身份的中奖者和不同的奖金类别,就得需要在原来的代码结构中进行添加和修改,或者再创建一个类重新实现一遍。如果扩展的东西越多,后果将不堪设想。于是为了解决这个问题,这时候就需要用到桥接模式了。
所谓桥接模式就是,把不同维度的模块之间进行分离,保持各自的独立性,然后再把这些模块进行桥接。这样子即使更改了哪个模块,也不会受到影响。
不同维度模块的主要分离为:抽象化和实现化进行分离:
抽象化:抽象化和修正抽象化。(抽象化派生给修正抽象化)
实现化:实现化和具体实现化。(实现化派生给具体实现化)
关系:实现化服务于抽象化。
以下将会解释这个概念。
特点:
将抽象化与实现化进行脱耦(也就是分离),使得他们两个互相独立,不会影响到彼此。
结构:
抽象化接口(Abstraction):定义高层模块的接口。(比如有设置形状大小等的抽象方法。作为形状的基类接口)
修正抽象化类(Refined Abstraction):扩展抽象化,改变或实现抽象化的接口。(比如扩展为矩形类、三角形类,并实现大小的接口;作为 Abstraction 的派生类)
实现化接口(Implementor):定义底层模块的接口。(比如画笔粗细等的抽象方法;作为画笔的基类接口)
具体实现化类(Concrete Implementor):扩展实现化,对实现化接口进行实现。(比如扩展为铅笔类、毛笔类,并实现粗细的接口;作为 Implementor 的派生类)
适合应用场景特点:
- 可扩展性:不同维度的模块都潜在可扩展性,需要进行抽象化和实现化的分离。(比如抽象化为形状接口、实现化为画笔接口)
- 服务与运用关系:抽象化属于高层模块,运用底层模块。实现化属于底层模块,服务高层模块。(比如画笔是为形状服务的)
- 桥接作用:主要针对于不同维度的模块。修正抽象化通常包装一个实现化接口的派生实例。(派生指的是具体实现化)注重对象结构模型。(把形状和画笔进行桥接)
二、例子
需求:
年底快到了,公司将要组织一场年会活动,以感谢大家这一年来为公司做出了贡献。老板做出决定,需要在这场活动中有抽奖环节。于是领导把开发抽奖系统的任务交给你去做。具体需求是:奖金设置一等奖有1名、二等奖有两名、三等奖有三名。第一轮被抽到的幸运者获得一等奖,第二轮是二等奖,第三轮是三等奖。后续可能还有安慰奖啥的,领导和老板还在策划中。抽奖结束后,需要公布中奖结果。目前参加成员有员工和员工家属。至于老板是否也参加抽奖环节还未定,暂时先补个空,到时候再做决定。
需求分析:
- 中奖者(员工、家属、老板(未定))和奖金(一等奖、二等奖、三等奖、安慰奖啥的(未定))是属于不同维度的模块。
- 奖金服务于中奖者,作为底层模块;而中奖者作为高层模块,运用底层模块。但两者的定义互不影响。即修改中奖者不影响奖金的代码逻辑,反之亦然。
- 本次抽奖活动在于老板是否参与还未定,以及奖金的类别后续在规划中,都属于可扩展的功能需求。
1、定义实现化接口和具体实现化类:
底层模块:奖金
//Implementor:实现化接口(奖金)
interface IBonus
{
string getGift();
}
//ConcreteImplementor:具体实现化类(一等奖)
class OneBonus : IBonus
{
public string getGift()
{
return "iPhone 15 Pro";
}
}
//ConcreteImplementor:具体实现化类(二等奖)
class TwoBonus : IBonus
{
public string getGift()
{
return "HUAWEI MatePad Pro 13.2";
}
}
//ConcreteImplementor:具体实现化类(三等奖)
class ThreeBonus : IBonus
{
public string getGift()
{
return "Xiaomi 14 Pro";
}
}
//可能还存在安慰奖、参与奖啥的...
2、定义抽象化接口和修正抽象化类:
高层模块:中奖者
//Abstraction:抽象化接口(中奖者)
interface ILucker
{
string Name { get; }
void showResult();
}
//Refined Abstraction:修正抽象化类(员工中奖者)
class LuckyStaff : ILucker
{
private IBonus iBonus;
private string _name;
public string Name { get { return _name; } }
public LuckyStaff(string name,IBonus iBonus)
{
this.iBonus = iBonus;
_name = name;
}
public void showResult()
{
Console.WriteLine("恭喜 {0}\t 获得一部 {1} 手机 !", Name, iBonus.getGift());
}
}
//Refined Abstraction:修正抽象化类(可扩展员工家属中奖者)
class LuckyFamilyMember :ILucker
{
private IBonus iBonus;
private string _name;
public string Name { get { return _name; } }
public LuckyFamilyMember(string name, IBonus iBonus)
{
this.iBonus = iBonus;
_name = name;
}
public void showResult()
{
Console.WriteLine("恭喜 {0}家属\t 获得一部 {1} 手机 !", Name, iBonus.getGift());
}
}
3、主程序(抽奖名单,公布结果):
class Program
{
static void Main(string[] args)
{
OneBonus oneBounds = new OneBonus();
TwoBonus twoBonus = new TwoBonus();
ThreeBonus threeBnus = new ThreeBonus();
//抽奖名单...
List<ILucker> lucker = new List<ILucker>(){
//第一轮抽奖
new LuckyFamilyMember("CRongQ",oneBounds),
//第二轮抽奖
new LuckyStaff("张三",twoBonus),
new LuckyStaff("小蓝",twoBonus),
//第三轮抽奖
new LuckyStaff("小红",threeBnus),
new LuckyFamilyMember("李四",threeBnus),
new LuckyStaff("赵五",threeBnus)
};
Console.WriteLine("抽奖结果:\n");
foreach (var item in lucker)
{
item.showResult();
}
Console.ReadLine();
}
}
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!