面向对象基础
ZOU

说明

该文章大部分内容来自以下开源博客:
面向对象思想

1 三大特性

1.1 封装

利用抽象数据类型将数据和基于数据的操作封装在一起,使其构成一个不可分割的独立实体。内部的数据被隐藏了起来,外部只可以通过提供的接口访问数据。因此用户无需关心对象内部的细节,同时也无从得知其内部的细节。

优点:

  • 减少耦合:可以独立地开发、测试、优化、使用、理解和修改

  • 减轻维护的负担:可以更容易被理解,并且在调试的时候不影响其他模块

  • 有效地调节性能:可以通过剖析来确定哪些模块影响了系统的性能

  • 提高软件的可重用性

  • 降低了构建大型系统的风险:即使整个系统不可用,但是这些独立的模块却有可能是可用的

1.2 继承

继承实现了IS-A关系,例如Cat和Animal就是一种IS-A关系,Cat可以继承ziAnimal,从而获得Animal非private的属性和方法。

继承应该遵循里氏替换原则。

Cat可以当作Animal来使用,也就是说可以使用Animal引用Cat对象(这是一个十分常用的写法)。父类对象引用指向子类对象称为向上转型

1
Animal animal = new Cat();

1.3 多态

多态分为编译时多态和运行时多态:

  • 编译时多态主要指方法的重载(Overload),发生在一个类之内。常见的形式是重载多个构造函数,提供创建新对象时的方便。重载应该满足的条件是:

    • 函数名必须相同

    • 参数列表必须不同(参数个数、数据类型、参数顺序)

    • 函数的返回类型可以相同也可以不同

    • 仅仅返回类型不同不足以称为重载

  • 运行时多态是指程序中定义的对象引用所指向的具体类型在运行期间才确定,具体实现有重写(Override),运行时多态有三个条件:

    • 继承

    • 覆盖(重写)

    • 向上转型

重写与重载的区别

重载(Overload)实现的是编译时的多态性,重写(Overrid)实现的是运行时的多态性。重载发生在一个类中,同名的方法如果有不同的参数列表则视为重载;重写发生在子类与父类之间,重写要求子类被重写方法与父类被重写方法有相同的参数列表,有兼容的返回类型。

2 六种关系

2.1 泛化关系

用来描述继承关系,在Java中使用extends关键字,uml写法如下:

2.2 实现关系

用来实现一个接口,在Java中使用implements关键字。

2.3 聚合关系

聚合是关联关系的一种特例,他体现的是整体与部分、拥有的关系,即has - a的关系,此时整体与部分之间是可分离的,他们可以具有各自的生命周期,部分可以属于多个整体对象,也可以为多个整体对象共享,比如计算机与CPU、公司与员工的关系等。表现在代码层面,和关联关系是一致的,只能从语义级别来区分。

2.4 组合关系

组合也是关联关系的一种特例,他体现的是一种contains - a的关系,这种关系比聚合更强,也称为强聚合。他同样体现整体与部分间的关系,但此时整体与部分是不可分的,整体的生命周期结束也就意味着部分的生命周期结束,比如你和你的大脑,表现在代码层面,和关联关系是一致的,只能从语义级别来区分。

以公司和部门为例子,如果公司没了,则部门就不存在了。

2.5 关联关系

关联关系体现的是两个类、或者类与接口之间语义级别的一种强依赖关系,比如我和我的朋友。这种关系比依赖更强、不存在依赖关系的偶然性、关系也不是临时性的,关系一般是平等的、关联可以是单向、双向的。表现在代码层面,为被关联类B以类属性的形式出现在关联类A中,也可能是关联类A引用了一个类型为被关联类B的全局变量。

这是一种静态的关系,与运行过程的状态无关。可以用1对1、1对多,多对多这种关系来表示。举例来说,学校和学生就是一种关联关系,一个学校可以有很多学生,一个学生只能属于一个学校。这种关系在运行之前就可以确定。

2.6 依赖关系

