转载

使用链式调用解决多可选参数接口设计问题

我们在 coding 的征途中,总会写下几个(or 几十个?) xxxUtils 之类的东西。想象有这样一个 NVMImageUtils ,一开始它的接口是这样的:

+ (UIImage *)imageWithSize:(CGSize)size color:(UIColor *)UIColor {
  // IMP
}

马上需要加个参数 borderColor,是可选的,于是接口成了这样:

+ (UIImage *)imageWithSize:(CGSize)size color:(UIColor *)UIColor {
  return [self imageWithSize:size color:color borderColor:nil];
}

+ (UIImage *)imageWithSize:(CGSize)size color:(UIColor *)UIColor borderColor:(UIColor *)borderColor {
  // IMP
}

过段时间再加个参数 cornerRadius:

+ (UIImage *)imageWithSize:(CGSize)size color:(UIColor *)UIColor {
  return [self imageWithSize:size color:color borderColor:nil];
}

+ (UIImage *)imageWithSize:(CGSize)size color:(UIColor *)UIColor borderColor:(UIColor *)borderColor {
  return [self imageWithSize:size color:color borderColor:borderColor cornerRadius:nil];
}

+ (UIImage *)imageWithSize:(CGSize)size color:(UIColor *)UIColor cornerRadius:(NSNumber *) cornerRadius {
  return [self imageWithSize:size color:color borderColor:nil cornerRadius:cornerRadius];
}

+ (UIImage *)imageWithSize:(CGSize)size color:(UIColor *)UIColor borderColor:(UIColor *)borderColor cornerRadius:(NSNumber *)cornerRadius {
  // IMP
}

要是再加一个 borderWidth,估计就 hold 不住了。。。

解决方案

通过玩弄 objc 的语法,最终的效果如下,接口可被链式调用,且需要什么可选参数就调什么:

UIImage *image = nvm_beginImage(size)
                     .fillColor(fillColor)     // optional
                     .borderColor(borderColor) // optional
                     .cornerRadius(20)         // optional
                     .opacity(0.5)             // optional
                     .make;

原理

头文件如下,声明一个类叫 NVMImageMaker ,用来储存 make 一个 image 需要的各种参数,最终可以通过这些参数 make 出 image:

@interfaceNVMImageMaker:NSObject

/// image fill color
- (NVMImageMaker* (^)(UIColor* imageFillColor))fillColor;
/// image border color
- (NVMImageMaker* (^)(UIColor* imageBorderColor))borderColor;
/// border 的 width, 默认 1 px, 仅当设置了 border color 才有效果
- (NVMImageMaker* (^)(CGFloat imageBorderWidth))borderWidth;
/// corner radius
- (NVMImageMaker* (^)(CGFloat imageCornerRadius))cornerRadius;
/// 透明度, 范围 0 - 1, 会应用在整个 image 上
- (NVMImageMaker* (^)(CGFloat opacity))opacity;

/// 生成 image 返回
- (UIImage *)make;

- (instancetype)init NS_UNAVAILABLE;
+ (instancetype)new NS_UNAVAILABLE;

@end

/// API 的入口, 从这里开始进行 chaining 式的 image making
extern NVMImageMaker* nvm_beginImage(CGSize imageSize);

接口的入口是 nvm_beginImage() ,接收的是必须的参数,这里是 imageSize ,用来返回一个 NVMImageMaker 对象,

extern NVMImageMaker *_Nonnull nvm_beginImage(CGSize imageSize) {
  NVMImageMaker *maker = [NVMImageMaker new];
  maker.imageSize = imageSize;
  return maker;
}

fillColor 为例,看一看如何实现链式调用传入可选参数 fillColor

- (NVMImageMaker * (^)(UIColor * imageFillColor))fillColor {
  return ^NVMImageMaker *(UIColor *imageFillColor) {
    self.imageFillColor = imageFillColor;
    return self;
  };
}

这里 fillColor 实际返回的是一个 block,这个 block 接收一个真正的 imageFillColor ,储存到 self ( NVMImageMaker ),然后将 self 返回。配合用 dot 语法调用方法的特性,我们可以做到:

nvm_beginImage(size).fillColor(color)
|         ①         |   ②   |  ③ |
// ①: 返回 NVMImageMaker 实例
// ②: 返回一个 block,它接收一个 color,然后储存到前面的 NVMImageMaker 实例中,再次将其返回
// ③: 实际传入 color,再次得到 NVMImageMaker 实例

依此类推,我们可以用这样的思路添加任意的可选参数。

最后, make 方法综合所有得到的参数,绘制成图片返回(实现忽略,不是本篇重点),最终得以通过链式调用得到图片:

nvm_beginImage(size).fillColor(color).make;

总结

代码地址: https://github.com/eleme/NVMImageMaker 。可以直接通过 pod 使用,当然实现并不复杂,大可自行实现。

至此,我们有了一个全新的思路,用来解决开篇演示的,让人越来越 hold 不住的多可选参数接口的设计问题。

Written by 饿了么iOS组 - axl411

原文  http://mobilists.eleme.io/2017/01/11/使用链式调用解决多可选参数接口设计问题/
正文到此结束
Loading...