软件构造学习笔记(3)

前言

本文主要介绍两部分内容,抽象数据类型ADT和面向对象编程OOP


ADT

一、抽象数据类型

1.ADT相关概念

抽象数据类型是一种不同于编程语言所提供的基本数据类型,用户自己定义的数据类型,例如类等。本质上ADT是由操作定义的,与其内部如何实现无关!

可以讲ADT分为可变的与不可变的数据类型:

可变类型的对象:提供了可改变其内部数据的值的操作,如StringBulider
不可变数据类型: 其操作不可改变内部值,而是构造新的对象 ,如String

对于ADT的操作,可以大致分为四类:

Creators:构造器(从无到有)  t* → T  可能实现为构造函数或静态函数

Producers:生产器(从有到新)   T+, t* → T  如 String.concat()

Observers:观察器 T+, t* → t   如 List.size()

Mutators:变值器,改变对象属性的方法 T+, t* → void | t | T  如果返回值为void,则必然意味着它改变了对象的某些内部状态

2.设计ADT

1.设计简洁、一致的操作

2.要足以支持client对数据所做的所有操作需要,且用操作满足client需要的难度要低 

3.要么抽象、要么具体,不要混合 ---要么针对抽象设计,要么针对具体应用的设计

二、表示独立性

1.表示独立性

我们称类中的具体属性为该抽象数据类型的表示,表示独立性的核心含义就是要达到使用效果与表示相独立,互不依赖。client使用ADT时无需考虑其内部如何实现,ADT内部表示的变化不应影响外部spec和客户端。

2.测试ADT

1.测试creators, producers, and mutators:调用observers来观察这些operations的结果是否满足spec; 

2. 测试observers:调用creators, producers, and mutators等方法产生或改变对象,来看结果是否正确。 

注意:测试时,对于自定义ADT需要重写equals和hashcode,后续会有讲解。

三、不变量

1.不变量

不变量就是程序在任何时候总是true的性质,由ADT 来负责其不变量,与client端的任何行为无关,可以保持程序的“正确性”,容易发现错误。

我们需要注意,防止表示泄漏,不仅影响不变量,也影响了表示独立性。当代价很高时可以在规约中做出相应规定,实际上最好的办法还是使用不可变数据类型。

2.表示不变量与抽象函数

我们可以将一个程序抽象成一个由实现者看到和使用的值R到使用者看到和使用的值A的一个映射:AF : R → A,其中R叫作表示空间,A叫作抽象空间。显然R->A一定是满射但未必单射、未必双射。

表示不变性RI:某个具体的“表示”是否是“合法的” 

也可将RI看作是所有表示值的一个子集,包含了所有合法的表示值 

也可将RI看作是一个条件,描述了什么是“合法”的表示值

总之,RI作用就是描述了R中合法的表示

同一个ADT,可以有多种表示;不同的内部表示,需要设计不同的AF和RI;同样的表示空间R,可以有不同的RI; 即使是同样的R、同样的RI,也可能有不同的AF,即“解释不同”。
 

所以我们在设计ADT时需要:

(1) 选择R和A;

(2) RI ---合法的表示值;

(3) 如何解释合法的表示值 ---映射AF;

(4) 做出具体的解释:每个rep value如何映射到abstract value,而且要把这种选择和解释明确写到代码当中,以注释的形式(注意,不能以规约的形式,因为规约是可以暴露给客户端的,如果把RI和AF暴露给客户端,这也是一种表示泄露);

(5)  在所有可能改变rep的方法内都要检查是否破坏了表示不变量,可以用checkRep函数的形式;

(6) 构造器和生产器在创建对象时要确保不变量为true ;变值器和观察器在执行时必须保持不变性。 

OOP

一、接口与枚举

1.接口

与类不同,接口只有方法的定义,没有实现;接口之间可以继承与扩展 ;一个类可以实现多个接口(从而具备了多个接口中的方法) ;一个接口可以有多种实现类。

接口+类也是一种常见的实现ADT的方式,接口:确定ADT规约;类:实现ADT。

自从Java8接口中允许有静态方法,可以用来实现构造器的效果。还可以通过default方法,在接口中统一实现某些功能,无需在各个类中重复实现它。

2.可见性

为了更好的实现表示独立性,更好的信息隐藏,可以使用不同的关键字来时的表示的可见性不同。如图,public所以都可访问,private只有当前类能访问

3.继承和重写

继承是java面向对象编程技术的一块基石,因为它允许创建分等级层次的类。

继承就是子类继承父类的特征和行为,使得子类对象(实例)具有父类的实例域和方法,或子类从父类继承方法,使得子类具有父类相同的行为。

可以分为严格继承和非严格继承

严格继承:子类只能添加新方法,无法重写超类中的方法,就是在软件构造学习笔记(2)中提到的final标记的类。

非严格继承:就是常见的继承,子类还可以按需要重写父类的方法

重写的要点:

1.重写要求函数签名保持一致,就是返回值、函数名和参数个数类型相同,实际执行时调用哪个方法,运行时决定(动态检查)

2.重写之后,利用super()复用了父类型中函数的功能,父类的构造方法必须放第一行

3.重写不能降低可见性

抽象类:抽象类是包含抽象方法的一种类,抽象方法就是只有定义没有实现的一种类,用关键字abstract来标识。

要点:

1.抽象类不能实例化(不能用new 生成对象)

2. 继承某个抽象类的子类在实例化时,所有父类中的 抽象方法必须已经实现

3.实际上接口就是所有方法都没有实现的抽象类

4.多态、子类型、重载

多态是一个宏观的概念,是同一个行为具有多个不同表现形式或形态的能力。包括三种形式的多态:1.方法重载2.泛型编程3.一个变量名字可以代表多个类的实例(子类型多态)

重载:多个方法具有同样的名字,但有不同的参数列表或返回值类型 

要点1:重载进行的是静态类型检查

要点2:重载必须有不同的参数列表

要点3:可以有相同/不同的返回值类型

要点4:可以有相同/不同的可见性public/private/protected

要点5:可以有相同/不同的异常

要点6: 可以在同一个类内重载,也可在子类中重载

要点7:子类重载了父类的方法后,子类仍然继承了被重载的方法

泛型: 泛型(generics)是 JDK 5 中引入的一个新特性, 泛型提供了编译时类型安全检测机制,该机制允许程序员在编译时检测到非法的类型。使用<>来声明泛型参数。

使用泛型变量的三种形式:泛型类、泛型接口和泛型方法

泛型类:类中如果声明了一个或多个泛型变量,则为泛型类

泛型接口:类似于泛型类,只不过有两种实现方式,泛型实现和非泛型实现,简单来讲就是实现接口使用的类是否是泛型类。

泛型方法:泛型方法在调用时可以接收不同类型的参数。根据传递给泛型方法的参数类型,编译器适当地处理每一个方法调用。

子类型多态:

子类型多态是继承的深入学习,要点如下:

1.一个类只有一个父类,但可以实现多个接口

2.子类型的规约不能弱化超类型的规约。

更多的内容要在后续LSP相关学习中讲解


总结

本文详细介绍了有关抽象数据类型和面向对象的程序设计两部分内容,这两部分是软件构造的基础,需要深入理解与掌握。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值