面向对象三大特征:封装、继承、多态,super关键字

面向对象三大特征:封装、继承、多态,super关键字

【摘要】OO的核心思想:面向父类、抽象、接口编程;对扩展开放,对修改关闭;高内聚、低耦合。

前言

面向对象三大特征:封装、继承、多态;五大基本原则:单一职责原则(SRP)、开放封闭原则(OCP)、里氏替换原则(LSP)、依赖倒置原则(DIP)、接口隔离原则(ISP)。

封装[encapsulation]

封装简单的说就是该隐藏的隐藏,该公开的公开。优点如下:

  • 提高代码的安全性。
  • 提高代码的复用性。
  • “高内聚”:封装细节,便于修改内部代码,提高可维护性。
  • “低耦合”:简化外部调用,便于调用者使用,便于扩展和协作。

类的封装

  1. 属性尽可能地私有化。
    但是为了外面的调用者要存取属性,我们必须对属性提供公开的存取方法。也就是get和set方法。
    如:
    1
    2
    3
    4
    5
    6
    7
    public String getStuName(){
    return this.stuName;
    }

    public void setStuName(String stuName){
    this.stuName = stuName;
    }
  2. 对外的业务方法要公开。

涉及到访问修饰符的使用。

方法的封装

由于类的数据[属性]和功能[方法]是分开的,所以,我们可以有另一个策略来封装类的功能。

策略一:把类的属性、构造、存取方法以及业务方法全部封装。同一个类中,这种策略适合功能单一、业务简单的场景下。
如:要封装一个银行账户类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class Account {
//属性
private String no; // 帐号
private double balance; //余额
private String realName; //真实姓名
...
//构造方法
...

//getter/setter方法
...

//业务方法
public void deposit(double money) { ... }

public void withdraw(double money) { ... }

public void transfer
(Account target, double money) { ... }
}

策略二:把类的属性、构造、存取方法单独封装成一个实体类,它就是纯数据的载体,而针对这个数据的操作,也就是业务方法另外封装成一个类[业务类]。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
//实体类
public class Account {
//属性
private String no; // 帐号
private double balance; //余额
private String realName; //真实姓名
...
//构造方法
...
//getter/setter方法
...
}

//针对Account数据的业务类
public class AccountService {
//提供操作Account对象的业务方法
public void deposit
(Account a, double money) { ... }

public void withdraw
(Account a, double money) { ... }

public void transfer
(Account from, Account target, double money) { ... }
}

很显然,策略二的扩展性更好。

继承[inheritance]

继承是一种能够让子类快速[获取父类]代码复用的机制。
在Java中,类只支持单继承,优点是结构简单,易于管理。
在java中,继承还有如下特点:

A.单继承。
B.传递性。
C.所有类都直接或者间接继承于java.lang.Object类。

如果自定义的类型没有显示地指定父类,则自动继承java.lang.Object类。
可以使用extends关键字表达继承关系。
当A类继承B类时,则可以这么表达:

1
2
3
4
5
6
public class B{
//...
}
public class A extends B{
//...
}

此时,我们可以说:
类A是类B的子类/派生类。
类B是类A的父类/超类[super class]。

何时使用继承呢?
1.使用集成时,两个类之间要满足IS A的关系。
2.只有当他们之间满足IS A的关系时,才应该使用继承关系否则,不要轻易使用继承。
如:
Bird IS A Animals
Apple IS A Fruits
...

有了父子类后,累的属性该如何划分?
共性归父类、个性归子类。
如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class Animals {
//属性
...
}

public class Dog extends Animals {
//属性[个性]
}

public class Cat extends Animals {
//属性[个性]
}

//父类可以指向一个具体的子类对象
Animals d = new Dog();
Animals c = new Cat();


//编译时类型:
// 就是对象的申明时类型,它可以是父类类型或本身
如:
Animals d1 = new Dog();//ok
Dog d2 = new Dog();//ok
Object d3 = new Dog();//ok
//运行时类型
// 就是对象的真正类型,也就是通过getClass()获取的类型。

:对象的编译时类型可以变化,但是,它的运行时类型永远不会改变,从对象创建时就已确定。

有了父子类后,创建对象的步骤升级为:总是按如下三步递归地创建父类对象

1.申请堆空间[本类对象]
2.给属性赋初始值[本类对象]
3.调用构造方法[本类对象]

多态[polymorphism]

具有相同类型的对象,调用同一个方法时,表现出不同的行为。但是,这要有如下前提:

A.要有继承关系
B.要有方法的重写[被调用的方法]

所谓方法的重写[override],是指子类中的方法声明与父类申明保持一致。可以体现在:

A.子类方法的访问控制修饰符必须大于或等于父类的。
B.子类方法的返回类型的上限是父类方法的返回类型。
C.方法名必须一样
D.参数列表必须一样
E.子类方法抛出

如:

1
2
3
4
5
Animals a1 = new Dog();
Animals a2 = new Cat();

a1.spark();//调用的是Dog()的spark方法
a2.spark();//调用的是Cat()的spark方法
  1. 对象的编译时类型决定了对象所能“看得见”的行为。
  2. 对象的运行时类型决定了对象的真正行为。

为什么此时对象的编译时类型要写Animals呢?
因为要用“统一”的类型来处理这些对象,而使用这些对象的父类类型是
一种既可以满足多态的要求又可以达到用户的解决方案。

因为要遵守如下OO的思想:

面向父类编程
面向接口编程
面向抽象编程

从编程的角度来理解的话,可以分为

1.对象的编译时类型尽可能的写父类
2.方法的参数尽可能地写父类

如:

1
2
3
4
5
6
7
8
9
10
public void m(Dog d){
//...
d.spark();
//...
}
public void m(Cat c){
//...
d.spark();
//...
}

可改成:

1
2
3
4
5
public void m(Animals a){
//...
d.spark();
//...
}

super关键字

  1. 在构造器中,用来调用父类的构造器。若是构造方法的第一行没有显式的supper(...)或者this(...),那么Java默认都会调用super(),含义是调用父类的无参构造方法。
  2. 表示指向父类对象的指针[引用]。
    如:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    public class A {
    protected int index = 50;
    public void f(){
    System.out.println("A:"+index);
    }
    //...
    }
    public class B extends A {
    private int index = 100;
    //
    public void f(){
    super.f();//调用父类的普通方法
    System.out.println("B:"+index);
    //System.out.println("A:"+super.index);//调用父类的成员属性
    }

结束语

~

评论