OOP->设计原则->设计模式->架构模式

这次,想码一下软件开发的内功心法:面向对象的基本特征,类关系,设计原则,设计模式,架构的概念。

一、面向对象

面向对象有四个基本特征:抽象,封装,继承,多态

1. 抽象

  • 忽略一个主题中与当前目标无关的东西,专注的注意与当前目标有关的方面(就是把现实世界中的某一类东西,提取出来,用程序代码表示,抽象出来的一般叫做类或者接口)。抽象并不打算了解全部问题,而是选择其中的一部分,暂时不用部分细节。抽象包括两个方面,一个数据抽象,而是过程抽象.
  • 数据抽象 –>表示世界中一类事物的特征,就是对象的属性,比如鸟有翅膀,羽毛等(类的属性)
  • 过程抽象 –>表示世界中一类事物的行为,就是对象的行为,比如鸟会飞,会叫(类的方法)

2. 封装

  • 封装就是把过程和数据包围起来,对数据的访问只能通过特定的界面,如私有变量,用set,get方法获取
  • 封装是为了隐藏内部实现细节,是保证软件部件具有优良的模块性的基础。封装的目标就是要实现软件部件“高内聚,低耦合”,防止程序之间的相互依赖性带来的变动影响。
  • *

3. 继承

一种联结类的层次模型,并且允许和鼓励类的重用,提供一种明确表达共性的方法。对象的一个新类可以从现有的类中派生,这个过程称为类继承。新类继承了原始类的特性,新类称为原始类的派生类(子类),原始类称为新类的基类(父类)。派生类可以从它的父类哪里继承方法和实例变量,并且类可以修改或增加新的方法使之更适合特殊的需要。因此可以说,继承为了重用父类代码,同时为实现多态性作准备。

4. 多态

多态是指允许不同类的对象对同一消息做出不同的响应。多态性包括参数化多态性和包含多态性。多态性语言具有灵活/抽象/行为共享/代码共享的优势,很好的解决了应用程序函数同名问题。总的来说,方法的重写,重载与动态链接构成多态性。

动态链接 –>对于父类中定义的方法,如果子类中重写了该方法,那么父类类型的引用将调用子类中的这个方法,这就是动态链接。

二、类与类之间的关系

类与类或接口之间有六种关系,可以分为纵向关系和横向关系(纵向关系强于横向关系),纵向关系有泛化,实现;横向关系有依赖,关联,聚合组合。横向关系的强弱:依赖(可能发生关系,也可能不)<关联(有联系)<聚合(整体与部分,可分)<组合(整体与部分,生死关系)

2.1 泛化

  • 泛化关系(Generalization)也就是继承关系,也称为“is-a-kind-of”关系,泛化关系用于描述父类与子类之间的关系,父类又称作基类或超类,子类又称作派生类。
  • 在UML中,泛化关系用带空心三角形的直线来表示。

2.2 实现(接口)

  • 是用来规定接口和实现接口的类或者构建结构的关系,接口是操作的集合,而这些操作就用于规定类或者构建的一种服务。
  • 接口之间也可以有与类之间关系类似的继承关系和依赖关系,但是接口和类之间还存在一种实现关系(Realization),在这种关系中,类实现了接口,类中的操作实现了接口中所声明的操作。
  • 在UML中,类与接口之间的实现关系用带空心三角形的虚线来表示。

2.3 依赖

  • 假设A类的变化引起了B类的变化,则说名B类依赖于A类。
  • 依赖关系(Dependency) 是一种使用关系,特定事物的改变有可能会影响到使用该事物的其他事物,在需要表示一个事物使用另一个事物时使用依赖关系。大多数情况下,依赖关系体现在某个类的方法使用另一个类的对象作为参数。
  • 在UML中,依赖关系用带箭头的虚线表示,由依赖的一方指向被依赖的一方。
  • 依赖关系是一种很弱的关系,具体某个对象可能会依赖于另一个对象,也可能不。

2.4 关联

  • 关联关系(Association) 是类与类之间最常用的一种关系,它是一种结构化关系,用于表示一类对象与另一类对象之间有联系。
  • 在UML类图中,用实线连接有关联的对象所对应的类,在使用Java、C#和C++等编程语言实现关联关系时,通常将一个类的对象作为另一个类的属性。
  • 在使用类图表示关联关系时可以在关联线上标注角色名。

