面向对象编程

1. 面向对象概念

1.1 什么是对象

对象是相关状态和行为的包装,软件对象通常用于对日常生活中找到真实对象进行建模。

对象是理解面向对象技术的关键。看看周围,你会发现有许多真实世界的对象的例子:你的狗狗、笔记本、桌子、显示器、自行车。

显示世界的对象有两个特征:状态和行为。狗有状态(名称,颜色,品种)和行为(吠叫,跑,摇尾巴)。识别真实对象的状态和行为是开始思考面向对象编程一种良好的方法。

软件对象在概念上与真实对象相似:它们也由状态和行为组成。对象将其状态存储在字段中,并通过方法公开其行为。方法对对象的内部状态进行操作,并充当对象和对象通信的主要机制。隐藏内部状态并要求通过对象的方法执行所有交互称为数据封装,这是面向对象编程的基本原则。

在这里插入图片描述

将代码捆绑到单个对象中提供了许多好处:

  1. 模块化:对象的源代码可以独立于其它对象的源代码编写和维护。创建后,可以在系统内轻松传递对象;
  2. 信息隐藏(封装):仅通过与对象的方法交互,其内部实现的详细信息仍然隐藏在对象内部;
  3. 代码可重用:如果一个对象已经存在,你可以在程序中使用该对象。这允许你实现/测试/调试复杂的任务的特定对象,然后可以信任这些对象在自己的代码中运行;
  4. 可插拔性和调试的易用性:如果特定对象出现问题,只需要将其从程序中删除,然后插入其它对象作为替换对象即可。这类似于在现实世界中修复机械问题,如果螺栓断裂,则更换螺栓而不是更换整台机器。

在 Java 的编程世界中,一切皆对象。

1.2 类

类是创建对象的蓝图或原型。

在现实世界中,有许多同一种个体。比如成千上万辆自行车具有相同的品牌和型号。每辆自行车都是根据相同的设计图制造的,因此包含相同的组件。用面向对象的术语来说,你的自行车是称为自行车的一个类对象的实例。类是从中创建单个对象的蓝图。

自行车类的实现可能为:

class Bicycle {
    int cadence = 0;
    int speed = 0;
    int gear = 1;
    
    void changeCadence(int newValue) {
        cadence = newValue;
    }
    
    void changeGear(int newValue) {
        gear = newValue;
    }
    
    void speedUp(int increment) {
        speedUp = speed + increment;
    }
    
    void applyBrakes(int decrement) {
        speed = speed - decrement;
    }
    
    void printStates() {
        System.out.println("cadence:" + cadence + " speed:" + speed + " gear:" + gear);
    }
}

此类是基于上文对自行车对象的讨论。字段 cadence,speed,gear 表示该对象的状态,并且通过 changeCadence(int), changeGear(int), speedUp(int) 方法与外部进行交互(这些方法可以修改其状态)。

2. 面向对象三大特征

2.1 面向对象 – 封装

   封装是指把一个对象的状态信息(也就是属性或成员变量)隐藏在对象内部,不允许外部对象直接访问对象的内部信息。但是对象自己可以提供一些可以被外界访问的方法来操作属性。如果属性不想被外界访问,我们可以不提供方法给外界访问。但是如果一个类没有提供任何外界访问的方法,那么这个类也就没有任何的意义了。封装增加了代码的安全性和可维护性。

public class Person {
	// id 属性私有化
	private int id;
	// name 属性私有化
	private String name;
	
  // 设置私有化id的方法
	public void setId(int id) {
		this.id = id;
	}
	// 获取私有化id的方法
	public int getId() {
		return id;
	}
	
	public void setName(String name) {
		this.name = name;
	}
	
	public String getName() {
		return name;
	}
}

2.2 面向对象 - 继承

不同类的对象之间有一些共同点。例如,山地自行车、公路自行车和双人自行车都具有自行车的特征(当前速度、踏板节奏、当前档位)。然而,每个自行车还定义了一些特有的功能:双人自行车有两个座位和两个车把;公路自行车有下降的车把;一些山地自行车有一个附加的链环,使他们的齿轮比更低。

面向对象的编程允许类从其他类继承常用的状态和行为。在这个例子中,Bicycle 现在变成了MountainBike、RoadBike、TandemBike的父类。在 Java 编程语言中,每个类都可以有一个直接的超类,并且每个超类都有无限数量的子类的可能。增加代码的复用性。

class MountainBike extends Bicycle {
    //......
}

