甲方不断改需求?如何以不变应万变——设计模式之桥接模式

徒弟小M接到任务开发一个画图程序,其中一个功能是画多边形:预置“矩形”和“三角形”。
他已经学习过面向对象的开发方式,于是设计类如下:

《甲方不断改需求?如何以不变应万变——设计模式之桥接模式》 设计模式-桥接模式.001

刚开始他决定用Quart2D绘制,

《甲方不断改需求?如何以不变应万变——设计模式之桥接模式》 设计模式-桥接模式.002

开发过程中,他发现,用贝塞尔曲线也可以绘制多边形,但不知道哪种方法更好,于是他决定保留已经开发好的Quart2D绘制方法,再用UIBezierPath实现,这样方便对比哪种效果好。

《甲方不断改需求?如何以不变应万变——设计模式之桥接模式》 设计模式-桥接模式.003

完成矩形和三角形后,甲方要求添加一个“五边形”,这时小M发现,他必须为“五边形”分别也写两套方法:
三种图形共需要6套绘制代码。

如果他继续研究,发现还可以用CAShapeLayer来绘制图形,这时绘制代码将达到3×3=9套,
《甲方不断改需求?如何以不变应万变——设计模式之桥接模式》
《甲方不断改需求?如何以不变应万变——设计模式之桥接模式》 设计模式-桥接模式.004

他为什么会陷入这种指数级增长的代码中呢?原因是形状类在变化,绘制方法也在变化,两种变化在继承模式下,处于紧耦合关系。
对于这种对象在“两个维度上”变化的情况,GOF“四人组”提出了“桥接模式”
下面我们通过实际重构说明这种模式的实现过程:
首先,我们先将绘图抽象出来:

@interface DrawImp : NSObject
- (void)drawLinesWithPoints:(CGPoint *)points count:(int)count;
@end

将不同的绘图方式,放到不同的DrawImp子类中:
实际画线1(Quartz方式):

@interface QuartzDrawImp : DrawImp

@end

@implementation QuartzDrawImp
- (void)drawLinesWithPoints:(CGPoint *)points count:(int)count {
    //Quartz
    CGContextRef context = UIGraphicsGetCurrentContext();
    
    UIColor *aColor = [UIColor colorWithRed:0 green:1.0 blue:0 alpha:0];
    CGContextSetRGBStrokeColor(context, 1.0, 0, 0, 1.0);
    CGContextSetFillColorWithColor(context, aColor.CGColor);
    CGContextSetLineWidth(context, 2.0);
    CGContextAddLines(context, points, count);
    CGContextDrawPath(context, kCGPathStroke);
}
@end

实际画线2(贝塞尔曲线方式):

@interface BezierDrawImp : DrawImp

@end
@implementation BezierDrawImp
- (void)drawLinesWithPoints:(CGPoint *)points count:(int)count {
    UIColor *color = [UIColor orangeColor];
    [color set];
    //创建path
    UIBezierPath *path = [UIBezierPath bezierPath];
    //设置线宽
    path.lineWidth = 3;
    //线条拐角
    path.lineCapStyle = kCGLineCapRound;
    //终点处理
    path.lineJoinStyle = kCGLineJoinRound;
    
    [path moveToPoint:points[0]];
    for (int i = 1; i < count; i++) {
        [path addLineToPoint:points[i]];
    }
    [path closePath];
    //根据坐标点连线
    [path stroke];
}
@end

对形状类进行重构,将绘制操作提到Shape类中:

@interface Shape : UIView
@property(nonatomic, strong) DrawImp *drawImp;

- (void)drawLinesWithPoints:(CGPoint *)points count:(int)count;
@end

@implementation Shape
- (void)drawLinesWithPoints:(CGPoint *)points count:(int)count {
    if (self.drawImp) {
        [self.drawImp drawLinesWithPoints:points count:count];
    }
}
@end

这时你会发现,形状绘制变得非常精简:

@implementation Triangle
- (void)drawRect:(CGRect)rect {
    CGPoint aPoints[4];
    aPoints[0] =CGPointMake(40, 10);
    aPoints[1] =CGPointMake(70, 60);
    aPoints[2] =CGPointMake(10, 60);
    aPoints[3] =CGPointMake(40, 10);
    [self drawLinesWithPoints:aPoints count:4];
}
@end

代码干净地好像“多种绘制方法”不存在一样!(矩形的绘制雷同,略去)

我们来看下“桥接模式”下的类关系图:

《甲方不断改需求?如何以不变应万变——设计模式之桥接模式》 设计模式-桥接模式.005

桥在哪里?

“桥”在抽象层,在定义类和实现类之间,通过类关联(区别与继承)形成了一座桥:

《甲方不断改需求?如何以不变应万变——设计模式之桥接模式》 设计模式-桥接模式.006

通过这座桥,定义和实现之间既联系,又分离,既可协同工作,又可独自扩展,添加新的图形和新的绘图方法都变得轻而易举。

调用者的位置

最后,我们来看下调用者的代码:

Triangle *triangle = [[Triangle alloc] initWithFrame:CGRectMake(80.0, 80.0, 100.0, 100.0)];
triangle.drawImp = [[QuartzDrawImp alloc] init];
[self.view addSubview:triangle];
    
Square *square = [[Square alloc] initWithFrame:CGRectMake(180.0, 80.0, 100.0, 100.0)];
square.drawImp = [[BezierDrawImp alloc] init];
[self.view addSubview:square];

从代码看出,调用者可以灵活的指定绘制方法,而无需修改任何形状类相关代码,形状类和绘制类被彻底解耦!

思考题

桥接模式体现了OOP面向对象设计的哪些原则呢?期待你的留言。

    原文作者:溪石iOS
    原文地址: https://www.jianshu.com/p/326c2e326861
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