美文网首页iOS基本功
策略模式应用(登录模块)

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

作者: YYYYYY25 | 来源:发表于2018-07-12 17:28 被阅读15次
    一、前言

    近期公司有一个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都是一样的。此外,假如你的设计思路确认无误,你还可以把这部分代码抽离出来,一来二去,你的水平自然就提高啦^^

    六、最后的最后

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

    相关文章

      网友评论

        本文标题:策略模式应用(登录模块)

        本文链接:https://www.haomeiwen.com/subject/jvplpftx.html