前言
本次总结一下软件构造第四章到第八章的内容。
一、第四章 数据类型与类型检验
4.1 先来看看本章的主要内容
前面一小部分讲解了数据类型的基本知识:
1、数据类型定义了一组值及其可执行的操作。
基本数据类型包括 int、long、boolean、double、char 等。
对象数据类型包括 String、BigInteger 等。
2、静态类型检查与动态类型检查:
静态类型检查在编译时进行,确保变量类型与操作匹配,从而避免运行时错误。
动态类型检查在运行时进行,确保操作合法。
4.2 而重点应该是接下来的可变与不可变数据类型
4.2.1 可变数据类型(Mutable Data Types)
可变数据类型是指其值可以在创建之后被修改的数据类型。这意味着对象在内存中的状态可以被改变。
可变数据类型对象的状态(内容)可以在其生命周期内被改变。而且对可变对象的操作可能更高效,特别是在频繁修改的场景下。但多个引用指向同一对象时,修改对象的状态可能引发不可预期的副作用,导致程序难以调试和维护。
4.2.2 不可变数据类型(Immutable Data Types)
不可变数据类型是指其值在创建之后不能被修改的数据类型。这意味着对象一旦创建,其内容不能被改变。因为不可变数据类型对象的状态不能改变,因此可以安全地在多线程环境下共享。并且会使代码更易于理解,因为对象一旦创建,其状态是确定的。但每次修改都会创建一个新的对象,可能导致性能开销和内存占用增大。
4.2.3 final
关键字在Java中的作用
-
final
变量:
定义一个常量,值一旦初始化后便不能改变。final int MAX_SIZE = 100;
-
final
参数:
方法参数加上final
关键字,表示在方法内部不能改变该参数的值。public void print(final String message) { // message = "New Message"; // 编译错误,不能修改final参数 System.out.println(message); }
-
final
方法:
方法加上final
关键字,表示该方法不能被子类重写。public final void display() { System.out.println("This is a final method."); }
-
final
类:
类加上final
关键字,表示该类不能被继承。public final class Constants { // 常量类 }
特别注意的是当一个不可变的基本数据类型加上final修饰后,就不能修改其对应的值了。
4.3 最后一部分是Snapshot(快照)图
二、第五章 设计规约
5.1 先给出设计规约的定义
设计规约(Design Specification)是描述方法或函数的行为、输入输出条件以及异常处理的文档或注释。良好的设计规约有助于提高代码的可维护性和可靠性。它通常包含以下几个部分:
- 方法签名:方法的名称、参数和返回类型。
- 前置条件(Preconditions):方法调用前必须满足的条件。
- 后置条件(Postconditions):方法调用后必须满足的条件。
- 异常(Exceptions):方法可能抛出的异常及其触发条件。
对于一个好的方法,规约是必不可少的,有了规约才知道要怎么设计该方法,别人才知道该怎么使用该方法。一下的一张图就很好的展示了spec的重要性。
5.2 接下来再来看看规约的设计
越强的规约,意味着implementor的自由度和责任越重,而client的责任越轻!
三、第六章 抽象数据类型 (ADT)
本章主要学习将数据和操作复合起来,构成ADT,学习ADT的核心特征,以及如何设计“好的”ADT。
6.1 先来看看ADT的分类
可变类型的对象:提供了可改变其内部数据的值的操作;
不变数据类型: 其操作不改变内部值,而是构造新的对象。
ADT内部主要包含如下:
设计好的ADT,靠“经验法则”,提供一组操作,设计其行为规约 spec
- 设计简洁、一致的操作
- 要足以支持client对数据所做的所有操作需要,且用操作满足client需要的难度要低
- 要么抽象、要么具体,不要混合 — 要么针对抽象设计,要么针对具体应用的设计
在抽象数据类型(ADT)的实现中,抽象函数(AF)、表示不变性(RI)和表示泄露(Representation Exposure)是非常重要的概念。这些概念有助于确保ADT的正确性和安全性。
6.2 再就是ADT中必不可缺的部分了
抽象函数(AF)
抽象函数(AF)描述了具体表示(representation, R)和抽象值(abstract value, A)之间的映射关系。它定义了如何将具体的内部数据结构解释为更高层次的抽象概念。
表示不变性(RI)
表示不变性(RI)定义了具体表示(representation, R)中的所有字段何为有效。这些不变性约束确保对象的状态始终保持一致和有效。
表示泄露(Representation Exposure)
表示泄露(Representation Exposure)是指ADT的内部表示细节被暴露给了外部用户,从而可能导致意外的修改和不一致。避免表示泄露可以提高ADT的安全性和可靠性。
写代码时注意: 在代码中用注释形式精确地记录AF和RI;要精确的记录RI:rep中的所有fields何为有效;要精确记录抽象函数AF:R和A之间映射关系的函数,即如何解释每一个R值,其是满射但未必是单射;表示泄漏的安全声明,给出理由,证明代码并未对外泄露其内部表示;
同时注意:保持不变性和避免表示泄漏,是ADT最
重要的一个Invariant!
四、第七章 面向对象的编程
经历了之前学习的ADT理论,本章就要开始学习ADT的具体实现技术:OOP。
面向对象编程(OOP, Object-Oriented Programming)是一种编程范式,它将程序组织成由数据和行为组成的对象。以下是OOP的核心概念和要素:
1. 对象(Object)
对象是类的实例,表示具体存在的实体。每个对象都有自己的属性和行为。对象是OOP的基本单位。
2. 类(Class)
类是对象的蓝图或模板,定义了对象的属性和行为。一个类可以创建多个对象。(这里提一下接口,接口:确定ADT规约;而类:实现ADT;当然也可以不需要接口直接使用类作为ADT(既有ADT定义也有ADT实现),但实际中更倾向于使用接口来定义变量)
public interface publicCar {
// 行为
void drive();
}
class Car implements publicCar {
// 属性
String color;
String model;
// 行为
@Override
void drive() {
System.out.println("Driving the car...");
}
class Car {
// 属性
String color;
String model;
// 行为
void drive() {
System.out.println("Driving the car...");
}
}
3. 封装(Encapsulation)
封装是将对象的属性和方法隐藏在类的内部,只通过公共方法访问。这有助于保护对象的内部状态,防止外部不正当的访问和修改。(使用接口定义就是一个很好的方法,还有private、protect等关键字的使用)
class Person {
private String name; // 私有属性
private int age;
// 公共方法
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
if(age > 0) { // 简单的验证
this.age = age;
}
}
}
4. 继承(Inheritance)
继承是类之间的一种关系,允许一个类(子类)继承另一个类(父类)的属性和行为。子类可以扩展和修改父类的行为。
override:重写的函数:完全同样的signature,而实际执行时调用哪个方法,运行时决定。(但final修饰的方法严格继承,无法被重写)
class Animal {
void eat() {
System.out.println("This animal is eating.");
}
}
class Dog extends Animal {
void bark() {
System.out.println("The dog is barking.");
}
}
5. 多态(Polymorphism)
多态允许不同类的对象通过相同的接口调用不同的实现方法。多态性分为编译时多态性(方法重载)和运行时多态性(方法重写)。
多态又分为如下三种:特设多态指的是同一个函数或操作符可以应用于不同的类型,并且每个类型有不同的实现。特设多态通过函数重载(function overloading)来实现。参数化多态指的是编写代码时不指定具体的类型,这样的代码可以与任何类型一起使用。在面向对象编程中,这通常称为泛型编程(generics)。子类型多态指的是同一个名字(如变量或函数)可以表示多个不同类的实例,这些类通过某个公共父类关联起来。也称为包含多态。
class Animal {
void makeSound() {
System.out.println("Some sound...");
}
}
class Dog extends Animal {
@Override
void makeSound() {
System.out.println("Bark");
}
}
class Cat extends Animal {
@Override
void makeSound() {
System.out.println("Meow");
}
}
public class TestPolymorphism {
public static void main(String[] args) {
Animal myDog = new Dog();
Animal myCat = new Cat();
myDog.makeSound(); // 输出:Bark
myCat.makeSound(); // 输出:Meow
}
}
6. 抽象(Abstraction)
抽象是将对象的复杂性隐藏起来,只保留必要的特性。这通常通过抽象类和接口实现。
如:某些操作是所有子类型都共有,但彼此有差别,可以在父类型中设计抽象方法,在各子类型中重写(如下一个例子)。如果所有子类型完全相同的操作,放在父类型中实现,子类型中无需重写。再比如有些子类型有而其他子类型无的操作,不要在父类型中定义和实现,而应在特定子类型中实现。
abstract class Animal {
abstract void makeSound(); // 抽象方法,没有实现
}
class Dog extends Animal {
@Override
void makeSound() {
System.out.println("Bark");
}
}
class Cat extends Animal {
@Override
void makeSound() {
System.out.println("Meow");
}
}
总结
面向对象编程通过类和对象将程序组织起来,利用封装、继承、多态和抽象等特性提高代码的可重用性、可维护性和可扩展性。
最后再看看override和overload的区别:
五、第八章 ADT和OOP中的“等价性”
先来看看本章的关键内容:
等价关系(Equivalence Relation)是一种定义在集合上的二元关系,具有自反性、对称性和传递性。
站在外部观察者角度:对两个对象调用任何相同的操作,都会得到相同的结果,则认为这两个对象是等价的。反之亦然!
当在自定义ADT时,需要我们重写Object的equals();
在对于等价性书写时,If two objects are equal according to the equals(Object) method, then calling the hashCode method on each of the two objects must produce the same integer result. 等价的对象必须有相同的hashCode;但实际上不相等的对象,也可以映射为同样的hashCode,只是性能会变差。
下面是对于可变数据类型行为等价性的描述:
总结体会
通过了四到八章的学习,主要的知识内容罗列如下:
内容很多,知识点很杂,许多细节还得慢慢斟酌体会。