提示

本文主要讲解面向对象编程的核心三大特性:封装、继承和多态。@ermo

# 面向对象

面向对象编程(Object-Oriented Programming)OOP 要和面向过程编程(Procedure-Oriented Programming)POP 比较才能加强记忆。

早期的高级编程语言都是面向过程设计的,比如 C 语言。面向过程的核心是以事件编程,将一个个步骤编写成函数,然后通过控制代码进行顺序执行。

面向对象的程序则是一种以对象为中心的设计思想。将一个个事物设计成相应的对象,每个对象内含有自己的属性和行为,多个对象组成一个功能,从而解决实际问题。大幅度增加代码的可维护性,降低耦合。

# 面向对象的三大特性

(1)封装

封装(Encapsulation),将类的属性私有化(private 修饰符),通过该类提供的方法来访问私有信息。使用者无需关心该类的内部实现细节。

就像用户想吃饭就去找厨师(类),而不需要关心食材、厨具以及厨艺(隐藏的属性)。

  • 隐藏内部实现细节,提高程序安全性
  • 降低耦合:降低程序的修改成本,便于对属性的统一管理与测试
  • 类内部结构可以自由修改
  • 对成员进行精确的控制:通过方法访问成员提高代码灵活度,不只是简单的赋值操作

下文厨师的这个类中,倘若有用户要吃饭,只需要调用 cook() 方法,用户并不需要知道 agesick 的具体细节。

public class Chef {
    private String name;
    private int age;
    private boolean sick;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void cook() {
        if (age >= 18 && age <= 65 && !sick) {
            System.out.println("cooking quick ...");
        } else {
            System.out.println("cooking hard ...");
        }
    }
}

(2)继承

无论是在生活中还是社会中,都会有很多分类。比如电商网站的商品分类,电视机就会分为很多品牌。再比如说手机,也会有很多类型的手机。自然中的动物也是这样,羊、狗、猫等等,每一类动物都会有很多细分的品类。

计算机中经常使用继承(Inheritance)来表示上述生活中的品类。使用继承可以将一类事务的相同属性和行为放到父类中,子类只需要关注一些独有的特点即可。

计算机编程中的父子关系与现实中不同,在编程中,通过 is-a 关系进行判断继承关系是否恰当。

比如:

  • 男人是人,正确
  • 正方形是图形,正确
  • 小狗是动物,正确
  • 狗粮是狗,错误

在 Java 中类只支持单继承,不支持多继承。Java 接口支持多重实现。

public interface Children {
}

public interface Human {
}

public class Person {
    public void eat() {

    }
}

public class Student extends Person implements Human, Children {
    @Override
    public void eat() {
        System.out.println("Student eat.");
    }
}

(3)多态

多态(Polymorphism)即字面意思,多种形态。一个行为(方法)具有多种不同的表现形式,就是多态。

多态的好处是:

  • 可扩展性,一个方法或接口可以让不同的类调用,如果要新增一种新的类型,无需修改原有代码
  • 降低耦合,当前行为无需知道类的具体调用类型,只知道该类有这个方法即可

实现多态的三个条件:继承、重写、向上转型。

看一个例子,比如说一所学校购买了一批篮球供学生在体育课上使用。

那使用代码实现应该是这样的。

public class Ball {
    public void play() {
        System.out.println("Students play ball.");
    }
}

public class Basketball extends Ball {

    @Override
    public void play() {
        System.out.println("Students play basketball.");
    }
}

public class Students {

    public void playBall(Basketball basketball) {
        System.out.println("Physical education class begin.");
        System.out.println("Student start run.");
        basketball.play();
        System.out.println("Physical education class end.");
    }

    public static void main(String[] args) {
        Students students = new Students();
        Basketball basketball = new Basketball();
        students.playBall(basketball);
    }
}

篮球属于球类的一种,所以我们这里使用了继承,覆写了球类的 play 方法,毕竟每种球类的游戏规则不同。

目前学生类中的 playBall 方法也只支持 Basketball 的参数类型。

一段时间后,学校有钱了,为了丰富学生的体育活动,又采购了乒乓球和足球供学生使用。如果用代码表示应该是这样的。

public class Football extends Ball {
    @Override
    public void play() {
        System.out.println("Students play football.");
    }
}

public class TableTennis extends Ball {
    @Override
    public void play() {
        System.out.println("Student play table tennis.");
    }
}

public class Students {

    public void playBall(Basketball basketball) {
        System.out.println("Physical education class begin.");
        System.out.println("Student start run.");
        basketball.play();
        System.out.println("Physical education class end.");
    }

    public void playBall(Football football) {
        System.out.println("Physical education class begin.");
        System.out.println("Student start run.");
        football.play();
        System.out.println("Physical education class end.");
    }

    public void playBall(TableTennis tableTennis) {
        System.out.println("Physical education class begin.");
        System.out.println("Student start run.");
        tableTennis.play();
        System.out.println("Physical education class end.");
    }

    public static void main(String[] args) {
        Students students = new Students();
        Basketball basketball = new Basketball();
        students.playBall(basketball);

        TableTennis tableTennis = new TableTennis();
        students.playBall(tableTennis);

        Football football = new Football();
        students.playBall(football);
    }
}

如上述代码,学生就拥有了三个 playBall 方法,对于方法名相同,参数列表不同,返回值相同的方法我们称之为 方法的重载

方法的重载(overload)和方法的覆写(override)的区别是,重载是多个方法,覆写只是屏蔽了父类的行为,子类进行重新定义。

上面的代码显然违背了 DRY(Don't Repeat Yourself)原则,要优化这段代码,就需要使用到 Java 中多态的第二个必要条件:向上转型。

public class Students {

    public void playBall(Ball ball) {
        System.out.println("Physical education class begin.");
        System.out.println("Student start run.");
        ball.play();
        System.out.println("Physical education class end.");
    }

    public static void main(String[] args) {
        Students students = new Students();
        Ball basketball = new Basketball();
        students.playBall(basketball);
    }
}

首先修改的是学生类的参数列表,对参数定义进行向上转型,playBall 方法并不需要关心入参的具体类型,只需要知道入参具有 play 的行为即可,一定程度上降低了代码的耦合度。

其次要修改的就是调用过程中的变量声明。将 Basketball basketball 修改为 Ball ball,赋值进行实例化的过程中再创建具体的子类实现 new Basketball()

从上面代码可以看出,playBall 方法在声明的过程中并不知道入参的真实类型,这就是多态的特点:方法只有在运行时才能知道入参的真实类型。

使用多态也是对当前方法入参进行了抽象,真实业务场景中使用多态是提高代码质量的一种关键手段。