对于每一位开发人员来说,设计模式都是他们进阶路上必可少的修炼。在项目中合理地运用设计模式可以完美地解决很多问题,提高代码的复用性,扩展性,可维护性。尤其是当你在设计大型的程序,模块或框架,或者重构代码的时候,设计模式一定会对你有所帮助。
个人觉得,学习设计模式的核心在于理解每种模式适用的场景和解决的问题。本文会以Cocoa(Touch)框架和经典开源库为例,围绕这两个核心来逐一讲解iOS中的设计模式。
设计模式的分类
-
创建型模式(Creational)
关注对象的创建。
- 工厂方法模式(Factory Method Pattern)
- 抽象工厂模式(Abstract Factory Pattern)
- 单例模式(Singleton Pattern)
- 建造者模式(Builder Pattern)
- 原型模式(Prototype Pattern)
-
结构型模式(Structural)
通过对象的组合获得新的功能。
- 适配器模式(Adapter Pattern)
- 桥接模式(Bridge Pattern)
- 组合模式(Composite Pattern)
- 装饰器模式(Decorator Pattern)
- 外观模式(Facade Pattern)
- 享元模式(Flyweight Pattern)
- 代理模式(Proxy Pattern)
-
行为型模式(Behavioral)
关注对象之间的通信。
- 责任链模式(Chain of Responsibility Pattern)
- 命令模式(Command Pattern)
- 解释器模式(Interpreter Pattern)
- 迭代器模式(Iterator Pattern)
- 中介者模式(Mediator Pattern)
- 备忘录模式(Memento Pattern)
- 观察者模式(Observer Pattern)
- 状态模式(State Pattern)
- 策略模式(Strategy Pattern)
- 模板模式(Template Pattern)
- 访问者模式(Visitor Pattern)
面向对象设计原则
学习设计模式的各种设计方法之前我们首先需要了解面向对象程序设计的一些基本原则。设计模式的提出正是基于了这些原则,它为我们实现代码的高内聚和低耦合,为我们提高程序的扩展性和可维护性提供了基础。
-
针对接口(协议)编程而不是针对实现编程
接口是抽象的,简洁的,相对稳定的;而实现是具体的,复杂的,相对多变的。更多的依赖不易变的接口,而不是易变的具体类,有助于提高代码的灵活性,同时减少代码的耦合。 接口常被用在以下情况:
- iOS开发中的各种delegate。
- 一组类实现各不相同,但是对外提供一样的方法。
- 模块或组件对外暴露方法。
- 设计模式中也有多种模式是面向接口的。
Swift的标准库完全是基于面向接口编程的思想而设计的,而且,Swift的protocol可以在条件扩展里给出方法的具体实现。
-
优先使用对象组合而不是继承
继承虽然实现了代码的复用,但这种复用被称白箱复用(white-box reuse)。父类的内部实现暴露于子类,父类的改动会传导给子类,它们之间是强耦合的关系。
而对象组合可以只通过接口来访问对象,该对象的内部实现对包含它的对象是不可见的,这种复用被称为黑箱复用(black-box reuse)。对象通过简单的接口访问另一个对象,它们之间的依赖大大降低了,因为接口常常是不易改变的,而且实现接口的具体类还可以在运行时动态的替换,灵活性大大的提高了。
组合优先并不等于不用继承,而要根据具体的情况加以判断。事实上,设计合理的系统,组合和继承常常是相互配合出现的。
iOS中除了组合,有些情况下还可以用下列两种方式替代继承:
- 类别
- AOP面向切面编程(或Method Swizzling)
我们在设计代码的时候可以想一想,原本要写到基类里,通过继承复用的代码是否可以加到类别里或是通过AOP来做。如统计埋点通常可以用AOP来做。
六大设计原则
-
开闭原则(Open Close Principle)
指一个软件实体(类、模块或函数)应当对扩展开放,对修改关闭。也就是说软件实体应尽量在不修改原有代码的情况下进行扩展。要做到这点抽象是关键。依赖于接口或抽象基类的类通常是稳定的,较少修改的。扩展的部分交给具体的子类去实现。
-
单一职责原则(Single Responsebility Principle)
指一个类的职责应该是单一的。比如一个下载文件的类,通过URL生成MD5文件名的方法就不应该放在它里面。
-
里氏替换原则(Liskov Substitution Principle)
指任何基类可以出现的地方,子类一定可以出现。比如原来方法里传入的是NSArray,那么传入NSMutableArray,代码行为功能不会出现问题。这个原则要求我们子类不要破坏父类的逻辑,子类应该只用来增加新的逻辑。
-
依赖倒置原则(Dependence Inversion Principle)
指高层次的模块不应依赖于低层次的模块。具体来说就是针对接口编程,依赖于抽象而不依赖于具体。
-
接口隔离原则(Interface Segregation Principle)
指我们应该使用多个隔离的接口,来隐藏不需要的方法。比如iOS中ViewController常常实现多个类的代理,对这些类而言,它们不需要知道整个ViewController的方法,它们只需要知道各自代理的方法即可。
-
最少知道原则(Demeter Principle)
指一个实体应当尽量少地暴露信息给其他实体,减少耦合。把无需公开的属性和成员变量放在.m文件里,保持.h文件的整洁和最简就是一种好习惯。
UML中的类图
统一建模语言中的类图是描述设计模式的最佳方法。UML类图中定义了六种关系:
依赖
继承
实现
关联
聚合
组合
继承和实现是两种纵向关系;其余的四种则是横向关系,它们之间关系的强弱依次为:组合>聚合>关联>依赖。