面向对象相关

1   面向对象

1.1 什么是面向对象

面向对象就是以对象来认知客观世界和抽象事物,以封装、抽象、继承、聚合、组合等来组织对象,从而构建相应的软件系统。

按对象去理解现实世界,更符合现实世界的组织方式。对象可以是客观可见,如经脉,花鸟,石头,学生,班级,学校,公司,国家;也可以是抽象出来的,如感情,欲望,神话,故事。对象可以有自身的状态、行为,并可以互相交互。

 

1.2 面向对象的特征

 本质:对象的划分,组织和交互。

设计的许多对象来源于现实世界,但是,结果所得到的的许多类通常在现实世界中并不存在。设计中的抽象对于灵活的设计是至关重要的。例如,描述过程或算法的类在现实中并不存在,却被设计成了对象,实体的状态也被设计成了对象。这些对象在分析阶段,或者设计的早期,并不存在,后来为使设计灵活,扩展、复用,被发掘或抽象了出来。

把流程划分为对象,把结构组织成对象,让对象进行交互。

 

知:容易理解,减少了细节,降低了复杂性、多样性。封装了过程和变化。

行:容易扩展。对于多样性和变化,直接扩展子类。需要增加新的功能,扩展类型。

 

1.2.1 继承

Is-a的关系用继承。少用继承,多用组合。

Has-a用组合。

 

覆盖:override

遮蔽:new

防止继承:sealed

要求子类必须实现:abstrace

 

1.2.2 多态

本质——函数具有不同实现。

1)静态多态

函数重载:同一个函数名,参数类型、返回值不同。

运算符重载:运算符重载,就是对已有的运算符重新进行定义,赋予其另一种功能,以适应不同的数据类型。

struct Vector

{

public double x,y,z;

public Vector(double x,double y,double z)

{

this.x = x;

this.y = y;

this.z = z;

}

 

public static Vector operator + (Vector lhs,Vector rhs)

{

}

}

运算符重载的声明方式与方法的声明方式相同,但operator关键字告诉编译器,它实际上是一个运算符重载,后面是相关运算符的符号,在本例中就是+。返回类型是在使用这个运算符时获得的类型。在本例中,把两个矢量加起来会得到另一个矢量,所以返回类型就是Vector。对于这个+运算符重载,返回类型与包含类一样,但这种情况并不是必需的。两个参数就是要操作的对象。对于二元运算符(带两个参数),如+和-运算符,第一个参数是放在运算符左边的值,第二个参数是放在运算符右边的值。

C#要求所有的运算符重载都声明为public和static,这表示它们与它们的类或结构相关联,而不是与实例相关联,所以运算符重载的代码体不能访问非静态类成员,也不能访问this标识符

 

2)动态多态

定义时,同一个父类型的方法在不同子类有不同实现。

使用时,同样的父类型,指向不同的子类。

运行时,同样的父类型,但因指向的子类不同,呈现不同的状态。

 

1.2.3 封装

面向对象,封装成函数,封装成类,抽象出上层类或者接口。使用聚合、组合。

 

类:封装了数据和行为。屏蔽了内部实现细节,只暴露了公共接口来使用。

抽象:向下屏蔽了多样性和变化性。向上统一了归属和行为结果。

父类——向下屏蔽了对象的多样性。向上统一了所属。

          接口——向下屏蔽了行为的变化性(不同实现)。向上统一了行为的对外体现(对外结果)。

 

1.2.4 抽象类和接口

抽象类:类型共属。

接口:行为共有。

 

抽象:向下屏蔽了多样性和变化性。向上统一了归属和行为结果。

父类——向下屏蔽了对象的多样性。向上统一了所属。

          接口——向下屏蔽了行为的变化性(不同实现)。向上统一了行为的对外体现(对外结果)。

 

 

所以,抽象类比接口包括的更大。如果只是行为共有,抽象成接口,使用接口继承。

 

1.2.5 组合和聚合

组合——组成。 同生共死。

聚合——持有。主被性。你死你的。

组合和聚合是有很大区别的,这个区别不是在形式上,而是在本质上: 比如A类中包含B类的一个引用b,当A类的一个对象消亡时,b这个引用所指向的对象也同时消亡(没有任何一个引用指向它,成了垃圾对象),这种情况叫做组合,反之b所指向的对象还会有另外的引用指向它,这种情况叫聚合。 在实际写代码时组合方式一般会这样写: A类的构造方法里创建B类的对象,也就是说,当A类的一个对象产生时,B类的对象随之产生,当A类的这个对象消亡时,它所包含的B类的对象也随之消亡。 聚合方式则是这样: A类的对象在创建时不会立即创建B类的对象,而是等待一个外界的对象传给它 传给它的这个对象不是A类创建的。 现实生活中: 人和人和手,脚是组合关系,因为当人死亡后人的手也就不复存在了。人和他的电脑是聚合关系

 

1.3 面向对象设计的6原则

对象定义和交互的原则。总原则开闭。

 

定义——单一职责。

交互——依赖最小的范围,依赖最少的依赖。

