设计模式浅析

 面向对象编程3大特性 7大原则

一、面向对象的特性
三个基本的特性:封装、继承与多态。
1、封装
面向对象编程核心思想这一就是就是将数据和对数据的操作封装在一起.通过抽象,即从具体的实例中抽取共同的性质形成一般的概念,比如类的概念. 
2、继承
继承体现了一种先进的编程模式.子类可以继承父类的属性和功能,即子类继承了父类所具有的数据和数据上的操作,同时又可以增添子类独有的数据和数据上的操作.例如,"人类"继承了"哺乳类"的属性和功能,同时又增添了人类独有的属性和功能.
3、多态
 多态是面向对象编程的又一重要特征.有两种意义的多态(表现为方法的重写(Overriding)和重载(Overloading)) 
 一种是操作名称的多态,即有多个操作具有相同的名字,但这些操作所接收的消息类型必须不同.所谓操作名称的多态是指可以向操作传递不同消息,以便让对象根据相应的消息来产生一定的行为.

二、七大基本原则 
7大基本原则:单一职责原则SRP(Single Responsibility Principle)、开放封闭原则OCP(Open-Close Principle) 、替换原则、依赖原则(the Dependency Inversion Principle DIP) (the Liskov Substitution Principle LSP) 、接口分离原则(the Interface Segregation Principle ISP)、“迪米特”法则 、组合/聚合原则 

1、单一职责原则SRP(Single Responsibility Principle)
即一个类只负责一项职责
可以降低类的复杂度,一个类只负责一项职责,其逻辑肯定要比负责多项职责简单的多;
提高类的可读性,提高系统的可维护性;
变更引起的风险降低,变更是必然的,如果单一职责原则遵守的好,当修改一个功能时,可以显著降低对其他功能的影响。
需要说明的一点是单一职责原则不只是面向对象编程思想所特有的,只要是模块化的程序设计,都适用单一职责原则。

2、开放封闭原则OCP(Open-Close Principle) 
一个模块在扩展性方面应该是开放的而在更改性方面应该是封闭的。比如:一个网络模块,原来只服务端功能,而现在要加入客户端功能,
那么应当在不用修改服务端功能代码的前提下,就能够增加客户端功能的实现代码,这要求在设计之初,就应当将服务端和客户端分开,公共部分抽象出来。

3、里氏替换原则(the Liskov Substitution Principle LSP) 
子类应当可以替换父类并出现在父类能够出现的任何地方。

4、依赖倒转原则(the Dependency Inversion Principle DIP) 
具体依赖抽象,上层依赖下层。假设B是较A低的模块,但B需要使用到A的功能,
这个时候,B不应当直接使用A中的具体类: 而应当由B定义一抽象接口,并由A来实现这个抽象接口,B只使用这个抽象接口:这样就达到
了 依赖倒置的目的,B也解除了对A的依赖,反过来是A依赖于B定义的抽象接口。通过上层模块难以避免依赖下层模块,假如B也直接依赖A的实现,那么就可能造 成循环依赖。一个常见的问题就是编译A模块时需要直接包含到B模块的cpp文件,而编译B时同样要直接包含到A的cpp文件。

5、接口分离原则(the Interface Segregation Principle ISP) 
模块间要通过抽象接口隔离开,而不是通过具体的类强耦合起来。即面向接口编程

6、“迪米特”法则
又叫最少知识原则,就是说,一个对象应当对其他对象有尽可能少的了解
① 在类的划分上,应该创建有弱耦合的类;
② 在类的结构设计上,每一个类都应当尽量降低成员的访问权限;
③ 在类的设计上,只要有可能,一个类应当设计成不变类;
④ 在对其他类的引用上,一个对象对其它对象的引用应当降到最低;
⑤ 尽量降低类的访问权限;
⑥ 谨慎使用序列化功能;
⑦ 不要暴露类成员,而应该提供相应的访问器(属性)。 