2.5 聚合

  • 表示的是整体和部分的关系,整体与部分可以分开.
  • 通常在定义一个整体类后,再去分析这个整体类的组成结构,从而找出一些成员类,该整体类和成员类之间就形成了聚合关系。
  • 在聚合关系中,成员类是整体类的一部分,即成员对象是整体对象的一部分,但是成员对象可以脱离整体对象独立存在。
  • 在UML中,聚合关系用带空心菱形的直线表示。

2.6 组合

  • 也是整体与部分的关系,但是整体与部分不可以分开
  • 组合关系(Composition)也表示类之间整体和部分的关系,但是组合关系中部分和整体具有统一的生存期。一旦整体对象不存在,部分对象也将不存在,部分对象与整体对象之间具有同生共死的关系。
  • 在组合关系中,成员类是整体类的一部分,而且整体类可以控制成员类的生命周期,即成员类的存在依赖于整体类。
  • 在UML中,组合关系用带实心菱形的直线表示。

三、面向对象的设计原则

一段代码,一个程序的好坏,有三个质量标准:正确性,可扩展型,可读性。面向对象的设计原则可以帮助开发者提高软件的三个质量标准,提高软件的可维护型,延长它的生命周期。

面向对象的设计原则分为对一个类类的设计或者多个类合作的设计。
对一个类的面向对象设计原则如下:
1. 封装、抽象和信息隐藏
2. 关注点分离和单一职责原则
3. 接口隔离原则
对多个合作类的面向对象设计原则如下:
1. 松耦合
2. 里氏替换原则
3. 契约式设计
4. 开闭原则
5. 依赖倒置原则

3.1 封装、抽象与信息隐藏

封装实现了信息隐藏(隐藏了内部的细节,包括数据和功能的实现)封装后只能通过接口对外。
方法的接口使对象的功能抽象化,对象要实现接口中定义的方法,从对象外面看,只能看到方法的定义。
对象只能通过定义的方法与外面的环境进行交流,对象中定义的方法就是对象对外的接口
信息隐藏或者保密原则意味着一个子系统(对象)不需要知道另外一个子系统的内部属性,这样可以避免一个子系统依赖于另一个子系统的实现。

3.2 关注点分离与单一职责原则

关注点分离原则要求每一个功能对应一个单独的任务,每一个功能都要在一个独立的模块中实现
单一职责原则:修改一个类的理由从来不应超过一个
单一职责原则对类有效,关注点分离具有普遍的有效性,可以把关注点分离原则看作是一个拆分过程,单一职责原则是对类的面向对象设计原则。

3.3 接口隔离原则

接口隔离原则要求把大接口分解成小接口,用小的接口来满足不同客户端的要求。客户端只包含它使用的接口,这样可以防止客户端对它不需要的接口有依赖性

客户端不应该被强迫要求依赖于它不需要的接口。对不需要的接口进行修改就不会影响到客户端程序。

3.4 松耦合

一个好的设计应该是
在子系统内部应该具有尽可能高的连接强度或者高内聚
子系统之间应该是松耦合的
应设计松耦合的子系统,并让每个子系统实现高内聚

子系统间的松耦合的优点是增加了子系统间链接的灵活性,使子系统的更换更为简单,减少了相互依赖造成的问题

3.5 里氏代换原则

里氏代换:

对象的多态意味着,一个对象处在一个有继承关系的链中,它可以变化为不同的类型,它可以是一个基类对象,也可以是一个子类对象。
在程序中如果用子类的对象替代了基类对象,基类方法的调用者并不会觉察到。因此,重写的方法必须和基类中对应的方法具有相同的行为。以同样的方法可以调用基类的方法和其子类的方法,他们的返回值也应该是同类的。

里氏代换的要求:

在程序中使用对象的多态性,就是把程序中指向派生类实例的引用当做指向基类实例的引用。派生类中重写的方法必须遵守与其他使用这个派生类的类之间的契约,也就是说不能违反基类中这个方法的前置条件,后置条件和类不变式。

只要派生类和它相应的基类之间的契约没有被打破,就可以在使用基类的程序中同样使用子类,这些子类可以随后再创建。