在这里插入图片描述

判断继承关系的简单规则:is-a

“is-a” 规则,例如,每个经理是一个员工,主管是一个员工;

“is-a” 规则的另一中表述法是置换法则。它表明程序中出现超类对象的任何地方都可以用子类对象置换,例如:

Employee e; // 雇员
e = new Employee(); // 普通雇员是一个雇员
e = new Manager();  // 主管雇员是一个雇员

注意:

  1. 子类拥有父类对象所有的属性和方法(包括私有属性和私有方法),但是子类无法访问父类中的私有属性和私有方法,只是拥有;
  2. 子类可以拥有自己的属性和方法,即子类可以对父类进行扩展;
  3. 子类可以用自己的方式实现父类的方法(重写)

2.2.2 重写

重写就是子类对父类方法的重新改造,子类返回值、方法名称和参数列表必须相同,访问修饰符不低于父类(public, protected, defalt),抛出异常范围必须小于等于父类抛出的异常,内部实现逻辑可以改变。

重写小结:

  1. 只有继承才会发生重写;
  2. 重写发生在运行期,是子类对父类方法进行重新编写和实现;
  3. 重写方法返回值、方法名、参数列表必须相同,否则就不构成重写;
  4. 抛出异常范围小于等于父类抛出异常的范围;
  5. 访问修饰符范围大于或等于父类访问修饰符范围;
  6. final 修饰的类不能被继承(因为 final 修饰的类是不可被修改的),所以其方法不能被重写;
  7. 父类方法访问修饰符为 private/final/staic 时,子类不能重写该方法,但 static 方法可以被在此声明(因为static方法属于类);
  8. 构造方法无法被重写;

2.3 面向对象 - 多态

多态在是继承的情况下对象的一种表现形式。

“is-a” 置换法则中,Employee 可以引用一个 Employee 对象,也可以引用一个 Employee 类的子类对象 Manager 等,这种员工的表现形式(普通员工,主管)称为多态。简单的说就是一个对象具有多种形态,具体表现就是父类的引用指向子类的实例。

Employee e; // 雇员
e = new Employee(); // 普通雇员是一个雇员
e = new Manager();  // 主管雇员是一个雇员

小结:

  • 多态条件:继承、重写、父类指向子类对象;
  • 对象类型和引用类型之间具有继承/实现关系;
  • 引用类型变量发出的方法调用到底是哪个类中的方法,必须在程序运行期间才能确定;
  • 多态不能调用只在子类存在但在父类不存在的方法;
  • 如果子类重写了父类的方法,真正执行的是子类覆盖的方法,如果子类没有覆盖父类的方法,执行的是父类的方法。

3. 面向对象设计的七大原则

3.1 开闭原则 - Open Close Principle(OCP)

3.1.1 定义

一个软件实体如类、模块和函数应该对扩展开放,对修改关闭。

3.1.2 基本概念

  • 开:对扩展开放,支持方便的扩展;
  • 闭:对修改关闭,严格限制对已有内容的修改;

说明:一个软件产品只要在声明周期内,都会发生变化,既然变化是不可避免的,我们就应该在设计时尽量适应这些变化,以提高项目的稳定性和灵活性,真正实现”拥抱变化“。开闭原则告诉我们尽量通过扩展软件实体的行为来实现变化,而不是通过修改现有代码来完成变化,它是为软件实体的未来事件而定制的对现有开发设计进行约束的一个原则。

注:任何对老接口的修改都会带来一定的风险。

3.1.3 优点

  • 提高项目稳定性(任何对老接口的直接修改都有一定的风险);
  • 提高可复用性;
  • 提高可维护性;

3.2 单一职责原则 - Single Responsibility Principle(SRP)

3.2.1 定义

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

3.2.2 基本概念

  • 单一职责是高内聚低耦合的一个体现;

说明:通俗的讲就是一个类只能负责一个职责,就像流水线上的工人一样,每个人负责一件事情,修改一个类不能影响到别的功能,也就是说只有一个导致该类被修改的原因。

3.2.3 优点

  • 高内聚,低耦合,代码修改时影响范围小;
  • 降低类的复杂度,职责分明,提高可读性;
  • 变更引起的风险低,利于维护;

3.3 里氏替换原则 - Liskov Substitution Principle(LSP)

3.3.1 定义

所有引用基类的地方必须能透明地使用其子类对象。