7、.组合/聚合原则
又叫合成复用原则。原则就是在一个新的对象里面通过关联关系(包括组合关系和聚合关系)使用一些已有的对象,使之成为新对象的一部分,新对象通过委派调用已有对象的方法达到复用其已有功能的目的。也就是,要尽量使用类的合成复用,尽量不要使用继承。
就是在一个新的对象里面使用一些已有的对象,使之成为新对象的一部分,新的对象通过向这些对象的委派达到复用已有功能的目的。这个原则有一个简短的描述:要尽量使用合成、聚合,尽量不要使用继承。
1)  新对象存取成分对象的唯一方法是通过成分对象的接口
2)  这种复用是黑箱复用,因为成分对象的内部细节是新对象所看不到的
3)  这种复用可以在运行时间内动态进行,新对象可以动态的引用与成分对象类型相同的对象
4)  合成、聚合可以应用到任何环境中去,而继承只能应用到一些有限环境中去
5)  导致错误的使用合成、聚合与继承的一个常见原因是错误的把“Has-a”关系当作“Is-a”关系。如果两个类是“Has-a”关系那么应使用合成、聚合,如果是“Is-a”关系那么可使用继承

在面向对象设计中,可以通过两种基本方法在不同的环境中复用已有的设计和实现,即通过组合/聚合关系或通过继承。
1)   继承复用:实现简单,易于扩展。破坏系统的封装性,从基类继承而来的实现是静态的,不可能在运行时发生改变,没有足够的灵活性;只能在有限的环境中使用。(“白箱”复用 )
2)   组合/聚合复用:耦合度相对较低,选择性地调用成员对象的操作;可以在运行时动态进行。(“黑箱”复用 )
组合/聚合可以使系统更加灵活,类与类之间的耦合度降低,一个类的变化对其他类造成的影响相对较少,因此一般首选使用组合/聚合来实现复用;
其次才考虑继承,在使用继承时,需要严格遵循里氏代换原则,有效使用继承会有助于对问题的理解,降低复杂度,而滥用继承反而会增加系统构建和维护的难度以及系统的复杂度,因此需要慎重使用继承复用。 
 
 

设计理念:

  1. 高聚集,低耦合;
  2. 对扩展开放,对修改关闭;
  3. 可扩展,可复用,易维护;
  4. 各施所职,相互协作,互不干涉,自己管自己

    为理念

达到

    取消一个功能

    • 只需在主板块算法结构上注释对该功能的一个接口调用,并且不会影响到其它功能的正常运作

    增加一个功能

    • 只需在主板块类上组合一个功能实例变量,调其接口,无需操心以及直接操作有关其功能数据  

 

编写过程中把基本不变的算法与需变更的算法分离,

利用组合抽象,根据协议接口编程。

 

其中算法分离以及组合抽象的方式需根据代码形势,以后需求变更,等因素而决定,

从而演变出一系列设计模式

(编译时期:

  • 编译器在运行程序的时候会把各类文件编译为机器语言这个过程,也就是代码写的是什么就是什么

运行时期:

  • 当代码真正运行到的时候才能确定具体内容,多态就好比一个方法接受一个泛型参数,只有在运行时真正编译才知道传入的具体类型,从而在编写的时候可以因不同情况传入不同适当的类型,达到多态。)

 

 

 

装饰者:包装一个对象,以提供新的行为

 

状态:    封装了基于状态的行为,并使用委托在行为之间切换

 

迭代器:在对象的集合之间游走,而不暴露集合的实现

 

外观:    简化一群类的接口

 

策略:    封装可以互换的行为,并使用委托来决定使用哪一个

 

代理:    包装对象,以控制对此对象的访问

 

工厂方法:由子类决定要创建的具体类是哪一个

 

适配器:封装对象,并提供不同的接口

 

观察者:让对象能够在状态改变时被通知

 

模板方法:由子类决定如何实现一个算法中的步骤

 

组合:     客户用一致的方式处理对象集合和单个对象

 

单件:     确保有且只有一个对象被创建

 

抽象工厂:允许客户创建对象的家族,而无需指定他们的具体类

 

命令:     封装请求成为对象

 

 

 

 

设计原则:

· 1.找出应用中可能需要变化之处,把他们独立出来,不要和那些不需要变化的代码混在一起;

· 2.针对接口编程,而不是针对实现编程;

· 3.多用组合,少用继承

· 4.为交互对象之间的松耦合设计而努力

· 5.依赖抽象,不要依赖具体类

· 6.最少知识原则:只和你的密友谈话

· 7.由基类主控一切,当需要时,自然去调用子类。

