结构型模式之桥接模式

桥接(Bridge)是用于把抽象化与实现化解耦,使得二者可以独立变化.这种类型的设计模式属于结构型模式,它通过提供抽象化与现实化之间的桥接结构,来实现二者的解耦 这种模式涉及到一个作为桥接的接口,使得实体类的功能独立于接口实现类。这两种类型的类可被结构化改变而互不影响

介绍

意图: 将抽象化部分与实现部分分离,使它们都可以独立的变化

主要解决:在有多种可能会变化的情况下,用继承会造成类爆炸问题,扩展起来不灵活

何时使用:实现系统可能有多个角度分类,每一种角度都有可能变化

如何解决:把这种多角度分类分离出来,让它们独立变化,减少他们之间耦合

关键代码:抽象类依赖实现类

应用实例:墙上的开关,可以看到的开关是抽象的,不用管里面具体怎么实现的。 优点:

  1. 抽象与实现的分离
  2. 优秀的扩展能力
  3. 实现细节对客户透明

缺点: 桥接模式的引入会增加系统的理解与设计难度,由于聚合关联关系建立在抽象层,要求开发者针对抽象进行设计与编程

注意事项:对于两个独立变化的维度,使用桥接模式再适合不过了。

示例

考虑这样一个实际的业务功能:发送提示消息。基本上所有带业务流程处理的系统都会有这样的功能,比如某人有新的工作了,需要发送一条消息提示他。 从业务上看,消息又分成普通消息、加急消息和特急消息多种,不同的消息类型,业务功能处理是不一样的,比如加急消息是在消息上添加加急,而特急消息除了添加特急外,还会做一条催促的记录,多久不完成会继续催促。从发送消息的手段上看,又有系统内短消息、手机短消息、邮件等等。 现在要实现这样的发送提示消息的功能,该如何实现呢?

不用模式的解决方案

1. 实现简化版本

先考虑实现一个简单点的版本,比如:消息先只是实现发送普通消息,发送的方式呢,先实现系统内短消息和邮件。其它的功能,等这个版本完成过后,再继续添加,这样先把问题简单化,实现起来会容易一点。

(1)由于发送普通消息会有两种不同的实现方式,为了让外部能统一操作,因此,把消息设计成接口,然后由两个不同的实现类,分别实现系统内短消息方式和邮件发送消息的方式。此时系统结构如图所示:

2.实现发送加急消息

上面的实现,看起来很简单,对不对。接下来,添加发送加急消息的功能,也有两种发送的方式,同样是站内短消息和Email的方式。 加急消息的实现跟普通消息不同,加急消息会自动在消息上添加加急,然后再发送消息;另外加急消息会提供监控的方法,让客户端可以随时通过这个方法来了解对于加急消息处理的进度,比如:相应的人员是否接收到这个信息,相应的工作是否已经开展等等。因此加急消息需要扩展出一个新的接口,除了基本的发送消息的功能,还需要添加监控的功能,这个时候,系统的结构如图所示

上图可以看出:

  1. 新扩展的加急消息类继承自消息类
  2. 相应的实现方式还是发送站内消息和Email两种,同样需要两个实现类分别实现这两种方式

3.有何问题

上面这样实现,好像也能满足基本的功能要求,可是这么实现好不好呢?有没有什么问题呢? 如果我再添加特急消息,只要没有完成,就直接催促,不需要看处理进程.再通过继承的方式来扩展,就会很不方便,没扩展一个消息处理,都需要实现两种处理方式,很痛苦. 更可怕的是,我还想添加一种新的发送消息方式,那以为着有3*3=9中搭配

结论: 采用通过继承来扩展的实现方式,有个明显的缺点:扩展消息的种类不太容易,不同种类的消息具有不同的业务,也就是有不同的实现,在这种情况下,每个种类的消息,需要实现所有不同的消息发送方式。如果要新加入一种消息的发送方式,那么会要求所有的消息种类,都要加入这种新的发送方式的实现

解决方案

桥接模式来解决

仔细分析上面的示例,根据示例的功能要求,示例的变化具有两个纬度,一个纬度是抽象的消息,包括普通消息、加急消息和特急消息,这几个抽象的消息本身就具有一定的关系,加急消息和特急消息会扩展普通消息;另一个纬度在具体的消息发送方式上,包括站内短消息、Email和手机短信息,这几个方式是平等的,可被切换的方式。这两个纬度一共可以组合出9种不同的可能性来,它们的关系如下图所示:

