软件构造:3-4 Object-Oriented Programming (OOP)

3-4 面向对象的编程

标准的面向对象

  • 泛型:对于“准备好更改”和“为重用而设计”:应该可以使用表示任意类型的正式泛型参数来编写类。
  • 继承:控制潜在的复杂性。
  • 多态:应该可以在基于继承的类型系统的控制下,将实体(表示运行时对象的软件文本中的名称)附加到各种可能类型的运行时对象上。
  • 动态分派/绑定:在实体上调用某个特性应该总是触发与所附运行时对象的类型相对应的特性,这在调用的不同执行中不一定是相同的。

基本概念:对象,类,参数和方法

对象有着两个共同的特性:状态和行为。
static和instance。

接口和枚举

接口和类:定义实现ADT
接口之间可以继承
一个类可以实现多个接口

一个接口可以有多种实现

一般用接口来定义类型,原因是:

  • 支持实现的改变
  • 防止依赖实现细节
  • 调用者只需理解接口即可调用ADT
  • 不同实现在不同类中

使用工厂方法防止客户端了解构造器

封装和信息隐藏

好的设计模块将内部数据以及其他实现细节隐藏起来。

信息隐藏的好处:

  • 分离组成系统的类,使得各个类可以独立地开发、测试、利用、理解以及修改;
  • 加快系统的开发,类可以平行开发;
  • 减轻了维护的负担;
  • 有效的进行性能调优,使用较多的类可以独立地进行完善;
  • 提高系统的可复用性

使用接口进行信息隐藏:

  • 使用接口类型声明变量;
  • 客户端仅使用接口中定义的方法;
  • 客户端代码无法直接访问属性。

信息隐藏最好的方法:

  • 细致的设计API;
  • 对客户端只提供相应的方法,所有其他的成员均应当为private类型;
  • 将私有成员设置为public而不破坏客户端,但不能相反

继承和重写

  1. 继承
    严格继承:子类只能添加新方法,无法重写超类中的方法。(父类需要将该方法定义为final类型,注意:父类中final方法也会被继承但是不能被重写)
  2. 重写
    (1)重写
    子类重写父类的函数需要具有完全同样的签名。即返回值,函数名,参数完全相同。

实际执行时调用哪个方法是在运行时决定的。

父类声明的子类对象在调用子类重写的函数时还是相当于调用子类的该函数;
但是调用子类独有的函数时即调用子类独有的函数。

父类型中的被重写函数体不为空:意味着对其大多数子类型来说,该方法是可以被直接复用的。对某些子类型来说,有特殊性,故重写父类型中的函数,实现自己的特殊要求。

如果父类型中的某个函数实现体为空,意味着其所有子类型都需要这个功能,但各有差异,没有共性,在每个子类中均需要重写。

重写的时候,不要改变原方法的本意。

(2)抽象类
抽象方法是具有签名但是无具体实现的方法。需要在定义部分加入abstract。

抽象类是包含至少一个抽象方法的类。

而接口是只有抽象方法的类。

多态、子类型化和重载

(1)三种类型的多态

  • 特殊多态:一个函数可以有多个同名的实现(功能重载)
  • 参数多态:一个类型名字可以代表多个类型(泛型编程)
  • 子类型多态、包含多态:一个变量名可以代表多个类的实例(???不太懂)

(2)特殊多态和重载
当一个函数处理多个不同类型时(这些类型可能不具有公共结构比如有不同的参数),并且可能以不相关的方式对每个类型进行操作,就会获得特别的多态性。

(3) 重载
重载:多个方法具有同样的名字,但有不同的参数列表或返回值类型。
价值:方便client调用,client可用不同的参数列表,调用同样的函数。

重载是一种静态多态:根据参数列表进行最佳匹配,进行静态类型检查,在编译阶段时决定要具体执行哪个方法。与之相反的是重写,重写是在运行时觉定具体执行哪个方法,并且进行的是动态类型检查。

