面向对象之设计模式

面向对象之设计模式

前言

  • 要想推开架构师的那扇大门,就离不开设计模式这把钥匙。

    • 对设计模式的理解与精通,是通往架构师之路的第一步
  • 在任何面向对象语言的开发过程以及个人职业技能成长的道路中,新手与新手或者新手与高手的对决中,决定成败的往往是对知识点的纵向熟悉,和对知识点横向的涉猎

    • 纵向:表示的对某一个知识点了解的有多深
    • 横向:表示知识点面的广泛
  • 而高手与高手的对决,决定成败的往往是对设计模式的理解与运用

    • 遗憾的是,对设计模式的理解只可意会,不可言传。很难通过文字和讲解口头阐述,只能自身通过大量的代码和项目的积累产生自己的见解
      • 这需要足够的兴趣和意志力。 技术,是一笔深情课

1.1-设计模式简介

1.1.1-什么是设计模式?

  • 设计模式(Design pattern)是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性。 毫无疑问,设计模式于己于他人于系统都是多赢的;设计模式使代码编制真正工程化;设计模式是软件工程的基石脉络,如同大厦的结构一样
    • 本人对设计模式的理解是:任何的设计模式的应用都离不开三点:代码的拓展性、可维护性、生存性

1.1.2-iOS有哪些设计模式?

  • 本小节主要参考《Objective-c编程之道 iOS设计模式解析》一书
  • iOS有21种设计模式
    • 1.原型模式
    • 2.工厂模式
    • 3.抽象模式
    • 4.生成器模式
    • 5.单例模式
    • 6.适配器模式
    • 7.桥接模式
    • 8.外观模式
    • 9.中间者模式
    • 10.观察者模式
    • 11.组合模式
    • 12.迭代器模式
    • 13.访问者模式
    • 14.装饰模式
    • 15.责任链模式
    • 16.模板模式
    • 17.策略模式
    • 18.命令模式
    • 19.亨元模式
    • 20.代理模式
    • 21.备忘录模式

1.2-设计模式的六大原则