依赖范围最小的方法依赖抽象,抽象包括抽象出父类和抽象出接口。抽象出父类后,如何实现继承。抽象接口时,应该使接口最小,以减少依赖。

依赖最少——迪米特法则

 

开闭——抽象框架,扩展细节和变化。

 

尽量降低复杂性,提高抽象。

抽象更稳定。抽象能力是设计的关键能力。

如何抽象:

父类:类型抽象。

接口:行为抽象。

委托:方法抽象。

如果组织对象的交互:

架构模式:分层模式,MVVM,数据总线模式,消息队列,CS/BS,管道过滤器,

设计模式:行为模式(策略,),结构模式(桥接,代理,中介,外观,)

 

1.3.1 单一职责——一类一职

不要存在多于一个导致类变更的原因。

 

一个类只提供此类的功能。

扩展:

一个函数只实现一个功能。

一个字段只代表一种含义。

 

  • 可以降低类的复杂度,一个类只负责一项职责,其逻辑肯定要比负责多项职责简单的多;
  • 提高类的可读性,提高系统的可维护性;
  • 提高类的可读性,提高系统的可维护性;
  •  变更引起的风险降低,变更是必然的,如果单一职责原则遵守的好,当修改一个功能时,可以显著降低对其他功能的影响。

在职责扩散到我们无法控制的程度之前,要立刻对代码进行重构

 

 

 

1.3.2 依赖

1)依赖倒置——依赖抽象(接口和父类)

依赖抽象,不要依赖于具体。外部看到是抽象,不能看到具体。

抽象层之间:对于子类有父类没有的方法,子类尽量不要公开。对外的内容由父类负责。对外看到的是父类,是抽象,尽量不要看到子类的独有内容。

 

抽象指父类和接口。接口包括抽象出的interface,对外或向上提供的一组函数,屏蔽了内部实现。

 

High level modules should not depend upon low level modules.Both should depend upon abstractions. 高层模块不应该依赖低层模块,两者都应该依赖其抽象(模块间的依赖通过抽象发生,实现类之间不发生直接的依赖关系,其依赖关系是通过接口或抽象类产生的)

Abstractions should not depend upon details. 抽象不应该依赖细节(接口或抽象类不依赖于实现类)

Details should depend upon abstractions. 细节应该依赖抽象(实现类依赖接口或抽象类)

对外提供一组可访问的函数,屏蔽内部实现。

向上或向下提供一组可访问的函数,使其与本层隔离。

 

(1)接口隔离——最小接口

定义:客户端不应该依赖它不需要的接口;一个类对另一个类的依赖应该建立在最小的接口上。

 

 

 

定义:客户端不应该依赖它不需要的接口;一个类对另一个类的依赖应该建立在最小的接口上。

问题由来:类A通过接口I依赖类B,类C通过接口I依赖类D,如果接口I对于类A和类B来说不是最小接口,则类B和类D必须去实现他们不需要的方法。

 

解决方案:将臃肿的接口I拆分为独立的几个接口,类A和类C分别与他们需要的接口建立依赖关系。也就是采用接口隔离原则。

 

 

(2)里氏替换——正确继承

任何基类可以出现的地方,子类一定可以出现。

基类可以替换子类。

 

子类完全实现父类的方法,也可以增加自己独有的方法。

子类函数:入参类型可以大于父类,返回值类型可以小于父类。宽进窄出。

 

 

2)迪米特法则—最少知道

 

定义:一个对象应该对其他对象保持最少的了解。(对自己依赖的类知道的越少越好。)

问题由来:类与类之间的关系越密切,耦合度越大,当一个类发生改变时,对另一个类的影响也越大。

 

解决方案:尽量降低类与类之间的耦合。

 

         自从我们接触编程开始,就知道了软件编程的总的原则:低耦合,高内聚。无论是面向过程编程还是面向对象编程,只有使各个模块之间的耦合尽量的低,才能提高代码的复用率。低耦合的优点不言而喻,但是怎么样编程才能做到低耦合呢?那正是迪米特法则要去完成的。

 

         迪米特法则又叫最少知道原则,最早是在1987年由美国Northeastern University的Ian Holland提出。通俗的来讲,就是一个类对自己依赖的类知道的越少越好。也就是说,对于被依赖的类来说,无论逻辑多么复杂,都尽量地的将逻辑封装在类的内部,对外除了提供的public方法,不对外泄漏任何信息。迪米特法则还有一个更简单的定义:只与直接的朋友通信。首先来解释一下什么是直接的朋友:每个对象都会与其他对象有耦合关系,只要两个对象之间有耦合关系,我们就说这两个对象之间是朋友关系。耦合的方式很多,依赖、关联、组合、聚合等。其中,我们称出现成员变量、方法参数、方法返回值中的类为直接的朋友,而出现在局部变量中的类则不是直接的朋友。也就是说,陌生的类最好不要作为局部变量的形式出现在类的内部。

————————————————

版权声明:本文为CSDN博主「三级小野怪」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。

原文链接:https://blog.csdn.net/zhengzhb/article/details/7296930

 

1.3.3 开闭原则——可扩展,不修改