如果对每一个类型为 T1 的对象 o1,都有类型为 T2 的对象 o2,使得以 T1 定义的所有程序 P 在所有的对象 o1 都替换成 o2 时,程序 P 的行为没有发生变化,那么类型 T2 是类型 T1 的子类型。

3.3.2 基本概念

里氏替换原则强调的设计和实现要依赖于抽象而非具体;子类只能去扩展基类,而不是隐藏或者覆盖基类它包含以下 4 层含义:

  1. 子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法。
  2. 子类中可以增加自己特有的方法;
  3. 当子类的方法重载父类的方法时,方法的前置条件(即方法的形参)要比父类方法的输入参数更宽松;
  4. 当子类的方法实现父类的抽象方法时,方法的后置条件(即方法的返回值)要比父类更严格。

3.3.3 优点

  • 开闭原则的体现,约束继承泛滥;
  • 提高系统的健壮性、扩展性和兼容性;

3.4 依赖倒置原则 - Dependence Inversion Principle(DIP)

3.4.1 定义

高层模块应该依赖底层模块,二者都应该依赖其抽象;抽象不应该依赖细节,细节应该依赖抽象。

3.4.2 基本概念

  • 依赖倒置原则的核心就是要我们面向接口编程,理解了面向接口编程,也就理解了依赖倒置;
  • 低层模块尽量都要有抽象类或接口,或者两者都有;
  • 变量的声明类型尽量是抽象类或接口;
  • 使用继承时遵循里氏替换原则;
  • 设计和实现要依赖于抽象而非具体实现。一方面抽象化更符合人的思维习惯;另一方面,根据里氏替换原则,可以很容易将原来的抽象替换为扩展后的具体实现,这样可以很好的支持开闭原则;

3.4.3 优点

  • 减少类之间的耦合性,提高系统的稳定性;
  • 降低并行开发引发的风险;
  • 提高代码的可读性和可维护性;

3.5 接口隔离原则 - Interface Segration Principle(ISP)

3.5.1 定义

  • 客户端不应该依赖它不需要的接口。
  • 类之间的依赖关系应该建立在最小的接口上。

3.5.2 基本概念

  • 一个类对另一个类的依赖应该建立在最小的接口上,通俗的讲就是需要什么就提供什么,不需要的就不提供;
  • 接口中的方法应该尽量少,不要使接口过于臃肿,不要有很多不相关的逻辑方法;

3.5.3 优点

  • 高内聚,低耦合;
  • 可读性高,易于维护

3.6 迪米特法则/最少知道原则 - Law of Demeter or Least Knowledge Principle(LoD or LKP)

3.6.1 定义

  • 一个对象应该对其它对象保持最少的了解;
  • 这个原理的名称来源于希腊神话中的农业女神,孤独的德墨忒尔;

3.6.2 基本概念

  • 每个单元对于其它单元只能拥有有限的知识:只是与当前单元紧密联系的单元;
  • 每个单元只能和它的朋友交谈:不能和陌生单元交谈;
  • 只和自己直接的朋友交谈;

3.6.3 优点

  • 使得软件更好的可维护与适应性;
  • 对象较少依赖其它对象的内部结构,可以改变对象容器而不用改变它的调用者;

3.7 合成/聚合复用原则 - Composite/Aggregate Reuse Principle(CARP/CRP)

3.7.1 定义

尽量采用组合、聚合的方式而不是继承的关系来达到软件的复用目的。

3.7.2 基本概念

如果新对象的某些功能在别的已经创建好的对象里面已经实现,那么应当尽量使用别的对象提供的功能,使之成为对象的一部分,而不要再从新创建。

组合/聚合的优点:类之间的耦合比较低,一个类的变化对其它类造成的影响比较少;

组合/聚合的缺点:类的数量增多实现起来比较麻烦;

继承的优点:由于很多方法父类已经实现,子类的实现会相对比较简单;

继承的缺点:将父类暴露给了子类,一定程度上破坏了封装性,父类的改变对子类影响比较大;

3.7.3 优点

  • 可以降低类与类之间的耦合程度;
  • 提高了系统的灵活性;

4. 面向接口编程

4.1 什么是接口

接口是类与外界之间的契约。当一个列类实现了一个接口时,它承若提供该接口发布的所有行为。

对象通过其公开的方法定义了它们与外界的交互。方法形成对象与外界的接口;例如,电视机正面的按钮是你与塑料外壳另一侧的电线之间的接口。按下店员按钮可以打开或关闭电视。

