浅谈设计模式、SOLID原则、封装、多态、继承 - AlvinSunny/OC-TheUnderlying GitHub Wiki
设计模式
设计模式(Design Pattern)是一套被反复使用、多数人知晓的代码设计经验的总结,使用设计模式能够更好的帮助我们解决开发中遇到的各种问题(比如:代码复用),是一套非常成熟的编程思想
设计模式可以分为三大类: 创建型、结构型、行为型
- 创建型模式: 对象实例化模式,用于解耦对象的实例化过程;常见的有:单例模式、工厂模式等
- 结构型模式: 把类或对象结合在一起形成一个更大的结构; 常见的有: 代理模式、适配器模式、组合模式、装饰模式等
- 行为型模式: 类或对象之间如何交互,及划分责任;常见的有: 观察者模式、命令模式、责任链模式、订阅者模式
创建型
- 单例模式: 某个类只能有一个实例,提供一个全局的访问;特点:只有一个实例、自我实例化、提供全局访问
- 简单工厂模式: 一个工厂类根据传入的参数决定创建出那种产品类的实例;
- 工厂方法模式: 定义一个创建对象的接口,让子类决定实例化那个类;非常符合“开闭原则”,当需要增加一个新的产品时,我们只需要增加一个具体的产品类和与之对应的具体工厂即可,无须修改原有系统
- 抽象工厂模式: 创建相关或依赖对象的家族,而无需明确指定具体类
- 建造者模式: 封装一个复杂对象的构建过程,并可以按步骤构造
- 原型模式: 通过复制现有的实例来创建新的实例
结构型
- 适配器模式: 将一个类的方法接口转换成客户希望的另外一个接口
- 组合模式: 将对象组合成树的形结构以表示“部分-整体”的层次结构
- 装饰模式: 动态的给对象添加新的功能; iOS中的分类就是很经典的例子
- 代理模式: 为其他对象提供一个代理 以便控制这个对象的访问
- 享元(蝇量)模式: 通过共享技术来有效的支持大量细粒度的对象
- 外观模式: 对外提供一个统一的方法,来访问子系统中的一群接口; 通过使用外观模式,使得客户对子系统的引用变得简单了,实现了客户与子系统之间的松耦合。但是它违背了“开闭原则”,因为增加新的子系统可能需要修改外观类或客户端的源代码
- 桥接模式: 将抽象部分和它的实现部分分离,使它们都可以独立的变化
行为型
- 模板模式: 定义一个算法结构,而将一些步骤延迟到子类实现
- 解释器模式: 给定一个语言,定义它的文法的一种表示,并定义一个解释器
- 策略模式: 定义一系列算法,把它们封装起来,并且使它们可以相互替换
- 状态模式: 允许一个对象在其对象内部状态改变时改变它的行为
- 观察者模式: 对象间的一对多的依赖关系
- 备忘录模式: 在不破坏封装的前提下,保持对象的内部状态
- 中介者模式: 用一个中介对象来封装一系列的对象交互
- 命令模式: 将命令请求封装为一个对象,使得可以用不同的请求来进行参数化
- 访问者模式: 在不改变数据结构的前提下,增加作用于一组对象元素的新功能
- 责任链模式: 将请求的发送者和接受者解耦,使得多个对象都有处理这个请求的机会
- 迭代器模式: 一种遍历访问聚合对象中各个元素的方法,不暴露该对象的内部结构
SOLID原则
-
单一职责原则(Single Responsibility Principle): 表明对一个类的设计上其责任应该单一的,举例: 一台机器生产面包就生产面包,不要即生产面包又生产油条
-
开闭原则(Open / Closed Principle): 一个类应该对扩展开放,对修改关闭;类设计出来后其核心应该固定,别人可以补充 但不能改变我本身的职能,
装饰模式就很符合这一原则,然而iOS中的类别就是使用装饰模式的经典案例 -
里是替换原则(Liskov Substitution Principle): 派生的子类应该可以替换父类,能够做到无缝衔接; 面向对象语言特性
继承就把这一点发挥出来了 -
接口隔离原则(Interface Segregation Principle): 接口功能要单一,不要在一个接口上根据不同入参实现多个功能; UITableView 的delegate和dataSource的接口设计就是很好的例子
-
依赖倒置原则(Dependency Inversion Principle): 实体可以依赖抽象,抽象不能依赖实体; 在组件化方案中我们要求基础层组件不能依赖上层业务组件,上层业务组件可以依赖基础层组件
-
迪米特原则: 一个对象应该对其他对象保持最少的了解, 即高内聚、低耦合;
合理的使用各种设计模式和编程原则都是为了实现高复用和解耦,以达到高内聚低耦合的目的
详细的了解它们
单一职责原则(SRP)声明:“引起类变化的因素永远不要多余一个”。这意味着你需要设计你的类,使得每个类都只有一个目的。这并不意味着每个类应该只有一个方法,而是说类中所有的方法都要与该类的主要功能相关。那些有多个职责的类,应该被分成新的类。
当一个类具有了多项职责,它需要被更改的可能性也随之增加。每次一个类的修改也会使得bug产生的风险增加。而通过集中职责与一点会使得风险被有效的限制。
开闭原则(OCP)指出:“软件实体(classes, modules, functions etc.)应该对拓展开放,对修改关闭”。该规则的“封闭”部分规定,一旦模块被开发和测试完成,代码被修改的原因应该只有修复bug这一种情况。 “开放”部分说,您应该能够扩展现有代码(而不是修改之前的代码)以引入新功能。与SRP一样,该原理通过限制对现有代码的更改来降低引入新错误的风险。
里氏替换原则(LSP)声明:“所有引用基类的地方必须能透明地使用其子类的对象”。如果你创建了一个给定类型关系的类,那么你应该可以提供该类型或任意该类型子类的对象,而不会出现意外的结果,并且没有依赖的类知道被提供依赖类的确切类型。如果必须检查依赖关系的类型,以便可以根据类型修改行为,或者如果子类型产生意外的规则或副作用,则代码可能变得更加复杂,僵化和脆弱。
依赖倒置原则(DIP)有两条声明。第一个是高级模块不应该依赖于低级模块。两者都应该依赖于抽象。第二部分规则是抽象不应该依赖于细节。细节应该依赖于抽象。
DIP主要涉及到应用中层次化的概念,其中较低级别的模块处理细节的功能,较高级别的模块使用较低级别的类来实现更大的任务。该原则规定了在类之间存在依赖关系的情况下,应使用抽象(如接口)来定义它们,而不是直接引用类。 这减少了由较低级别模块的变化导致的错误,导致较高层的错误。 DIP经常在依赖注入中被使用。
接口隔离原则(ISP)指出:“客户端不应该强制依赖那些他们没有使用到的接口”。这个规则意味着当一个类依赖另一个类时,接口中可以被依赖类显示的成员的数量应该被最小化。通常当您创建一个具有大量方法和属性的类时,该类将被其他类使用,并且只访问其一个或两个成员。随着他们意识到的成员数量的增加,这些类更加紧密地耦合在一起。当您遵循ISP时,大类实现了多个更小的接口,根据用途对功能进行分组。依赖关系与那些相关联用于松耦合,增加健壮性,灵活性以及可复用性
迪米特原则: 一个对象应该对其他对象保持最少的了解
面向对象三大特性: 封装、多态、继承
封装: 对外公开功能接口,内部隐藏实现细节; 封装特性中会需要用到单一责任、开闭、接口隔离相关思想
是对象和类概念的主要特性。它是隐藏内部实现,稳定外部接口,可以看作是“包装”。封装,也就是把客观事物封装成抽象的类,并且类可以把自己的数据和方法只让可信的类或者对象操作,对不可信的进行信息隐藏。
好处:使用更简单变量更安全可以隐藏内部实现细节,使代码模块化
OC中一个类可以继承另一个类,被继承的类成为超类(superclass),继承的类是子类(childclass),可以直接拥有父类中所有非私有成员(相关实例变量)、方法。继承的实现在接口中使用符号“:”。
举个例子:@interface Student: NSObject () 不过大家要注意的是:属性封装实例变量,方法封装具体实现代码,类封装属性和方法。子类可继承父类中的方法,还可重写父类方法。
继承: 如果类和类之间存在逻辑关系,且有共用部分;即可抽取重复代码形成父类,子类拥有父类的成员变量和方法
面向对象编程(OOP[Object oriented programming])语言的一个主要功能就是“继承”。
继承是指这样一种能力:它可以使用现有类的所有功能,并在无需重新编写原来的类的情况下对这些功能进行扩展。
通过继承创建的新类称为“子类”或“派生类”,被继承的类称为“基类”、“父类”或“超类”。继承的过程,就是从一般到特殊的过程。
在考虑使用继承时,有一点需要注意,那就是两个类之间的关系应该是“属于”关系。例如,Employee是一个人,Manager也是一个人,因此这两个类都可以继承Person类。但是Leg类却不能继承Person类,因为腿并不是一个人。
继承可扩展已存在的代码模块(类),它们最终需要的结果(代码重用)
缺点: 耦合性太强,子类与父类相互依赖(父类依赖子类场景: 子类重写父类方法testA 在内部调用[super testA], 父类testA实现中用到了某一个方法testB被子类重写,此时就会调用到子类重写的这个方法; 这父类就依赖子类实现咯)
注意:
- 不允许子类和父类拥有相同的成员变量(不绝对在OC中), 正常情况子类重复声明父类的属性会提示: 属性合成不会合成属性“信息”
它将由它的超类来实现,使用@dynamic来确认意图, 这我们就明白了子类一旦声明父类属性,只需要用@dynamic来修饰属性 然后自己重写setter、getter方法就可以覆盖父类的属性 不过一般没人这么干
多态: 父类指针可以指向子类对象; 多态中会需要用到里氏替换相关思想
多态性(polymorphism)是允许你将父对象设置成为和一个或更多的他的子对象相等的技术,赋值之后,父对象就可以根据当前赋值给它的子对象的特性以不同的方式运作。
简单的说,就是一句话: 允许将子类类型的指针赋值给父类类型的指针。不同对象以自己的方式响应相同的消息的能力叫做多态。
假设生物类(life)都用有一个相同的方法-eat;那人类属于生物,猪也属于生物,都继承了life后,实现各自的eat,但是调用是我们只需调用各自的eat方法。也就是不同的对象以自己的方式响应了相同的消息(响应了eat这个选择器)。
多态是为了实现另一个目的(接口重用),多态的作用就是为了类在继承和派生的时候,保证使用“家谱”中任一类的实例的某一属性时的正确调用
实现多态,有二种方式,覆盖,重载。
1)覆盖:是指子类重新定义父类的虚函数的做法。
2)重载:是指允许存在多个同名函数,而这些函数的参数表不同(或许参数个数不同,或许参数类型不同,或许两者都不同)。
优点: 在需要使用父类类型时,可以传入父类、子类对象(例如方法传参为父类类型,即可传父类对象,也可以传子类对象)
缺点: 父类类型不能直接调用子类方法,必须强转为子类类型后才能调用
注意:
- 程序运行时才会确定对象的真实类型
- 如果存在多态,父类就可以强转为子类类型,不存在则转不了; 不过要记得这么做是有风险的