对扩展开放,对修改关闭。

扩展:增加子类,增加类。增加接口的行为。

修改:使用if-else。

 

行为抽象:实现开闭原则的关键就在于“抽象”。把系统的所有可能的行为抽象成一个抽象底层,这个抽象底层规定出所有的具体实现必须提供的方法的特征。作为 系统设计的抽象层,要预见所有可能的扩展,从而使得在任何扩展情况下,系统的抽象底层不需修改;同时,由于可以从抽象底层导出一个或多个新的具体实现,可以改变系统的行为,因此系统设计对扩展是开放的。

我们在 软件开发的过程中,一直都是提倡需求导向的。这就要求我们在设计的时候,要非常清楚地了解用户需求,判断需求中包含的可能的变化,从而明确在什么情况下使用开闭原则。

变化封装或抽象:关于系统可变的部分,还有一个更具体的对可变性封装原则(Principle of Encapsulation of Variation, EVP),它从 软件工程实现的角度对开闭原则进行了进一步的解释。EVP要求在做系统设计的时候,对系统所有可能发生变化的部分进行评估和分类,每一个可变的因素都单独进行封装。

我们在实际开发过程的设计开始阶段,就要罗列出来系统所有可能的行为,并把这些行为加入到抽象底层,根本就是不可能的,这么去做也是不经济的。因此我们应该现实的接受修改拥抱变化,使我们的代码可以对扩展开放,对修改关闭。

 

开闭原则是面向对象设计中最基础的设计原则,它指导我们如何建立稳定灵活的系统。开闭原则可能是设计模式六项原则中定义最模糊的一个了,它只告诉我们对扩 展开放,对修改关闭,可是到底如何才能做到对扩展开放,对修改关闭,并没有明确的告诉我们。以前,如果有人告诉我“你进行设计的时候一定要遵守开闭原 则”,我会觉的他什么都没说,但貌似又什么都说了。因为开闭原则真的太虚了。

在仔细思考以及仔细阅读很多设计模式的文章后,终于对开闭原则有了一点认识。其实,我们遵循设计模式前面5大原则,以及使用23种设计模式的目的就是遵循 开闭原则。也就是说,只要我们对前面5项原则遵守的好了,设计出的软件自然是符合开闭原则的,这个开闭原则更像是前面五项原则遵守程度的“平均得分”,前 面5项原则遵守的好,平均分自然就高,说明软件设计开闭原则遵守的好;如果前面5项原则遵守的不好,则说明开闭原则遵守的不好。

其实笔者认为,开闭原则无非就是想表达这样一层意思:用抽象构建框架,用实现扩展细节。因为抽象灵活性好,适应性广,只要抽象的合理,可以基本保持软件架 构的稳定。而软件中易变的细节,我们用从抽象派生的实现类来进行扩展,当软件需要发生变化时,我们只需要根据需求重新派生一个实现类来扩展就可以了。当然 前提是我们的抽象要合理,要对需求的变更有前瞻性和预见性才行。

说到这里,再回想一下前面说的5项原则,恰恰是告诉我们用抽象构建框架,用实现扩展细节的注意事项而已:单一职责原则告诉我们实现类要职责单一;里氏替换 原则告诉我们不要破坏继承体系;依赖倒置原则告诉我们要面向接口编程;接口隔离原则告诉我们在设计接口的时候要精简单一;迪米特法则告诉我们要降低耦合。 而开闭原则是总纲,他告诉我们要对扩展开放,对修改关闭。

最后说明一下如何去遵守这六个原则。对这六个原则的遵守并不是是和否的问题,而是多和少的问题,也就是说,我们一般不会说有没有遵守,而是说遵守程度的多 少。任何事都是过犹不及,设计模式的六个设计原则也是一样,制定这六个原则的目的并不是要我们刻板的遵守他们,而需要根据实际情况灵活运用。对他们的遵守 程度只要在一个合理的范围内,就算是良好的设计。我们用一幅图来说明一下。

 

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

 

https://www.cnblogs.com/qinying/p/4376484.html

1.4 如何使用面向对象

抽象类型多样性和实现变化性:if-else,抽象出父类或者接口。

依赖抽象。

隐藏细节和内部实现:封装,只对外提供需要的部分。

 

流程:1.定义成一个对象或多个对象。前面的对象持有后面的对象。对外看到的是第一个对象,或者提供一个对外的适配对象。其中某些多个对象可以向上抽象。

      2.抽象成多个对象。在这些对象之外抽象出一个管理对象,负责流程。对外看到的是管理对象。

洗漱--坐车---打卡---工作---吃中饭--工作--打卡--坐车---到家吃饭

 

1.4.1 (寻找)划分对象

 

1.4.2 组织对象

 

1.4.3 对象交互

 

 

1.5 设计的技巧

抽象:同类抽象,行为抽象。

委托(事件):同行为抽象。

 

分离:

衔接方式(依赖方式):聚合,对外接口,事件,消息,队列(公共数据相互连接)

posted on 2019-12-15 15:01  Zachagy  阅读(53)  评论(0)    收藏  举报