第一章 什么是面向对象

第一章 什么是面向对象 面向过程与面向对象 我们在现实生活中,思考问题、发现问题、处理问题,往往都会用“表格”作为工具。实际上,“表格思维”就是一种典型的面向

第一章 什么是面向对象

面向过程与面向对象

我们在现实生活中,思考问题、发现问题、处理问题,往往都会用“表格”作为工具。实际上,“表格思维”就是一种典型的面向对象思维。

 实际上,互联网上所有的数据本质上都是“表格”。我们在这里通过从表格表示数据开始,引入对象和类。大家就会神奇的发现,原来“表格就是对象”。

以公司雇员表为例:

上面这个雇员表,可以将公司所有员工信息“结构化”、“标准化”,让管理者可以方便的进行统计和管理。

我们也经常将表中的“列”,叫做“字段”,英文中统一叫做“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来进行继承

继承特性

  1. java中一个类最多只能有一个直接的父类,即单继承
  2. java中要实现多继承,通过接口来实现
  3. 父类中所有属性和方法都能继承给子类;父类中的私有方法不能继承给子类。
  4. 共性放到父类,特性放到子类

继承好处:

  • 提高了代码的复用性(多个类相同的成员可以放到同一个类中)
  • 提高了代码的维护性(如果方法的代码需要修改,修改一处即可)

继承弊端:

  • 继承让类与类之间产生了关系,类的耦合性增强了,当父类发生变化时子类实现也不得不跟着变化,削弱了子类的独立性

实例:

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();
    }
}

在子类方法中访问一个变量,采用的是就近原则。

  1. 子类局部范围找

  2. 子类成员范围找

  3. 父类成员范围找

  4. 如果都没有就报错

实例:

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 多重继承分离:将接口分离成一个个定制化接口后,可以使用接口的多继承实现自己想要的功能。