· 8.一个类应该只有一个引起变化的原因,每个类应保持单一责任

 

 

 

 

 

策略模式:(定义了算法族,分别封装起来,让他们之间可以互相替换,此模式让算法的变化独立于使用算法的客户。)

声明各种行为接口协议,实现各种遵循行为接口协议的具体行为类(算法类),在主体中有的只是各种行为抽象对象,在具体使用时才赋上具体行为对象。

(通过算法族实现算法协议组合到产品基类中,在子类初始化或方法传参数时确定具体算法,达到可扩展,少用继承多用组合,重复利用算法代码,避免子类经常需要复写基类方法等目的)

 

观察者模式:(在对象之间定义一对多的依赖,这样当一个对象改变状态,依赖它的对象都会收到通知,并自动更新。)

声明主题接口协议(供观察者注册的接口,撤销注册接口,发起通知接口),声明观察者接口协议,让主题知道观察他的所有对象都遵循这个观察者协议,都有这些接口。具体主题类(一般为单例模)遵主题口协议并且内部有个数组保存所注册的观察者对象,发起通知时便可遍历数组拿到观察者对象调用他们的观察者协议接口。

(该模式可通过增加观察者而扩展功能,而不需要修改主题,达到了对扩展开放,对修改关闭原则。)

 

装饰模式:

声明一个接口协议,该协议中的接口为将来装饰对象时所发生的接口,将会被装饰的对象遵循该协议,实现该协议中接口,到需要装饰他的时候,重新实现一个装饰者类,该类同样遵循该协议(装饰者与被装饰者遵循同一个协议为的是让他们都有相同的抽象行为方式)并且类内有一个被装饰者对象的引用并且在初始化时用被装饰者作为参数传进并保留起来,然后装饰者实现协议接口时便可以取到被装饰者也就是旧的接口方法的同时还可以添加上自己新的行为上去

id<behavior> class=[[Class1 alloc]init];

class=[[Decorate1_Class alloc]initWithClass1:class];

class=[[Decorate2_Class alloc]initWithClass1:class];

(该模式通过增加装饰者类对功能的扩展,达到对扩展开放,而不需要修改原有代码,达到对修改关闭,并且通过组合被装饰者,达到代码重用。由于装饰者模式需要大量组件子类在构造最终产物类之前往往需要多次装饰对象,因此多与工厂模式结合使用。)

 

简单工厂(非设计模式):

把需要根据不同情况生成不同类型对象的这部分抽出封装进一个工厂类中,该类中提供一个返回确定生成的对象的方法。

(该理念可以把因变化而改变代码程度降到最低并且集中。可把工厂类中返回具体对象的方法实现为类(静态)方法,也可以抽象为接口或基类用以有多个种类(流水线)下不同的产品这种情况。而下面的工厂方法则把控制种类(流水线)结合到抽象基类中放到了抽象方法去,让子类实现抽象方法,也就是它的每一个子类就是一个种类(流水线),能产生同类型不用种类的产品)

 

工厂方法模式:(抽象基类声明了一个返回具体组件对象的抽象方法,并且在基类内部的算法中调用该方法获取组件对象进行操作,而由子类实现该抽象方法决定供实例化的组件类范围有哪些相当于哪一个流水线的产品范围,也就是子类实现该方法时实际上是代替了简单工厂的范围的同时限定了供选择的产品族,因此这里无需与简单工厂结合使用。工厂方法让类把组合对象(产品)实例化推迟到子类。)

定义一个抽象基类把不变的部分实现,需要生成返回具体类型对象的部分写成抽象方法,基类中可调用该抽象方法拿到一个对象,就可以在基类对该对象操作一些不变的步骤,这样决定该对象具体类型就推延到子类实现该抽象方法的时候,又不妨碍基类中对它的操作。

(该模式其实和模板方法模式有点像,但是模板方法模式抽象基类中的抽象方法是实现功能时算法的抽象,而工厂方法模式抽象基类中的抽象方法是为了生成需要的组合具体对象,是生成对象的一种设计模式)

(ios实现抽象基类以及子类:声明一个接口协议(接口为基类中抽象化的接口),让父类遵循该接口协议,子类继承父类,同样子类也需要遵循该接口协议)

 

抽象工厂模式:(提供一个接口,用于创建相关或依赖对象的家族,而不需要明确指定具体类)

