策略模式应用(登录模块)

一、前言

近期公司有一个APP重构登录页面,我们知道在页面绘制大量UITextField时会产生一个问题:需要在UITextFieldDelegate或者监听.editingChanged状态时添加大量判断逻辑。控制器中充斥着大量if else/switch case等逻辑判断。这种情况下去解耦控制器,使用策略模式再好不过。

二、什么是策略模式

策略模式的概念这里不多赘述,可以去参考一下我之前写的一篇:iOS设计模式:策略模式和工厂模式区别

策略模式图

这里把策略模式的结构图贴过来,一会根据实际项目在讲解。

三、项目案例

这里仿写一个<沪江网校>的登录界面,把关键代码贴出来给大家讲解。


普通登录
手机快速登录

上面图中所示的两种登录方式,分别有4个TextField,一般的做法是在点击登录按钮时,添加每个TextField的判断:

/// 伪代码
func loginButtonClick() {
  1 用户名不能为空 -> 用户名长度在4-20位 -> 不要有符号等等
  2 密码不能为空 -> 密码长度在8-20位 -> 必须包含大小写等等
  3 手机号正则判断
  4 动态码长度判断 -> 特殊字符判断等等
}

可想而知这个方法会有大量的if else判断,下面我们通过策略模式对其进行解耦。

四、策略模式应用
4.1 实现Strategy类

对应之前的策略模式结构图,每一个TextField的逻辑判断对应一个Strategy子类,所以先声明一个Strategy父类,所有子类继承它:

class TextFieldStrategy {
    var message: String = "" // 策略判断的结果说明
    var code: LoginSuccessCode = .Error // 策略判断的结果

    // 抽象方法
    func textFieldValidate(_ textField: UITextField) -> LoginSuccessCode {
        return code
    }
}

说明:这里抽象方法的返回值,可以根据不同的项目需求自行定义,我这里是声明了一个枚举,默认值的枚举rawValue为400,如果返回结果rawValue>400,则说明策略判断失败,反之成功。

enum LoginSuccessCode: Int {
    case Username           = 1
    case Password           = 2
    case Phonenum           = 3
    case Verifycode         = 4
    
    case Error_Username     = 401
    case Error_Password     = 402
    case Error_Phonenum     = 403
    case Error_VerifyCode   = 404
    case Error              = 400
}
4.2 实现Context类

在策略模式结构图中,Context要持有Strategy。这个案例中,Context类毫无疑问就是TextField的父类了,让TextField基类持有刚刚创建的TextFieldStrategy策略,并指定一个目标方法,在目标方法中执行TextFieldStrategy策略的抽象方法。

class BaseTextField: UITextField {
    /// init()等其他代码省略

    /// Context
    var inputStrategy: TextFieldStrategy?
    
    func validate() -> LoginSuccessCode {
        if let strategy = inputStrategy {
            let result = strategy.textFieldValidate(self)
            /// 判断 textFieldValidate 返回的 code
            if result.rawValue > LoginSuccessCode.Error.rawValue {
                /// 失败
                print(strategy.message) 
                return strategy.code /// 返回判断结果
            }else {
                /// 成功
                return strategy.code /// 返回判断结果
            }
        }
        return .Error
    }
}

到这里,一个策略模式的雏形已经基本完成,Context就是BaseTextField基类,这个基类持有一个Strategy基类的属性:inputStrategy,并声明一个目标方法:validate()
接下去的工作就是根据不同的TextField子类去创建自己的策略子类,并在控制器中调用Context的目标方法即可。

4.3 创建Strategy子类

以输入用户名为例,创建一个用户名判断的策略

/// 用户名策略
class UserNameTextFieldStrategy: TextFieldStrategy {
    
    let UserNameHud = "用户名必须是4~20位(仅支持字母、数字)"

    override func textFieldValidate(_ textField: UITextField) -> LoginSuccessCode {
        if let name = textField.text?.replacingOccurrences(of: " ", with: "") {
            if name.length == 0 {
                message = "请输入用户名"
            }else if 正则判断(4~20位,仅支持字母、数字) {
                code = .Username
                return .Username
            }else {
                message = UserNameHud
            }
        }
    
        code = .Error_Username
        return .Error_Username
    }
}
4.4 Controller中写法

以用户名为例,这里有在创建textField时,讲之前在基类中声明的属性绑定策略UserNameTextFieldStrategy,下面关于$0的写法如果有疑惑的,可以参考一下:Swift - 属性 相关

/// 用户名
lazy var userNameTextField: BaseTextField = {
  $0.delegate = self // UITextFieldDelegate
  $0.inputStrategy = UserNameTextFieldStrategy() // 指定策略
  return $0
}(BaseTextField())

上面的工作完成之后,只需要在合适的地方调用Context类(也就是BaseTextField类)中的目标方法:validate()就完成了。

func textFieldDidEndEditing(_ textField: UITextField) {
  if textField is BaseTextField {
    let code = (textField as! BaseTextField).validate()
    // 处理 code
  }
}

到此,回到文章开头的登录按钮点击方法,其中复杂的逻辑判断已经被封装到不同的textField对应的策略中,在点击登录时,你只需要对textFieldDidEndEditing中最后的code进行判断就可以了。

五、坑与总结

如果你真的在项目中使用了策略模式,那么你可能会问它到底有什么好处?本身很容易的一件事,处理的这么复杂?
答:策略模式可以对控制器进行解耦,大大提升代码的扩展能力,假如你现在实现的是一个注册页面,需要填写姓名,性别,昵称等等各种信息,难道你写一个if else的金字塔吗?

当然,在我使用的过程中也遇到了问题,因为textFieldDidEndEditing在输入完成后返回结果,而且这个结果会在多次调用时不断变化,你需要做的是记录这些结果,方法很多:
我设计的是2个数组分别接收成功和失败的结果,成功时只保留状态,失败时保留状态和错误信息,方便弹出提醒。两个数组相互关联(即输入正确后,就把之前保存的错误状态移除,反之亦然)
听上去有些复杂,其实上面的所有逻辑大概只有10行左右的代码,而且只需写一次即可。之后无论你再添加多少个TextField都是一样的。此外,假如你的设计思路确认无误,你还可以把这部分代码抽离出来,一来二去,你的水平自然就提高啦^^

六、最后的最后

如何你遇到任何问题,欢迎留言一起讨论,我会尽力提供帮助。进步来源于不断尝试嘛

你可能感兴趣的:(策略模式应用(登录模块))