徒弟小M接到任务开发一个画图程序,其中一个功能是画多边形:预置“矩形”和“三角形”。
他已经学习过面向对象的开发方式,于是设计类如下:
![](https://img.haomeiwen.com/i2025746/c62aa659897d44df.jpeg)
刚开始他决定用Quart2D绘制,
![](https://img.haomeiwen.com/i2025746/b852db95f56a9496.jpeg)
开发过程中,他发现,用贝塞尔曲线也可以绘制多边形,但不知道哪种方法更好,于是他决定保留已经开发好的Quart2D绘制方法,再用UIBezierPath实现,这样方便对比哪种效果好。
![](https://img.haomeiwen.com/i2025746/718e54cca410c75f.jpeg)
完成矩形和三角形后,甲方要求添加一个“五边形”,这时小M发现,他必须为“五边形”分别也写两套方法:
三种图形共需要6套绘制代码。
![](https://img.haomeiwen.com/i2025746/8b33c32da1e6b8cd.jpeg)
![](https://img.haomeiwen.com/i2025746/bb23527bbe32450e.jpeg)
他为什么会陷入这种指数级增长的代码中呢?原因是形状类在变化,绘制方法也在变化,两种变化在继承模式下,处于紧耦合关系。
对于这种对象在“两个维度上”变化的情况,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
代码干净地好像“多种绘制方法”不存在一样!(矩形的绘制雷同,略去)
我们来看下“桥接模式”下的类关系图:
![](https://img.haomeiwen.com/i2025746/c009626fe5ea0116.jpeg)
桥在哪里?
"桥"在抽象层,在定义类和实现类之间,通过类关联(区别与继承)形成了一座桥:
![](https://img.haomeiwen.com/i2025746/1a8f1c35dae24ae5.jpeg)
通过这座桥,定义和实现之间既联系,又分离,既可协同工作,又可独自扩展,添加新的图形和新的绘图方法都变得轻而易举。
调用者的位置
最后,我们来看下调用者的代码:
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面向对象设计的哪些原则呢?期待你的留言。
网友评论