声明一个接口协议,该协议的接口是一组返回具体类型对象的接口,然后具体工厂类遵循该协议,再实现具体的抽象方法,实现过程中针对该具体工厂生成具体的类型对象返回出去给别人使用。

 

——————————————工厂方法和抽象工厂的区别:——————————————————

工厂方法:是基类中有一个抽象接口返回具体类型对象,子类需实现该接口返回确定的类型对象;

抽象工厂:是声明一个接口协议,该协议的众多接口都是为返回具体类型对象,也就是负责创建一组产品的接口不像工厂方法只有一个抽象返回具体类型对象的接口,然后让具体工厂类遵循该协议,再实现具体的抽象方法,针对该具体工厂生成具体的类型对象,不像工厂方法是子类实现父类的抽象方法

针对该子类生成具体的类型对象。

 

 

单例模式:(确保一个类只有一个实例,并提供全局访问点)

编写类时,在类内部声明一个该类的静态对象,重写初始化方法时,判断该静态对象是否为空,是便为第一次生成需要对其分配内存,否则直接返回该对象,期间需要注意多线程安全问题,并且一个提供一个类方法直接获取该类对象。

 

命令模式:(将请求封装成对象,可以让你使用不同的请求,队列,或者日志请求来参数化其他对象。命令模式也可以支持撤销操作。)

已经有一群具体类(被马仔命令的类)中有各种操作接口,我们需要用不同命令管理控制这一群类,需要声明一个所有command(马仔)都需要遵循的接口协议,然后根据那群具体类中的每个操作设计一个对应的马仔command类,需要遵循那个command协议,并且类中有一个被命令对象的引用在初始化的时候被赋值, 然后实现该类遵循的协议抽象方法,在该协议抽象方法中有一个发起命令的抽象接口供老板叫马仔去发起命令从而调用具体类的对应方法。再设计一个老板类也就是控制器类,里面可以有一个数组用来保存众多马仔,并且提供外部方法只要传入下标就让马仔发起命令。如果逐个命令执行不爽,可以再宏马仔类(马仔领班),该类同样遵循那个协议,类内部有一个数组保存一组命令(马仔们)在初始化的时候被赋值,实现协议抽象接口时便让所以马仔都调用他们的抽象接口便可。这个马仔领班同样时id<马仔协议>类型,一样可以传入老板类让老板号召。至于撤销功能,让所有马仔遵循的协议添加一个撤销抽象方法,所有马仔实现他做他的反操作,让后老板类中有一个最近一个操作的马仔引用,在撤销的时候便调用该引用的撤销抽象协议方法便可。

 

适配器模式:(将一个类的接口,转换成客户期望的另一个接口,适配器让原本接口不兼容的类可以合作无间)

我们已经封装好一些控制类了,有一些新的类供我们使用,但是他提供的接口和我们原来写好的控制类

不兼容,我们又不想改封装好的类代码,那么适配器派上用场了,首先声明一个接口协议,该协议接口是根据迁就控制类而定的一套接口,然后设计一个适配器类遵循该协议,类中有一个被适配对象的引用并且初始化时赋值,然后适配器类就实现协议抽象接口,在实现的过程中调用引用对应的功能方法,这样就把改变让新建的适配器类承担了,不用动封装好的控制类了,控制类也可以像平时使用一样去使用适配器类从而间接使用了新的类功能了。

 

外观模式:(提供了一个统一的接口,用来访问子系统中的一群接口。外观定义了一个高层接口,让子系统更容易使用)

有很多子系统的组件类,类中有很多操作方法,在主系统中想做一个动作,需要在主系统中拿到各种组件,然后逐个组件去执行他们对应的操作,这个主系统每次想做这个动作都得重新去拿到各种组件,然后逐个组件去执行他们对应的操作,这时外观模式出现了,把那一系列子组件动作封起来,新设计一个外观类,一个外观类对应一个主系统的动作,类中有刚那一系列子组件对象引用,并初始化时被赋值,

让后该外观类提供一个简洁的接口供主系统使用去执行想做的那个动作,外观类实现那个接口的时候就调用各种子组件的操作以完成主系统想要的该动作。这样每次主系统想做那个动作只需要执行那个外观类的那个简洁方法便可。

 