3.6 契约式设计

契约

  1. 一个类不仅仅由方法和属性组成,类要被其它类使用,使用它的类称为客户类,类和它的客户类具有关系
  2. 契约式设计要求这种关系的参与者具有形式上的一致性和精确的定义,即在什么情况下执行哪种正确的程序流程。
  3. 在调用一个方法时,调用者和被调用的方法之间是相互依赖的。它们之间的关系就是契约(麻蛋,就这样?),契约如果被破坏,就会发生错误。一个契约双方都有权利和义务。
  4. 契约借助于断言详细说明。断言是在程序某一处的一个布尔型的表达式,其值不能为假。

断言

一个契约包含三种不同的断言:前置条件,后置条件,不变式。一个方法的契约包括它的前置条件和后置条件,类的契约包括方法的契约和类不变式(因为一个类的所有方法都必须遵守不变式,所以也称它为类不变式)。

前置条件:定义正确调用方法的限制
后置条件:描述的是方法被调用后的状态
类不变式:是与类有关的一个断言。一个类的所有实体都必须遵守这个断言。断言的属性对于一个类的所有操作都有效,并不是只对一个方法有效。

  • 不变式是类中所有对外可见的方法在调用前和调用后都必须遵守的,不变式在这个类的对象的生命周期始终有效
  • 重写类的方法不能强化前置条件,相反,可以弱化前置条件;不能弱化后置条件,相反,可以强化后置条件

3.7 开闭原则

开闭原则来自Bertrand Meyer,他提出:“模块应该是开放的和闭合的。”
开放的含义是说明其是可扩展的,如,可以添加新的模块或数据项,在不改变代码和类的原始接口的情况下,类应该具有适应性并且可以略作改动,可以通过静态继承,动态地使用聚集的概念。
如果模块以不可改变的形式在库的框架内供其它模块使用,模块就是闭合的,这说明这个模块是稳定,可复用。在编程的层面上是闭合的,说明代码关闭了修改的可能。

程序代码的闭合性或者可执行程序的闭合性与可修改性想必,可以保证库里面已有程序的强壮性和可复用性。程序代码的开放性或者可执行程序的开放性使人们赢得了把已有的程序植入到新的模块中的复用性,并以此实现了对当前程序的扩展性。

3.8 依赖倒置原则与控制反转

在依赖倒置原则和控制反转中,其依赖关系将会发生倒置。在依赖倒置原则中,依赖关系的倒置是发生在层次结构的系统中;在控制反转中,依赖关系的倒置式发生在平等关系的对象中。

依赖倒置原则

高层模块不应该依赖于低级别的模块。两者都应该依赖于抽象。抽象不应该依赖于细节,细节应该依赖于抽象

通过依赖反转关系,就可以简单地重复使用高层中的元素,因为它们已经不再依赖于低层中的元素。此外,因为低层可以全部被替换,高层并不会受到影响,所以高层中的类还可以通过Mock对象测试。

控制反转

控制反转可以应用在同一等级中的对象,以转变它们之间的依赖关系。

在控制流反转时,原先掌握控制流的程序要把控制权转交给另外的模块(大部分是可复用的模块),这样可以从轮询转换到事件驱动。一个可复用的模块(例如框架)常常调用专有的模块(例如应用程序)。

控制流的反转可以使程序由原先的轮询方式转变为事件驱动

控制反转的实际应用例子是观察者模式。

3.9 最少知识原则(迪米特法则)

每个单元只能和它的 “朋友” 交谈,不能和 “陌生人” 交谈;

可以理解为封装具体的实现,将多个接口封装为一个直接的接口

四、设计模式

常用的设计模式有23中,这里简单罗列其中的几个。

4.1 单例模式

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

单例模式又分为饿汉模式,懒汉模式,区别体现在对象在什么时候实例化。

4.2工厂方法模式

工厂方法模式定义了一个创建对象的接口,但由子类决定要实例化的类是哪一个。工厂方法把实例化推迟到子类。

4.3 抽象工厂模式

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

(notice: 工厂方法模式只是创建一个产品,而抽象工厂模式是创建了对象的家族,即抽象工厂模式定义了创建多个产品的接口)