只需了解即可,课后感兴趣的可以慢慢咀嚼,设计模式的六大原则一个比一个难理解,一个比一个更深奥,单纯的靠文字和课堂讲解很难真正的掌握,必须要大量的代码积累和不断的思考感悟

  • 描述设计模式的原则,这里笔者主要从三个角度分析

    • 原则定义
    • 原则由来
    • 解决方案
  • ***1.单一原则***

    • 定义:一个类只负责一项职责
    • 由来:如果类A的职责是完成功能P,类B的职责是完成功能Q。如果将功能Q的任何代码放到类A中,那么一旦类A发生变更,即使本该完成Q功能的类B代码没有任何变更,Q功能也会产生一定的故障。
    • 解决方案:如果类A原本的职责是完成功能P,如果功能P在职责之外发生需求上的变更或者需要拓展,这个时候不可以在类A中修改代码,应该是新建一个类B来完成新增的功能Q
      • 但是如果P功能是在职责之内发生变更,可以在类A中修改
  • ***2.里氏替换原则***

    • 定义:在程序P中,类B继承于类A,如果将P程序中的所有的类A的对象替换成类B的对象,这个程序不会发生任何的变化
    • 由来:有一功能R,由类A完成。现需要将功能R进行扩展,扩展后的功能为Q,其中Q由原有功能R与新功能P组成。新功能P由类A的子类B来完成,则子类B在完成新功能P的同时,有可能会导致原有功能R发生故障
    • 解决方案:子类继承父类的同时,遵循里氏替换原则,只在父类的基础上拓展功能,而避免重写或重载父类的方法,更改父类的功能。
      • 在不更改父类原有的功能的基础上,可以酌情重写父类的方法在父类原有功能上进行拓展
        • 所以问题的核心并不是重写,而是更改
  • ***3.依赖倒置原则***

    • 定义:1.高层次的模块不应该依赖于低层次的模块,他们都应该依赖于抽象。2.抽象不应该依赖于具体实现,具体实现应该依赖于抽象
      • 依赖倒置原则(Dependence Inversion Principle)是程序要依赖于抽象接口,不要依赖于具体实现。简单的说就是要求对抽象进行编程,不要对实现进行编程,这样就降低了客户与实现模块间的耦合。
    • 由来:耦合度太高导致类与类之间的依赖性太强

      • 在类A调用类B的方法P,而方法P却依赖于类C,如果需求发生变更,类B中的方法需要依赖于类D,那么就必须要修改类B的代码,这样每一个变更都要修改类B的代码,而类B的逻辑比较多,属于高层模块,修改起来会导致其他一些问题。这主要是由于类B与类C之间的偶尔性太强导致。
        • 假如有一个基类E,本身没有任何实现,只是指明了类E与类B之间的依赖关系,并且有一个接口。那么当类B需要依赖类C的时候,只需要指明类B依赖的类E的接口指向类C就可以。这样的话需求发生变更就不在需要修改类B的代码,甚至也不需要修改类E的代码,因为类E本身是一个虚拟类,没有任何的实现,我们只需要增加修改的代码放入类E的接口中即可。
    • 解决方案:解耦

      • 低层模块尽量都要有抽象类或接口,或者两者都有。
      • 变量的声明类型尽量是抽象类或接口。
      • 使用继承时遵循里氏替换原则。
  • 依赖倒置原则的核心就是要我们面向接口编程,理解了面向接口编程,也就理解了依赖倒置

  • ***4.接口隔离原则***

    • 定义:客户端不应该依赖它不需要的接口;一个类对另一个类的依赖应该建立在最小的接口上
    • 问题由来:在依赖倒置原则的基础上,有一个接口类A,类A有五个方法,分别是方法A1,A2,A3,A4,A5;其中类B通过方法A1,A2,A3依赖于类C,类D通过方法A4,A5,A6依赖于类E。那么对于B而言,接口类A的方法A4,A5是多余的,对于类D而言,接口类A的方法A1,A2,A3是多余的。结果本来好好的一个抽象接口类遵循了依赖倒置原则,结果对于类B和类D而言都存在臃肿的部分。
    • 解决方案:将臃肿的接口类A,分离成独立的几个接口类,如类L,类M,类N,这三个接口类分别承担类A的原有的五个方法,这样的话类B和类D通过多个接口类去依赖对应的类,也就是接口隔离原则
  • 使用接口隔离原则需要注意以下几点:

    • 接口尽量小,但是要有限度。对接口进行细化可以提高程序设计灵活性是不挣的事实,但是如果过小,则会造成接口数量过多,使设计复杂化。所以一定要适度。
    • 为依赖接口的类定制服务,只暴露给调用的类它需要的方法,它不需要的方法则隐藏起来。只有专注地为一个模块提供定制服务,才能建立最小的依赖关系
    • 提高内聚,减少对外交互。使接口用最少的方法去完成最多的事情
  • 运用接口隔离原则,一定要适度,接口设计的过大或过小都不好。设计接口的时候,只有多花些时间去思考和筹划,才能准确地实践这一原则

  • 接口隔离原则与OC的多重继承冲突的解决方案

    • 接口隔离的原则实际上就是多重继承进行接口分离,但是OC不支持多重继承,所以在iOS开发中一般使用协议来进行接口分离,这就是为什么有的系统类同时遵循了好几个协议的原因
  • ***5.迪米特法则***

    • 定义:一个对象应该对其他对象保持最少的了解
    • 问题由来:类与类之间的关系越密切,耦合度越大,当一个类发生改变时,对另一个类的影响也越大。
    • 解决方案:尽量降低类与类之间的耦合
      • 主要针对的是具体的实现类,在设计的时候应该在遵循其他原则的基础上最大限度的保证高内聚低耦合
  • 自从我们接触编程开始,就知道了软件编程的总的原则:低耦合,高内聚。无论是面向过程编程还是面向对象编程,只有使各个模块之间的耦合尽量的低,才能提高代码的复用率。低耦合的优点不言而喻,但是怎么样编程才能做到低耦合呢?那正是迪米特法则要去完成的

    • 迪米特法则又叫最少知道原则,最早是在1987年由美国Northeastern University的Ian Holland提出。通俗的来讲,就是一个类对自己依赖的类知道的越少越好。也就是说,对于被依赖的类来说,无论逻辑多么复杂,都尽量地的将逻辑封装在类的内部,对外除了提供的public方法,不对外泄漏任何信息。迪米特法则还有一个更简单的定义:只与直接的朋友通信。首先来解释一下什么是直接的朋友:每个对象都会与其他对象有耦合关系,只要两个对象之间有耦合关系,我们就说这两个对象之间是朋友关系。耦合的方式很多,依赖、关联、组合、聚合等。其中,我们称出现成员变量、方法参数、方法返回值中的类为直接的朋友,而出现在局部变量中的类则不是直接的朋友。也就是说,陌生的类最好不要作为局部变量的形式出现在类的内部。
  • 使用迪米特法则的时候可以联想一下公司的为人处事之道,假如你是员工,你的上面有主管,然后有总监,再上面有老板。当你有一件事只跟你的老板有关,那么如果你直接找你的老板,这就是属于越级的行为(与单一原则产生冲突)。而你先找你的主管,再通过主管找总监,最后通过总监找到老板,这个过程即复杂又让跟这件事没有关系的人知道了(与迪米特法则自身冲突),是不是突然变得非常棘手不知道怎么处理呢?所以有时候合理的理解设计模式需要强大的情商和悟性

    • 迪米特法则的初衷是降低类之间的耦合,由于每个类都减少了不必要的依赖,因此的确可以降低耦合关系。但是凡事都有度,虽然可以避免与非直接的类通信,但是要通信,必然会通过一个“中介”来发生联系,例如本例中,总公司就是通过分公司这个“中介”来与分公司的员工发生联系的。过分的使用迪米特原则,会产生大量这样的中介和传递类,导致系统复杂度变大。所以在采用迪米特法则时要反复权衡,既做到结构清晰,又要高内聚低耦合。
      • 迪米特法则最核心的内容是在设计类时,对类的耦合度和内聚度有一个合理的综合权衡,而并未单独的偏向一方
        • 解决迪米特法则的冲突问题的时候其实可以考虑到21种设计模式中的某一些设计模式来中和某些冲突
          • 比如使用中间人模式(又叫中介者模式)
  • ***6.开闭原则***

    • 定义:一个软件实体如类、模块和函数应该对扩展开放,对修改关闭
    • 由来:在软件的生命周期内,因为变化、升级和维护等原因需要对软件原有代码进行修改时,可能会给旧代码中引入错误,也可能会使我们不得不对整个功能进行重构,并且需要原有代码经过重新测试
    • 解决方案:当软件需要变化时,尽量通过扩展软件实体的行为来实现变化,而不是通过修改已有的代码来实现变化
  • 我曾经看过一些架构师的书籍,其中提到我们进行设计的时候一定要遵守开闭原则,当时觉得这句话说了等于没说。到今天回过头来在看一次,貌似又什么都说了。

    • 开闭原则是六大原则中最抽象和虚拟的原则
  • 开闭原则 = 前面五大原则总和的平均分

    • 开闭原则无非就是想表达这样一层意思:用抽象构建框架,用实现扩展细节。因为抽象灵活性好,适应性广,只要抽象的合理,可以基本保持软件架构的稳定。而软件中易变的细节,我们用从抽象派生的实现类来进行扩展,当软件需要发生变化时,我们只需要根据需求重新派生一个实现类来扩展就可以了。当然前提是我们的抽象要合理,要对需求的变更有前瞻性和预见性才行
      • 也就是说前面的五大原则遵循好了,你自己而然就遵守了开闭原则,如果前面五个没有遵守好,也就没有遵守开闭原则。开闭原则是对前面五个原则的总结和约束!
  • ***六大原则总结***

这里写图片描述

  • 图中的每一条维度各代表一项原则,我们依据对这项原则的遵守程度在维度上画一个点,则如果对这项原则遵守的合理的话,这个点应该落在红色的同心圆内部;如果遵守的差,点将会在小圆内部;如果过度遵守,点将会落在大圆外部。一个良好的设计体现在图中,应该是六个顶点都在同心圆中的六边形。

这里写图片描述

  • 在上图中,设计1、设计2属于良好的设计,他们对六项原则的遵守程度都在合理的范围内;设计3、设计4设计虽然有些不足,但也基本可以接受;设计5则严重不足,对各项原则都没有很好的遵守;而设计6则遵守过渡了,设计5和设计6都是迫切需要重构的设计

  • 本小节参考博客http://www.uml.org.cn/sjms/201211023.asp

  • 本小节参考书籍《设计模式之禅》《面向模式的软件架构》
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值