2022软件构造我的学习笔记(2)

一、规约

含义:写在方法前面的说明性注释,用来解释方法的功能、参数、返回值等。

例如:

规约的好处
记录自己的设计决策,供自已或他人阅读。

代码中的精确规范让您可以将责任归咎于代码片段,并且可以让您免于为修复应该去哪里而困惑的痛苦。团队协作中,如果没有规约就无法分派任务,无法写程序;即使写出来,也无法验证对错。
规约作为一种客户端与程序之间的“契约”,可以明确双方的责任。
规约可以隔离变化,无需通知客户端,即不管具体实现如何修改,只要符合规约,客户端就没必要知道修改的具体内容。

规范对方法的客户有好处,因为它们省去了阅读代码的任务。
达到解耦的目标。
可以利用规约来判定方法的行为等价性。
测试用例可以根据规约来编写。

为什么需要规格?
▪ 现实:程序中许多最糟糕的错误都是由于对两段代码之间的接口行为的误解而产生的。
–虽然每个程序员都有自己的规范,但并不是所有的程序员都把它们写下来。因此,团队中的不同程序员有不同的规范。
–当程序失败时,很难确定错误在哪里。
 

规范(合同)
▪ 该契约充当客户端和实现者之间的防火墙。
–它保护客户不受该单元工作细节的影响。
–它保护实现者不受单元使用细节的影响。
–此防火墙令客户端和程序不耦合,只要更改符合规范,允许独立更改单元代码和客户端代码。

行为等价性
为了确定行为等价性,问题是我们是否可以用一个实现代替另一个实现

规范结构:前置条件和后置条件
规格结构
▪ 方法的规范由几个子句组成:
–Precondition,由关键字requires指示
–Postcondition,由关键字effects指示
–Exceptional behavior:如果违反了Precondition,它会做什么
▪ 先决条件是客户的义务(即方法的调用方)。它是调用方法的状态的条件。
▪ 后置条件是方法实现者的义务。
▪ 如果调用状态的前提条件成立,则该方法有义务通过返回适当的值、抛出指定的异常、修改或不修改对象等方式遵守后条件。

规格结构
▪ 整体结构是一个逻辑含义:如果在调用方法时前置条件保满足了,则在方法完成时后置条件必须满足。▪ 如果在调用方法时前置条件不成立,则实现不受后置条件的约束。–它可以做任何事情,包括不终止、抛出异常、返回任意结果、进行任意修改等。

按照强弱分类
规约A强度>B规约强度意味着下面两点:

A的前置条件更弱
A的后置条件更强
前置条件和后置条件的强弱由条件的严格程度决定,即如果条件越放松,那么这个条件越弱。反之亦然。

如果要增强一个规约的强度,那么就意味着更松的前置条件+更严格的后置条件。
举个例子

上图中第二个规约的前置条件比第一个规约的前置条件更加放松,因此规约变强。第三个规约的后置条件更加严格,所以强度更高。

但是要注意一点,后置条件的范围变大,并不意味着后置条件就变弱了,是否变弱需要看前置条件对应的那一部分后置条件是否变弱。如下图,规约二的前置条件变弱了,后置条件的范围变大,但是在满足规约一中的前置条件的情况下,此后置条件并没有变化,所以后置条件并没有变弱。因此整个规约二的强度变大了。

注意比较后置条件时要假定前置条件相同

 二、LSP(放在规约后面一起说了)

 LSP原则:

1.子类必须完全的实现父类的方法(不能删父类的方法;子类型需要实现抽象类型中的所有未实现的方法)
2.子类可以有自己的个性(子类型可以增加方法)
子类型中重写的方法必须使用同样类型的参数或者符合co-variance的参数(此种情况Java目前按照overload处理)
3.子类型中重写的方法不能抛出额外的异常(协变)
4.覆盖和实现父类的方法时输入参数可以被放大(更弱的前置条件/逆变)
5.覆盖和实现父类的方法时输出参数可以被缩小(更强的后置条件/协变)
6.更强/保持的不变量
记住一句话:LSP直观表现为——子类型传入参数不变或更抽象,返回值不变或更具体,抛出异常更少或不变或更具体

三、ADT抽象数据类型 

 

ADT操作的四种类型:
1.Creators 构造器:
从无到有

2.Producers生产器:
从有到新

3.Observers观察器
不改变,只观察,显然必须有返回值

4.Mutators变值器(改变对象属性的方法)
改变对象内部的信息和属性。
通常为void,如果为void,则必然意味着它改变了某些对象的内部状态,也有可能返回非空类型(如容器类的put、add方法)

可变数据类型有mutator,不可变没有

 

以上四类方法接受的参数类型和产生的返回值类型关系如下:

 表示独立性:client使用ADT时无需考虑其内部如何实 现,ADT内部表示的变化不应影响外部spec和客户端。

抽象类型的使用与其表示(用于实现它的实际数据结构或数据字段)无关,因此表示的更改对抽象类型本身之外的代码没有影响。

– 例如,List 提供的操作与列表是表示为链表还是表示为数组无关。

通过前提条件和后置条件充分刻画 了ADT的操作,spec规定了client和implementer之间的契约,明确 了client知道可以依赖哪些内容,implementer知道可以安全更改的内 容。

我们编写ADT追求的是表示独立性,避免表示泄露。

表示泄露:表示泄露形容了一种类外部的代码可以直接修改类内部存储的数据的现象。通常是类外代码直接对类内的可变类型对象进行直接修改,导致了之前所提到过的引用别名现象,这种引用方式会导致我们对同一块内存进行多次引用,导致程序运行过程中的不确定性,恶意代码也可以十分简单的对ADT进行攻击,严重影响ADT的表示独立性和不变量。
  为了防止表示泄露,我们通过可以采用以下几种方法:

使用private和final关键词对域进行修饰。
使用防御性拷贝,需要注意的是,防御性拷贝可以发生在传入和传出数据时。
通过规约对用户的行为进行限制。
使用不可变类型的数据构建ADT。
 

测试ADT的方法:

    (1)测试creators,producers和mutators:调用observers来观察这些operations的结

            果是否满足spec;

    (2)测试observers:调用creators,produces和mutators等方法产生或改变对象,来

            看结果是否正确。

        例如:测试构造的String类ADT:MyString

        有测试策略:

        

 

不变量:程序在任何时候总是true的性质,一个好的ADT的属性是需要始终保持其不变量。不变量是由ADT负责,与client端的行为无关。
不变量可以保持程序的“正确性”,易于发现错误。在设计时要假设client可能存在“恶意”毁坏ADT不变量行为。
可变类型一种危险是客户端可以直接访问它的域,例如:

 AF与RI:

 表示空间R指的是开发人员实际实现时内部的值,而抽象空间A表示的是用户看到的和使用的值。开发人员更关注R,而用户更关注A。

AF是一个满射,即用户所看到或使用的任意一个值都是由一个表示值映射而来的。
AF未必是一个单射,即用户户所看到或使用的任意一个值可能由不止一个表示值映射而来。可能有多种输入对应同一个A。
AF未必是一个双射,即开发人员所面对的表示值中,会存在不满足前置条件的表示值,对于这类表示值不存在对于的抽象值。R当中含有非法输入,不进行映射关系。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值