软件构造-面向可复用性的编程-复习笔记

 

这篇博客主要摘录我在复习软件构造的可复用部分的感悟以及对一些重点内容的摘录java

可复用主要包括了两个重点内容:程序员

  • 继承与面对对象编程
  • 设计模式

其他部分就是以这两个重点为核心的一些补充,这也是软件构造的的一个很是重要的思想部分编程

 

这一部份内容其实是对前面ADT,specification相关内容的继续,那些内容制定了编程过程当中的一些规范,这一章就是在规范的基础上研究软件的顶层设计。正由于有了specification的保护,使得咱们能够不用关心类的内部实现,而可以把注意力放在功能块之间的设计上来。设计模式

 

继承与OOP

接口,继承与泛型

 关于继承的约定

若是B是A的子类型,那么A与B之间应该有这样的关系:框架

  • 全部的B都是A

而这个关系在程序中的体现就是:ide

  • B的specification必定要强于A的specification

而这个约定又分为两种,一种是可以经过静态检查发现的。也就是A中全部的方法都必须在B中出现,并且方法的返回值,签名等等都必须相同。post

当咱们使用extends关键词来定义A的子类型的时候,静态检查会帮咱们保证这一性质。设计

可是还有一种是静态检查所发现不了的,也就是pre-condition和post-condition3d

继承保证了共性,可是子类型也必定有其个性。子类型能够经过添加本身的方法来为本身添加细化的性质,也能够重写父类的方法,所以就有可能改变父类的方法的precondition与postconditioncode

要想知足B的specificatioin强于A的specifition这一性质,就必须保证:

  • B的重写方法的precondition不能强于A中的原方法
  • B的重写方法的postcondition不能弱于A中的原方法

这是ide没法静态检查获得的,须要程序员去保证。

一道例题

MIT的readingd提供了一道例题:

这道例题能够这样思考

题目中父类是rectangle,而后尝试添加一个square的子类。

乍一想是很合理的,每个square固然是一个rectangle了,specification明明强化了——多了rectangle相邻边必须相等的保证。

可是square中对rectangle的setSize方法的重写,却没有一个可以知足条件的,为何?

由于题中给定是mutable的类型,应该这样想:

每个square必定是一个rectangle,可是可以任意设定两条边长的square并非可以任意设定两条边长的rectangle

 

另外一道例题

这题选B

我是这么理解了,

Double对Number是增强了postCondition,因此是符合的

事实上根据老师上课时ppt5-2中的内容,在继承关系中,子类中对父类方法的重写,变量能够逆变是没有错的,只是在java中并不支持这种写法。

 

所以,咱们在须要用到变量的逆变的时候,应该不使用@Override标记,而是直接改写。这样对编译器来讲是父类方法的重载,只是在概念上咱们会将之当成重写罢了。

根据Liskov替换原则,这样的改写是知足条件的,也就是若是把任何对父类的引用改为子类,也不会产生任何问题,子类的结果只会更严格。

 

关于Liskov原则的理解

LIskov对继承的规定有以下几点:

  • 前置条件不能强化
  • 后置条件不能弱化
  • 不变量必须保持
  • 子类型方法参数:逆变
  • 子类型方法返回值:协变
  • 异常类型:协变

其实我倾向于将子类型方法参数与子类型方法返回值的规定归于对前置条件与后置条件限制

也就是:协变是一种强化,缩小范围,更加具体。而逆变是一种弱化,扩大范围,更加抽象。

在此我有一个疑问:为何liskov原则不把子类型方法的参数与变量归于前置条件与后置条件呢?

泛型的继承规则

一个让初学者感到出人意料的事实:

ArrayLIst<String> 是 List<String> 的子类型

List<String> 不是 List<Object> 的子类型

java是彻底屏蔽了泛型中的继承检查,只要是不一致的类型,都会被简单的当成不一样类型

除非用上通配符 <?>

 <?> 是任意E : <E> 的超类型

 

 这样也没有问题:

 

 这样也能够:

经过这几个小例子能够发现,使用了通配符的泛型与外面的类型是同等进行静态检查的。

也就是说: ArrayList<Integer> 是 List<?> 的子类型,由于内外都是

 

关于equality的讨论

在离散数学当中,等价性的原则有三

  • 自反性
  • 对称性
  • 传递性

而在java当中,不一样ADT的等价性在知足这几个条件的基础上,还存在着更加严格的要求。

我在这里说的是ADT的等价性而不是对象的等价性,由于对象等价的概念是物理层面的,简单的理解就是调用“==”或者equals()方法可以获得真值,详细一些来讲就是从java的运行时角度来看,两个对象的数据值是同样的。

而ADT则是抽象层面的东西,是咱们在设计程序的时候本身定义出来的,可能有不一样的规则:
好比说,若是我在讨论动物与植物,那么我能够在概念上把兔子和猴子当作等价的,而猴子和树是不等价的。

而咱们在设计ADT时,就必须使咱们定义的等价遵循上面的三个条件。

 

额外的规则

程序设计中的等价是为程序行为服务的,所以应该具备更加多的限制

  • equality应当遵循等价的基本原则,也就是自反,对称和传递,这是基础
  • equals应当具备行为连续性,若是equals中比较的元素的值没有改变,那么对该实例的重复调用某一个方法应当始终返回相同的结果。
  • 若x不是null,x.equals(null)应当永远返回false
  • equals应当与hashcode的比较具备相同的结果

观察等价性与行为等价性

观察等价性

即“看起来同样”。

指的是两个引用,若是它们是观察等价的,在不对它们调用mutator方法的状况下,任何obsever方法的调用都没法区分这两个引用。

也就是说,client能够只经过observers就区分两个具备观察等价性的实例对象。

观察等价性侧重于两个对象在程序运行的当前时刻是处于相同状态的

行为等价性


即“用起来同样”

对于哪怕引用,哪怕调用了mutators方法,两个ADT的行为始终是同样的(这个行为固然也能够包括observers,因此行为等价性是包含观察等价性的)。

行为等价性侧重于两个对象如今是同样的,在将来也会一直是同样的。

行为等价性的规定如此严格,通常来讲,只有两个指向同一个对象的引用才能知足这个条件。

这对mutable对象来讲是合理的,由于mutable对象当中存在着太多的不肯定性,好比说经典的“将list放入set中”的例子

 设计模式

设计模式相关的内容汗牛充栋,对于设计模式自己,我没有什么好补充的,由于我写博客的目的是记录感想大于总结知识点,我主要想要谈一下对设计模式的理解。

设计模式是顶层设计。

若是说继承与面对对象是代码层面的复用,那么设计模式就是模块层面的复用,经过一些很好用的框架来方便咱们的程序的编写。

起初我对设计模式十分抵触。缘由是我对java语法的掌握不够熟练,所以在使用设计模式中的工厂模式的时候,在继承,泛型,类型转换的各类错误之中摸爬滚打了好久,吃了不少苦,最后也没有体会到工厂模式的妙处所在,反而为其所累。

我是这么想的:

  • 设计模式不是目的,若是为了使用设计模式而使用设计模式,反而会使程序更加复杂而难以理解。

个人代码量实在过小,功能也很简单,因此没有尝到设计模式的好处。

相关文章
相关标签/搜索