现在出现问题的根本原因,就在于消息的抽象和实现是混杂在一起的,这就导致了,一个纬度的变化,会引起另一个纬度进行相应的变化,从而使得程序扩展起来非常困难。

要想解决这个问题,就必须把这两个纬度分开,也就是将抽象部分和实现部分分开,让它们相互独立,这样就可以实现独立的变化,使扩展变得简单。

桥接模式通过引入实现的接口,把实现部分从系统中分离出去;那么,抽象这边如何使用具体的实现呢?肯定是面向实现的接口来编程了,为了让抽象这边能够很方便的与实现结合起来,把顶层的抽象接口改成抽象类,在里面持有一个具体的实现部分的实例。

这样一来,对于需要发送消息的客户端而言,就只需要创建相应的消息对象,然后调用这个消息对象的方法就可以了,这个消息对象会调用持有的真正的消息发送方式来把消息发送出去。也就是说客户端只是想要发送消息而已,并不想关心具体如何发送。

Abstraction:抽象部分的接口。通常在这个对象里面,要维护一个实现部分的对象引用,在抽象对象里面的方法,需要调用实现部分的对象来完成。这个对象里面的方法,通常都是跟具体的业务相关的方法。

RefinedAbstraction:扩展抽象部分的接口,通常在这些对象里面,定义跟实际业务相关的方法,这些方法的实现通常会使用Abstraction中定义的方法,也可能需要调用实现部分的对象来完成。

Implementor:定义实现部分的接口,这个接口不用和Abstraction里面的方法一致,通常是由Implementor接口提供基本的操作,而Abstraction里面定义的是基于这些基本操作的业务方法,也就是说Abstraction定义了基于这些基本操作的较高层次的操作。

ConcreteImplementor:真正实现Implementor接口的对象。

示例代码

步骤1: 实现部分定义的接口

@interface MessageImplementor : NSObject

//实现发送消息的统一接口
- (void)sendMessage:(NSString *)message user:(NSString *)user;

@end
复制代码

步骤2: 抽象部分定义的接口

//抽象的消息对象
@interface AbstractMessage : NSObject

- (void)abstractMessage:(MessageImplementor *)impl;

- (void)sendMessage:(NSString *)message toUser:(NSString *)user;

@end
@interface AbstractMessage()

/*持有一个实现部分的对象*/
@property (nonatomic,strong) MessageImplementor *impl;

@end

@implementation AbstractMessage

//构造方法,传入实现部分的对象
- (void)abstractMessage:(MessageImplementor *)impl
{
    self.impl = impl;
}
//发送消息,转调实现部分的方法 
- (void)sendMessage:(NSString *)message toUser:(NSString *)user
{
    [self.impl sendMessage:message user:user];
}

复制代码

步骤3:具体的实现返送消息

SMS发送方式

@interface MessageSMS : MessageImplementor

@end

- (void)sendMessage:(NSString *)message user:(NSString *)user
{
    NSLog(@"使用站内短消息的方式,发送消息%@,给%@",message,user);
}
复制代码

Email发送方式

@interface MessageEmail : MessageImplementor

@end
- (void)sendMessage:(NSString *)message user:(NSString *)user
{
    NSLog(@"使用Email的方式,发送消息%@,给%@",message,user);
}

复制代码

步骤4: 扩展抽象的消息接口

普通消息

@interface CommonMessage : AbstractMessage

@end
- (void)sendMessage:(NSString *)message toUser:(NSString *)user
{
    //普通消息什么也不做,直接转发父类的消息
    [super sendMessage:message toUser:user];
}
复制代码

加急消息

@interface UrgencyMessage : AbstractMessage

@end
- (void)sendMessage:(NSString *)message toUser:(NSString *)user
{
    message = [NSString stringWithFormat:@"--加急---%@",message];
    [super sendMessage:message toUser:user];
    //同时下面还可以对加急消息有自己的处理方式
}
复制代码