4.4 生成器模式

使用生成器模式封装一个产品的构造过程,并允许按步骤构造。

4.5 观察者模式

观察者模式定义了对象间的一对多依赖,这样一来,当一个对象改变状态时,它的所有依赖者都会受到通知并自动更新

装饰者模式

装饰者模式动态地将责任附加到对象上。若要扩展功能,装饰者提供了比继承更有弹性的替代方案

4.6 责任链模式

当你想要让一个以上的对象有机会能够处理某个请求的时候,就是用责任链模式

理解,具体例子:Android中的事件传递机制。

4.7 中介者模式

使用中介者模式(Mediator Pattern)来集中相关对象之间复杂的沟通和控制方式

4.8 解释器模式

使用解释器模式(InterpreterPattern)为语言创建解释器

4.9 原型模式(Prototype Pattern)

当创建给定类的实例的过程很昂贵或者很复杂时,就使用原型模式

参考Java中的浅拷贝和深拷贝
(notice: 更多详细内容请参考《Head First 设计模式》《设计模式—可复用面向对象软件的基础》)

五、架构模式

设计模式提供了用于解决具体的问题模式,而架构模式是粗线条的模式,它的作用不是解决局部问题,而是要影响一个系统的基本结构。

架构模式可以分为系统的结构(分层架构模式,管道和过滤器架构模式),自适应系统(插件架构模式),分布式系统(中介模式,面向服务的架构模式),交互式系统(模型-视图-控制器模式)。

5.1 分层架构模式

把一个系统的架构划分为不同的层次,每一层可以调用下一层的服务。这个模式要求的运行的流程是:客户端把系统的最上层作为服务器进行调用,最上层又作为客户,把下一层视为服务器进行调用,系统以这种方式一直调用到最后一层。

5.2 管道和过滤器架构模式

把一个应用的基本形式构造成按时间顺序加工处理的链状结构,它们之间通过输出和输入相互耦合:一个进程的输出是下一个进程的输入。这个架构是把一个系统划分成由进程组成的链状结构,每个进程都接收前一个进程的结果,然后再进一步加工。因此管道用于解决两个相邻进程的异步解耦。

5.3 插件架构模式

插件架构模式对于特定的应用,把可以扩展的部分构造成接口形式,这样就可以通过使用插件实现这些接口,由插件管理者负责实例化和加载。应用在没有扩展的情况下也可以正常运行。通过使用插件架构模式,没有扩展的基本应用可以具有广泛的用户,而扩展后的应用针对某些用户群。

5.4 中介模式

构造的系统是由多个客户端和服务器组成的一个架构,中介作为中间者实例对客户提出的需求根据其所需的服务找到相应的服务器,并把服务器的应答送至客户端。客户组件和服务组件通过中介交流。在分布式系统中,中介作为中间件分布在系统中的所有节点计算机中。

5.5 面向服务架构

面向服务架构的设计模式把系统的架构划分为组件或子组件,它们与系统设计阶段的应用实例或部分应用实例相对应,通过提供服务反应自身的功能,因此服务流程的改变只有一下影响:作为应用实例所代表的相应组件必须被修改;在使用现有基本服务的基础上生成新的组件。

5.6 模型-视图-控制器模式(MVC)

把应用分解成模型组件,视图和控制器。因此模型不依赖与输入和输出。使用这个策略易于更换系统中的视图和控制器,因此应用的核心部分比输入和输出程序有更长的试用期。

更多详细内容请参考:《软件架构与模式》

六 总结

在软件开发中,使用面向对象的思想对现实世界,现实问题进行抽象和封装,从而将问题抽象为计算机可以处理的问题;解决问题的方法有很多种,而符合面向对象的设计原则的方法可以提高软件的准确性,可扩展性和健壮性;从架构模式的高度看问题,会有一种跳出庐山,看清庐山真面目的感觉,很多问题自然会豁然开朗。

参考

  1. 《软件架构与模式》 Joachim Goll注,贾山等译
  2. 《Head First 设计模式》 Eric Freeman 等著
  3. 《设计模式—可复用面向对象软件的基础》
原创文章 25 获赞 12 访问量 1万+

猜你喜欢

转载自blog.csdn.net/half_bottle/article/details/78994136