重载规则:

  • 必须有不同的参数列表
  • 可以有不同的返回值类型
  • 可以修改权限:public/private/protected
  • 可以声明新的类型的异常
  • 一个方法可以在同一个类中重载,也可以在子类中重载

合法重载:
区分子类父类的重写重载问题,测试如下代码:

public class Person {
    public void show(){
        System.out.println("I am a person");
    }
}

class Student extends Person{
    @Override
    public void show(){
        System.out.println("I am a student");
    }
    
    //overload
    public void show(int money){
        System.out.println("I am a student, i have "+money);
    }
}

class Test{
    public static void main(String[] args){
        Person person = new Person();
        person.show();
        
        Person student = new Student();
        student.show();
        
        student.show(123);///////////////////////
        
        ((Student) student).show(123);
        
        Student student1 = new Student();
        student1.show();
        
        student1.show(123);
    }
}

其中 student.show(123);部分无法通过静态检查。
注释掉这一句以后运行结果如下:
I am a person
I am a student
I am a student, i have 123
I am a student
I am a student, i have 123

继续测试如下代码:

public interface Animal {
    void vocalize();
}

class Dog implements Animal{
    @Override
    public void vocalize() {
        System.out.println("wang wang wang");
    }
}

class Cow implements Animal{
    @Override
    public void vocalize() {
        moo();
    }

    public void moo(){
        System.out.println("moo");
    }
}

class Test{
    public static void main(String[] args){
        Animal a = new Animal();
        a.vocalize();

        Dog d = new Dog();
        d.vocalize();

        Animal b = new Cow();
        b.vocalize();
        b.moo();;
    }
}

其中 Animal a = new Animal();和b.moo()无法通过静态类型检查,前者原因是接口无法实例化,后者原因是Animal没有moo方法必须先强转再使用对应方法。

重载VS重写

重载 重写
参数列表 必须改变 不能改变
返回类型 可以变 不能改变
异常 可以变 可以减少或者消除,但是不能增加异常类型
使用 可以变 不能提高使用限制
时间 编译时 运行时

(3) 参数多态和泛型编程

(4)子类型多态
子类的规约不能弱化超类型的规约,即至少强于超类型的规约;
B是A的子类型,意味着每个B都是一个A,即每个B都满足A的规约;

动态分派

动态分派指的是在运行过程中选择调用哪一个实现。
静态分派指的是编译阶段即可确定要执行哪个具体操作。

动态分派不同于推迟绑定;
绑定:将调用的名字与实际的方法名字联系起来(可能很多个);分派:具体执行哪个方法(early binding -> static dispatch)
动态分派:编译阶段可能绑定到多态操作,运行阶段决定具体执行哪个(override和overload均是如此);
推迟绑定:编译阶段不知道类型,一定是动态分派(override是推迟绑定,overload是early binding)

软件构造:3-4 Object-Oriented Programming (OOP)_第1张图片
软件构造:3-4 Object-Oriented Programming (OOP)_第2张图片

动态方法分派:

  • 编译时:确认是哪个类

  • 编译时:确认被执行的方法签名(获取所有可获得的方法,选择最特定的匹配方法???)

  • 运行时:确定接收的动态类

  • 运行时:从动态类中找到调用的方法(找与第二部中相同签名的方法,无法找到就找父类中的方法等)

    软件构造:3-4 Object-Oriented Programming (OOP)_第3张图片

设计好的类

不可变类的好处:

  • 简洁
  • 固有的线程安全
  • 可自由分享
  • 无需防御性拷贝
  • 好的构建块(?)

如何写不可变类:

  • 不提供任何变值器
  • 确认没有方法可以被重写
  • 定义所有域为private final
  • 为所有改变rep的操作确保安全性(避免表示泄露)
  • 实现 toString hashCode clone equals等方法

你可能感兴趣的:(软件构造)