在常见的形式中,接口是一组具有空主体的相关方法。如果将自行车的行为指定为接口,则可能如下所示:

interface Bicycle {
	void changeCadence(int newValue);
	void changeGear(int newValue);
	void speedUp(int increment);
	void applyBrakes(int decrement);
}

要实现此接口,你的类名称需要加上 implements 关键字,例如 ACMEBicycle:

class ACMEBicycle implements Bicycle {
	int cadence = 0;
	int speed = 0;
	int gear = 1;
	
	void changeCadence(int newValue) {
		cadence = newValue;
	}
	
	void speedUp(int increment) {
		speed = speed + increment;
	}
	
	void applyBrakes(int decrement) {
		speed = speed - decrement;
	}
	
	void printStates() {
		System.out.println("cadence:" + cadence + " speed:" + speed + " gear:" + gear);
  }
}

实现接口可以使类对其承若提供的行为变得更加正式。接口在类和外部世界之间形成契约,并且该契约在编译时由编译器强制执行。如果你的类声明要实现一个接口,则在成功编译该类之前,该接口的所有方法必须出现在其源代码中。

4.2 面向接口编程和面向对象编程是什么关系

面向接口编程并不是独立于面向对象编程的更先进的编程思想,而是隶属于面向对象编程思想体系,换句话说,面向接口编程是面向对象编程体系中的思想精髓之一。

4.3 接口的本质

站在面向接口编程的角度来看。”接口“可以说是一种从软件架构的角度、从一个高度抽象的层面上指的是用于隐藏具体底层类和实现多态性的结构组件。

站在面向对象编程语言角度来看。表面上接口由几个没有主体实现代码(Java8 default 可有主体)的方法定义组成的集合体,可以被类或其它接口所实现或继承。接口定义可能如下:

interface Bicycle {
	void changeCadence(int newValue);
	void changeGear(int newValue);
	void speedUp(int increment);
	void applyBrakes(int decrement);
}
  1. 接口是一组规则的集合,它规定了实现此接口的类或接口必须拥有的一组规则。体现了自然界“如果你是… … 则必须能… …”的理念

例如,在自然界中,人都能吃饭,及“如果你是人,则必须能吃饭”。那么模拟到计算机程序中,就应该有一个 IPerson接口,并有一个方法叫 eat(),然后我们规定,每一个表示“人”的类,必须实现 IPerson接口,这就模拟了自然界“如果你是人,则必须能吃饭”这条规则。

从这里,我想各位也能看到些许面向对象思想的东西。面向对象的思想的核心之一,就是模拟真是世界,把真是世界中的事物抽象成类,整个程序靠各个类的实例互相通信、互相协作完成系统功能,这非常符合真实世界的运行状况,也就是面向对象思想的精髓。

  1. 接口是在一定粒度视图上同类事物的抽象表示。注意这里强调了在一定粒度上,因为“同类事物”这个概念是相对的,它因为粒度视图不同而不同。

例如,在我的眼里,我是一个人,和一头猪有本质区别,我可以接受我和我同学是同类这个说法,但决不能接受我和一头猪是同类。但是,如果在一个动物学家眼里,我和猪应该是同类,因为我们都是动物,他可以认为“人”和“猪”都是动物,而他在研究动物行为时,不会把人和猪分开对待,而是从“动物”这个较大的粒度上研究,但他会认为人和树有本质区别。

现在换了一个遗传学家,情况又不同了,因为生物都能遗传,所以在他眼中,人不仅和猪没区别,和蚊子、细菌、树都没有区别,因为他会认为这些都是可遗传的,他不会分别研究这些对象,而会将所有生物作为同类进行研究,在他眼中没有人和其它生物之分,只有可遗传和不可以穿的物质。但至少,动物和石头还是有区别的。

可不幸的事情发生了,某日,地球上出现了以为伟大的人,他叫列宁,他在熟读马克思、恩格斯的辩证唯物主义思想巨著后,颇有心得,于是他下了一个著名的定义:所谓物质,就是能被意识所反映的客观事物。至此,任何一块石头、空气、磁场等已经没有区别了,因为在列宁的眼中,我们都是可被意识所反映的客观事物。如果列宁是一名程序员,他会这样说:所谓物质,就是所有同事实现了 “IReflectabe”和“IEsse”两个接口所生成的实例。

5. 参考:

面向对象设计的七大原则 - 简书 (jianshu.com)

面向接口编程详解(一)——思想基础 - T2噬菌体 - 博客园 (cnblogs.com)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值