步骤5: 功能测试

   MessageSMS *smsMsg =[[MessageSMS alloc] init];
    MessageEmail *emailMsg =[[MessageEmail alloc] init];
    
    CommonMessage *commonMessage = [[CommonMessage alloc] init];
    [commonMessage abstractMessage:smsMsg];
    [commonMessage sendMessage:@"123" toUser:@"王军"];
    [commonMessage abstractMessage:emailMsg];
    [commonMessage sendMessage:@"234" toUser:@"李四"];
    
    UrgencyMessage *urgencyMessage = [[UrgencyMessage alloc] init];
    [urgencyMessage abstractMessage:emailMsg];
    [urgencyMessage sendMessage:@"你好" toUser:@"张三"];
复制代码

结果显示:

如果你还想添加对特急消息的处理,同时添加一个使用手机发送消息的方式,该怎么办?通过上面的例子大家很快能想到

只需要在抽象部分再添加一个特急消息的类,扩展抽象消息就可以把特急消息的处理功能加入到系统中了;对于添加手机发送消息的方式也很简单,在实现部分新增加一个实现类,实现用手机发送消息的方式,也就可以了

模式讲解

1. 什么是桥接

所谓桥接,通俗点说就是在不同的东西之间搭一个桥,让他们能够连接起来,可以相互通讯和使用。那么在桥接模式中到底是给什么东西来搭桥呢?就是为被分离了的抽象部分和实现部分来搭桥

但是这里要注意一个问题:在桥接模式中的桥接是单向的,也就是只能是抽象部分的对象去使用具体实现部分的对象,而不能反过来,也就是个单向桥。

2. 为何需要桥接

为了达到让抽象部分和实现部分都可以独立变化的目的.在桥接模式中,是把抽象部分和实现部分分离开来的,虽然从程序结构上是分开了,但是在抽象部分实现的时候,还是需要使用具体的实现的,这可怎么办呢?抽象部分如何才能调用到具体实现部分的功能呢?很简单,搭个桥不就可以了,搭个桥,让抽象部分通过这个桥就可以调用到实现部分的功能了,因此需要桥接。

3. 桥接模式和继承

继承是扩展对象功能的一种常见手段,通常情况下,继承扩展的功能变化纬度都是一纬的,也就是变化的因素只有一类。 对于出现变化因素有两类的,也就是有两个变化纬度的情况,继承实现就会比较痛苦。比如上面的示例,就有两个变化纬度,一个是消息的类别,不同的消息类别处理不同;另外一个是消息的发送方式。 从理论上来说,如果用继承的方式来实现这种有两个变化纬度的情况,最后实际的实现类应该是两个纬度上可变数量的乘积那么多个。比如上面的示例,在消息类别的纬度上,目前的可变数量是3个,普通消息、加急消息和特急消息;在消息发送方式的纬度上,目前的可变数量也是3个,站内短消息、Email和手机短消息。这种情况下,如果要实现全的话,那么需要的实现类应该是:3 X 3 = 9个。 如果要在任何一个纬度上进行扩展,都需要实现另外一个纬度上的可变数量那么多个实现类,这也是为何会感到扩展起来很困难。而且随着程序规模的加大,会越来越难以扩展和维护。

而桥接模式就是用来解决这种有两个变化纬度的情况下,如何灵活的扩展功能的一个很好的方案。其实,桥接模式主要是把继承改成了使用对象组合,从而把两个纬度分开,让每一个纬度单独去变化,最后通过对象组合的方式,把两个纬度组合起来,每一种组合的方式就相当于原来继承中的一种实现,这样就有效的减少了实际实现的类的个数。 从理论上来说,如果用桥接模式的方式来实现这种有两个变化纬度的情况,最后实际的实现类应该是两个纬度上可变数量的和那么多个。同样是上面那个示例,使用桥接模式来实现,实现全的话,最后需要的实现类的数目应该是:3 + 3 = 6个。这也从侧面体现了,使用对象组合的方式比继承要来得更灵活。

结论

采用桥接模式来实现过后,抽象部分和实现部分分离开了,可以相互独立的变化,而不会相互影响

到这可能还有人不明白到底是如何桥接的?很简单,只要让抽象部分又有实现部分的接口对象(其实说白了就是绑定或者把接口对象当成抽象部分的一个属性),这就桥接上了,在抽象部分就可以通过这个接口来调用具体实现部分的功能

Demo地址

转载于:https://juejin.im/post/5d5e63b7f265da03d55e5da4

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值