模板方法模式:(在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中,模板方法使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤)

我们对有共同操作方法但有的方法又有各自特点的类进行整理,写一基类抽出共同操作方法也就是对什么类来说都不变的方法写死在基类中,会变不同子类有不同特点的方法声明为抽象方法,如果仅仅这样不能算模板方法模式,应该并且基类有一个模板方法,该方法内指定了调用类内的方法包含具体方法与抽象方法的顺序,这样既定下了算法的顺序,又能让基类中抽象的接口在子类中才实现具体的算法。至于对模板方法挂钩,可以根据实际情况改变算法骨架也就是改变模板方法中的顺序,实现:在基类中添加一个抽象返回bool值的方法,然后基类中的模板方法中根据调用这个抽象方法的结果而去做不同的算法,因为这挂钩方法是个抽象方法,所以具体实现也是延迟到了具体子类中实现。这样特殊子类便可以有特殊的算法步骤了。

 

迭代器模式:(提供一种方法顺序访问一个聚合对象中的各个元素,而又不暴露其内部的表示)

用一个统一的方式hasnext方法和next方法去遍历任何数据结构的集合,那么迭代器出现了,声明一个迭代器都应该遵循的接口协议,其中有返回bool类型的hasNext抽象方法和放回id类型的next抽象方法,然后针对不同的数据结构集合的类型设计对应的具体迭代器类需要遵循那个协议,迭代器具体类中有一个该数据结构集合比如数组的引用并在初始化时赋值,还有一个记录位置的变量,在实现hasNext方法时根据记录位置的变量和集合的数量比较得出返回真假,next方法则根据记录位置的变量返回集合中下一个元素。有了迭代器,虽然因不同的类型数据集合有不同的具体迭代器类,但是其实他们都是id<迭代器协议>类型,使用迭代器具体类的用户也可以统一操作不同类型的具体迭代器类。简单数据结构集合一般可以用for in 循环达到迭代器功效。

 

组合模式:(允许你将对象组合成树形结构来表现“整体/部分”层次结构。组合能让客户以一致的方式处理个别对象以及对象组合)

 

状态模式:(允许对象在内部状态改变时改变它的行为,对象看起来好像修改了它的类)

有时候有一组动作,每个动作在不同的状态下有不同的处理方式,因为随时可能添加新状态,要管理起来就难了,所以状态模式出现了,先声明一个状态接口协议,协议内针对每个动作都写成一个对应的方法,然后每种状态设计一个对应的具体状态类遵循状态接口协议,实现各自状态中的不同动作操作,这样就把动作操作的管理放到了各自的状态中管理了,因为状态类中的动作方法中有可能需要对主系统的状态获取并设置(主系统中有各个具体状态对象其实他们也是抽象对象id<状态协议>),所以状态类中还有一个主系统对象的引用,并且在初始化时被赋值,然后设计主系统具体也就是这些状态的载体,首先类中包含了各种抽象具体状态对象和一个当前状态的对象,并在初始化的时候初始化他们,然后主系统中也有对应那组每个动作对应的方法,每个动作方法里面就执行当前具体状态类对象中对应的动作方法即可。这样动作的具体实现就放到了状态类中而不在主系统类中了。

 

代理模式:(为另一个对象提供一个替身或占位符以控制对这个对象的访问)

 

复合模式:(复合模式结合两个或以上的模式,组成一个解决反感,解决一再发生的一般性问题)

 

设计模式:(模式是在某情景下,针对某问题的某种解决方案)

 

 

模式分类:

创建型:(涉及到将对象实例化,这类模式都提供一个方法,将客户从所需要实例化的对象中解耦)

单例模式,抽象工厂模式,工厂方法模式

 

行为行:(只要是行为型模式,都涉及到类和对象如何交互及分配职责)

模板方法模式,命令模式,观察者模式,状态模式,策略模式,迭代器模式

 

结构型:(可以让你把类或对象组合到更大的结构中)

装饰者模式,适配器模式,复合模式,外观模式,代理模式

 

 

警告:过度使用设计模式可能导致代码被过度工程化。应该总是用最简单的解决方案完成工作,并在真正需要模式的地方才使用它。

 

posted @ 2016-03-13 13:38  Jk_Chan  阅读(231)  评论(0编辑  收藏  举报