第一章 什么是面向对象
第一章 什么是面向对象
面向过程与面向对象
我们在现实生活中,思考问题、发现问题、处理问题,往往都会用“表格”作为工具。实际上,“表格思维”就是一种典型的面向对象思维。
实际上,互联网上所有的数据本质上都是“表格”。我们在这里通过从表格表示数据开始,引入对象和类。大家就会神奇的发现,原来“表格就是对象”。
以公司雇员表为例:
上面这个雇员表,可以将公司所有员工信息“结构化”、“标准化”,让管理者可以方便的进行统计和管理。
我们也经常将表中的“列”,叫做“字段”,英文中统一叫做“field”。显然,field定义了表的结构,我们可以通过增加新的field(列),让表中所有的行增加数据
面向对象编程中,类对应的结构(表的field),我们可以定义出“雇员类”
通过上图,可以看出,雇员类和雇员表的结构完全一样,只不过,雇员类增加了数据的类型而已。
表格的动作和类的方法
每个公司的雇员都要有相关的动作。比如:所有雇员每天的工作标准动作有:
1、参加晨会,领取当天任务
2、午休
3、提交工作日志
我们可以在雇员表中将动作信息也包含进去:
新增的列“雇员动作说明”,显然是对所有的雇员都有用,每个雇员都有这个动作。在类中就是定义成方法:
当然,我们也可以根据需求,为雇员定义多个动作。比如:午休、提交工作日志、领工资等等。
对象对应的“表中的行数据”
表中一行一行的数据,都在表结构的约束范围内,大家的结构都是相同的。如下表:
每一行的数据都有“姓名”、“基本工资”等“列”,也都有标准的“晨会动作”。面向对象编程中,下面三句话很重要:
1、表结构对应:类结构
2、一行数据对应:一个对象
3、表中所有数据对应:这个类的所有对象
面向过程和面向对象的区别
面向过程和面向对象都是对软件分析、设计和开发的一种思想,它指导着人们以不同的方式去分析、设计和开发软件。C语言是一种典型的面向过程语言,Java是一种典型的面向对象的语言。
面向过程适合简单、不需要协作的事务,重点关注如何执行。面向过程中,我们首先思考“怎么按步骤实现?”并将步骤对应成方法,一步一步,最终完成。这个适合简单任务,不需要过多协作的情况下。比如,如何开车?就很荣放已列出实现步骤:点火,启动——>挂挡——>踩油门——>走你。比如,把大象放进冰箱需要几步:打开冰箱门——>把大象放进去——>关上冰箱门
但是当我们思考比较复杂的设计任务时,比如“如何制造车”,就发现列出1234这样的步骤是不可能的。因为造车太复杂。 需要很多协作才能完成。此时面向对象思想就应运而生。
面向对象思想更契合人的思维模式。首先我们思考的是“怎么设计这个事物?”比如何如造车,我们就会先思考“车怎么设计”,而不是“怎么按步骤造车的问题”。这就是思维的转变。
因此,面向对象可以帮助我们从宏观上把握、从整体上分析整个系统。但是,具体到实现部分的微观操作(就是一个个方法),仍然需要面向过程的思路去处理。
我们千万不要把面向过程和面向对象对立起来,他们是相辅相成的。面向对象离不开面向过程。
面向对象和面向过程思想的总结
1、都是解决问题的思维方式,都是代码组织的方式。
2、面向过程是一种“执行者思维”,解决简单问题可以使用面向过程。
3、面向对象是一种“设计者思维”,解决复杂、需要协作的问题可以使用面向对象
4、面向对象离不开面向过程:
宏观上:通过面向对象进行整体设计
微观上:执行和处理数据,仍然是面向过程
面向对象的三大基本特征
【封装】、【继承】、【多态】
1.封装
封装,即隐藏对象的属性和实现细节,仅对外公开接口,控制在程序中属性的读和修改的访问级别
封装原则:
• 将不需要对外提供的内容都隐藏起来
• 把属性都隐藏,提供公共方法对其访问
封装的好处:
1.提高重用性
2.不必关心具体的实现
3.具有安全性。
private 中文谐音是:普莱维特
在Java中通过private来进行封装
private是一个修饰符,可以用来修饰成员(成员变量,成员方法)
被private修饰的成员,只能在本类进行访问,针对private修饰的成员变量,如果需要被其他类使用,提供相应的操作
提供“get变量名()”方法,用于获取成员变量的值,方法用public修饰
提供“set变量名(参数)”方法,用于设置成员变量的值,方法用public修饰
实例: /* 学生类 */ class Student { //成员变量 String name;
//私有变量 private int age; //提供get/set方法 public void setAge(int a) { if(a<0 || a>120) { System.out.println("你给的年龄有误"); } else { age = a; } } public int getAge() { return age; } //成员方法 public void show() { System.out.println(name + "," + age); } } /* 学生测试类 */ public class StudentDemo { public static void main(String[] args) { //创建对象 Student s = new Student(); //给成员变量赋值 s.name = "林青霞"; s.setAge(30); //调用show方法 s.show(); } }
经private修饰的成员变量,只能通过提供对应的set , get方法对其进行访问,提高了其安全性。
this
this修饰的变量用于指代成员变量,其主要作用是(区分局部变量和成员变量的重名问题)
-
方法的形参如果与成员变量同名,不带this修饰的变量指的是形参,而不是成员变量
-
方法的形参没有与成员变量同名,不带this修饰的变量指的是成员变量
实例: public class Student { private String name; private int age; public void setName(String name) { this.name = name; } public String getName() { return name; } public void setAge(int age) { this.age = age; } public int getAge() { return age; } public void show() { System.out.println(name + "," + age); } }
2.继承
继承是面向对象最显著的一个特性。 继承是从已有的类中派生出新的类, 新的类能吸收已有类的数据属性和行为,并能扩展新的能力。
在JAVA中, 被继承的类叫父类(parent class)或超类(superclass), 继承父类的类叫子类(subclass)或派生类(derivedclass)。 因此, 子类是父类的一个专门用途的版本, 它继承了父类中定义的所有实例变量和方法, 并且增加了独特的元素 。
2.1 extends
在Java中通过extends来进行继承
继承特性
- java中一个类最多只能有一个直接的父类,即单继承
- java中要实现多继承,通过接口来实现
- 父类中所有属性和方法都能继承给子类;父类中的私有方法不能继承给子类。
- 共性放到父类,特性放到子类
继承好处:
- 提高了代码的复用性(多个类相同的成员可以放到同一个类中)
- 提高了代码的维护性(如果方法的代码需要修改,修改一处即可)
继承弊端:
- 继承让类与类之间产生了关系,类的耦合性增强了,当父类发生变化时子类实现也不得不跟着变化,削弱了子类的独立性
实例:
public class Fu { public void show() { System.out.println("show方法被调用"); } } public class Zi extends Fu { public void method() { System.out.println("method方法被调用"); } } public class Demo { public static void main(String[] args) { //创建对象,调用方法 Fu f = new Fu(); f.show(); Zi z = new Zi(); z.method(); z.show(); } }
在子类方法中访问一个变量,采用的是就近原则。
-
子类局部范围找
-
子类成员范围找
-
父类成员范围找
-
如果都没有就报错
实例:
class Fu { int num = 10; } class Zi { int num = 20; public void show(){ int num = 30; System.out.println(num); } } public class Demo1 { public static void main(String[] args) { Zi z = new Zi(); z.show(); // 输出show方法中的局部变量30 } }
2.2 super
- this&super关键字:
- this:代表本类对象的引用
- super:代表父类存储空间的标识(可以理解为父类对象引用)
- this和super的使用分别
- 成员变量:
- this.成员变量 - 访问本类成员变量
- super.成员变量 - 访问父类成员变量
- 成员方法:
- this.成员方法 - 访问本类成员方法
- super.成员方法 - 访问父类成员方法
构造方法:
- this(…) - 访问本类构造方法
- super(…) - 访问父类构造方法
- 子类会继承父类中的数据,可能还会使用父类的数据。所以,子类初始化之前,一定要先完成父类数据的初始化,原因在于,每一个子类构造方法的第一条语句默认都是:super()
2.3 方法重写
1.方法重写概念
子类出现了和父类中一模一样的方法声明(方法名一样,参数列表也必须一样)
2.方法重写的应用场景
当子类需要父类的功能,而功能主体子类有自己特有内容时,可以重写父类中的方法,这样,即沿袭了父类的功能,又定义了子类特有的内容
3.Override注解
用来检测当前的方法,是否是重写的方法,起到【校验】的作用
4.方法重写注意事项
私有方法不能被重写(父类私有成员子类是不能继承的),子类方法访问权限不能更低(public > 默认 > 私有)
实例:
public class Fu { private void show() { System.out.println("Fu中show()方法被调用"); } void method() { System.out.println("Fu中method()方法被调用"); } } public class Zi extends Fu { /* 编译【出错】,子类不能重写父类私有的方法*/ @Override private void show() { System.out.println("Zi中show()方法被调用"); } /* 编译【出错】,子类重写父类方法的时候,访问权限需要大于等于父类 */ @Override private void method() { System.out.println("Zi中method()方法被调用"); } /* 编译【通过】,子类重写父类方法的时候,访问权限需要大于等于父类 */ @Override public void method() { System.out.println("Zi中method()方法被调用"); } }
2.4 final
final 关键字:
“final”关键字用来修饰类、 方法和变量, 其含义“不可改变的、 最终的”。
final修饰的类不能被继承,即不能有子类,final修饰的方法不可被重写,final修饰的变量值不可被更改,即为常量。
3.多态
在面向对象语言中, 多态性是指一个方法可以有多种实现版本,即“一种定义, 多种实现”。 利用多态可以设计和实现可扩展的系统, 只要新类也在继承层次中。 新的类对程序的通用部分只需进行很少的修改, 或不做修改。 类的多态性表现为方法的多态性,方法的多态性主要有方法的重载和方法的覆盖。
3.1概述
什么是多态?
同一个对象,在不同时刻表现出不同形态
多态的前提
- 要有继承或实现关系
- 要有方法的重写
- 要有父类引用指向子类对象
多态的利弊
- 好处
提高程序的扩展性。定义方法时候,使用父类型作为参数,在使用的时候,使用具体的子类型参与操作
- 弊端
不能使用子类的特有成员
成员访问特点
- 成员变量
编译看父类,运行看父类
- 成员方法
编译看父类,运行看子类
3.2 运行时多态
子类的对象放在父类的引用中,子类对象当父类对象来使用。
Animal a=new Cat( );
实例:
public class Animal { //父类 public int age = 40; public void eat() { System.out.println("动物吃东西"); } } public class Cat extends Animal { //继承Animal类 public int age = 20; public int weight = 10; @Override //重写父类方法 public void eat() { System.out.println("猫吃鱼"); } public void playGame() { System.out.println("猫捉迷藏"); } } public class AnimalDemo { public static void main(String[] args) { Animal a = new Cat(); //有父类引用指向子类对象 System.out.println(a.age); // System.out.println(a.weight); a.eat(); // a.playGame(); } }
面向对象的五大基本原则
了解面向对象的思想时,之前仅仅了解了面向对象的三大特性,最近才发现原来面向对象在编程时还有五道金科玉律,这五大基本原则不必一定要遵守,但如《OOD启示录》中所说的:“你并不必严格遵守这些原则,违背它们也不会被处以宗教刑罚。但你应当把这些原则看做警铃,若违背了其中的一条,那么警铃就会响起。”
原则一:单一职责原则
简单来说,我们所创建的每一个类的职责越单一,越专注最好,最好只做一件事。是面向对象的低耦合和高内聚的延伸
原则二:开放封闭原则
开发好的软件是可以扩展的,但不可修改。核心思想就是对抽象编程,不对具体编程。让类依赖固定的抽象,因此修改就是封闭的。想要扩展时,我们可以依赖面向对象的继承和多态形式,通过子类继承并覆写父类方法的形式来扩展新的功能,因此又是对扩展开放的。需求时刻在变,因此需要开放封闭原则来维持软件的稳定性,同时又能满足需求的变化。
原则三:LisKov替换原则
软件中的子类必须可以替换其父类,反之不成立。这里的替换指的是将软件中的父类替换为子类,程序的行为不会有任何的变化,反之不成立。LisKov原则着眼于抽象和多态是建立在继承的基础上的。实现方式就是面向接口编程:将公共部分抽象基类接口或者抽象类,在子类中通过复写父类方法支持同样的职责。LisKov替换原则是继承机制的设计原则,违反了Liskov替换原则则必然违反了开放封闭原则。LisKov替换原则使系统能够保持良好的扩展性,同时实现了基于多态的继承机制,减少了程序中的类型判别,减少了代码冗余。
原则四:依赖倒置原则
依赖于抽象。意思就是高层模块不依赖低层模块。在面向对象的编程中,类与类之间不可避免的会有依赖关系,但这种依赖关系会导致程序高耦合,如何解决这种依赖耦合的关系呢?很简单,将顶层需要的功能和底层提供的功能抽象成接口,顶层只需调用这些抽象接口,底层只需实现这些抽象接口,这样的设计模式下,顶层和底层的直接依赖就消失了。而且抽象接口的稳定性维护了程序的稳定性。但有些时候,依赖细节是不可避免的,因此在编程时要具体情况具体分析,做好取舍。
原则五:接口隔离原则
使用多个小而专的接口,不用大而广的接口。首先,非抽象类继承接口时,必须要实现接口中的所有方法,如果接口很胖的话,那每一个实现类可能都要实现很多本不该需要自己实现的功能,压根用不到的功能,这会使代码大大冗余。因此,将胖接口分离成一个个小的定制化方法是很有必要的。分离方法主要有两种:1 委托分离:通过增加一个新的类型来委托客户请求,隔离客户和接口的直接依赖,但是会增加系统开销。2 多重继承分离:将接口分离成一个个定制化接口后,可以使用接口的多继承实现自己想要的功能。