可以简单的理解,就是一个类A使用到了另一个类B,而这种使用关系是具有偶然性的、临时性的、非常弱的,但是B类的变化会影响到A。比如某人要过河,需要借用一条船,此时人与船之间的关系就是依赖。表现在代码层面,为类B作为参数被类A在某个method方法中使用。

与关联关系不同的是,依赖关系是在运行过程中起作用的。A类和B类是依赖关系主要有三种形式:

  • A类是B类方法的局部变量

  • A类是B类方法的参数

  • A类向B类发送消息,从而影响B类发生变化

关系的耦合程度

耦合程度排序:泛化 ≈ 实现 > 组合 > 聚合 > 关联 > 依赖。

好的代码应该实现:高内聚,低耦合

3 五种设计原则

简写 全拼 中文翻译
SRP The Single Responsibility Principle 单一职责原则
OCP The Open Closed Principle 开放封闭原则
LSP The Liskov Substitution Principle 里氏替换原则
ISP The Interface Segregation Principle 接口分离原则
DIP The Dependency Inversion Principle 依赖倒置原则

3.1 单一职责原则

修改一个类的原因应该只有一个。

一个类只负责一件事,当这个类需要做过多事情的时候,就需要分解这个类。

如果一个类承担的职责过多,那么某个职责的变化可能会削弱这个类完成其他职责的能力。

多职责会导致脆弱和不易理解的设计。

3.2 开放封闭原则

类应该对拓展开放,对修改关闭。

该原则要求在添加新功能时可以新增,但是不能修改原代码。

符合开闭原则最典型的例子就是装饰者模式,它可以动态地将责任附加在对象上,而不用去修改类的代码。

实现OCP的主要机制是抽象和多态,LSP和DIP是OCP的基础。

3.3 里氏替换原则

子类对象必须能够替换掉所有父类对象。

所有出现父类对象的地方将其换成子类都不会出问题。子类应该在实现父类的一切功能的基础上比父类更加特殊。

LSP是多态顺利实现的保证,从而使OCP称为可能,因为正是子类型的可替换性才使得使用基类的模块在无需修改的情况下就可以拓展:

  • 增加或修改任何一个子类型,基类不用修改(封闭)

  • 基类的使用者(客户程序)可以通过多态得到拓展或修改过的行为(开放)

3.4 接口分离原则

不应该强迫客户依赖于他们不用的方法。

使用多个专门的接口比使用单一的总接口要好。

3.5 依赖倒置原则

高层模块不应该依赖于低层模块,二者都应该依赖于抽象。
抽象不应该依赖于细节,细节应该依赖于抽象。

按照自上而下的依赖关系,高层的策略设置模块往往是无法重用的,如果设法让高层模块独立于低层模块,则实现重用就变成可能。

面向接口编程:依赖倒置原则的启发式建议是“依赖于抽象”,具体做法是将高层需要的服务声明为抽象接口,高层使用这些接口,低层实现这些接口,使得高层不再依赖于底层,而是依赖于抽象接口,同样底层也依赖于抽象接口。

依赖于抽象意味着:

  • 任何变量都不应该持有一个指向具体类的指针或者引用

  • 任何类都不应该从具体类派生

  • 任何方法都不应该重写它的任何基类中已经实现的方法

细节

抽象类和接口的区别

  • 抽象类中可以有0~n个抽象方法,也可以有普通方法,接口中只能有抽象方法(JDK1.8之后允许有普通方法)。

  • 抽象类定义之前必须有abstract修饰符,而接口不用class,用interface,其实这也说明了接口并不是类。

  • 抽象类可以继承自抽象类,接口可以继承自接口,抽象类可以实现接口,但是接口不能实现接口。

  • 抽象类中的成员变量和方法可以是各种类型的(public,protected,default,private),但抽象方法必须是protected or public(因为要给子类访问)。接口不能有变量,只能拥有常量(public static final),抽象方法必须为public(通用接口)。

  • 本文标题:面向对象基础
  • 本文作者:ZOU
  • 创建时间:2021-06-14 12:37:50
  • 本文链接:https://yipeng.xyz/2021/06/14/面向对象基础/
  • 版权声明:可随意使用,但是转载请联系我!
 评论