软件构造学习笔记 【六】 (第8、9章)


这次笔记我们主要学习第五部分。

一、ADT和OOP中的“等价性”

1.1 等价关系

关于等价关系我们在集合论与图论中已经学过,因此不加赘述。
等价关系满足的性质:自反性、传递性、传递性。
java中常用的等价关系包括==.equals()

1.2 对于等价关系的定义

对于等价关系的定义大概可以有两个方面:

1.2.1 AF

众所周知,AF是一个映射。如果对于两个表示值映射到的抽象值相同,那么两个表示值可以称之为等价。

1.2.2 observer

众所周知,observer是ADT的一种操作,用于获取其他种类的数据。假如对于两个值的所有操作获取的值都相同,那么这两个值可以称之为等价。

1.3 ==和equals()

1.3.1 ==

用于判断引用等价性,如果两个值的内存地址相同则返回true
一般应用在基本数据类型中。

1.3.2 equals()

这位更是重量级——equals()
用于判断对象等价性。
java自带的类中的的equals方法是已经经过修改的,但是我们自定义的ADT就需要自己去重写这个方法,因为如果不重写的话就跟==相同。

1.4 重写equals()和hashcode()

1.4.1 equals()重写

大致套路如下:
先写false的情况(传入空指针、类型是否相同),再进行逻辑判断。
看类型的方法instanceof
instanceof关键字实现的功能是判断某个对象是不是特定的类型或子类型,使用instanceof是一种动态类型检查。如果类型相同,返回true,不同返回false;
重写的代码示例如下:

 @Override
     public boolean equals(Object obj) {
         if (obj instanceof Person) {
             Person person = (Person) obj;
             return name.equalsIgnoreCase(person.getName().trim());
         }
         return false;
     }

1.4.2 hashcode()重写

在重写equals()时,我们也要重写hashcode()
由于java的规定,等价的两个对象必须要有相同的哈希值,同时尽可能使得不等价的对象的哈希值不同。哈希值是跟对象一一对应的。
hashCode()一般用在基于哈希表的集合类中(HashMap、HashSet)。查找一个对象时,
先通过hashCode直接找到对象对应的桶,再在桶里一个个用equals比较。
总之要根据对象的特性写出不同的hashcode()

1.5 观察等价性、行为等价性

观察等价性: 在不改变状态的情况下,两个mutable对象是否看起来一致
行为等价性:调用对象的任何方法都展示出一致的结果,基本相当于==
可变类型倾向于看它严格的观察等价性,但是有些时候观察等价性会导致RI出现问题:
当可变类型放入set这种集合类中,当它变化时,hashcode随之改变,set直接就找不到了。
因此对于这种可变类型我们可能不要求重写equals()。
对于StringBuilder我们一般常看它的行为等价性。

1.6 自动装箱

自动装箱,指的是把int自动装成Integer格式的过程。而Integer在-127~128范围内是直接占用内存的一片区域的,因此可以直接用==完成。

二、面向复用的软件构造技术

2.1 软件复用

2.1.1 概念

面向复用编程(programming for reuse):开发出可复用的软件;
基于复用编程(programming with reuse):利用已有的可复用软件搭建应用系统。
程序的组件级、代码级视图都可复用,其中最主要的是源代码的复用。
模块级别的复用中涉及到了编写可复用的接口、类、抽象类等,是我们这章学习的主要内容。

2.1.2 优点

降低成本和开发时间;
经过充分的测试,可靠、稳定;
标准化,在不同应用中保持一致。
在这里插入图片描述

图2.1 复用对程序性能的影响

2.2 Liskov 替换原则(LSP)

子类型多态:客户端可用统一的方式处理不同类型的对象
在可以使用父类的场景,都可以用子类型代替而不会有任何问题

这段话就是LSP原则体现出的状态。

2.2.1 LSP内容

子类型可以增加方法,但不可删除方法;
子类型需要实现抽象类型中的所有未实现方法;
子类型中重写的方法不能抛出额外的异常;
协变(co-variance):子类型中重写的方法必须有相同或子类型的返回值或者符合的参数;
逆变(contra-variance):子类型中重写的方法必须使用同样类型的参数或者符合的参数。
可以简记如下:
更强的不变量
更弱的前置条件
更强的后置条件

2.2.2 协变和逆变

2.2.2.1 协变

协变:子类更具体,返回值异常不变或越来越具体。

在这里插入图片描述

图2.2 协变
2.2.2.2逆变

逆变:子类更具体,参数不变或越来越抽象。
注意:逆变无法用在java中,如图所示:
在这里插入图片描述

图2.3 逆变示例

这种代码在java中会报错,如果去掉@overload会视为重载。

2.2.3 数组的子类型化

java中,数组是可协变的,举个例子,Integer[]是Number[]的子类,如下图所示:
在这里插入图片描述

图2.4 如图所示

2.2.4 泛型的子类型化

java中,泛型类型是不支持协变的,这是因为发生了类型擦除,运行时就不存在泛型了,所有的泛型都被替换为具体的类型。如ArrayList<String>是List<String>的子类,但是List<String>不是List<object>的子类。
在这里插入图片描述

图2.5 如图所示

怎么解决这个问题呢?我们可用之前说过的通配符?,用这东西即可解决引用未知类型的问题。
在这里插入图片描述

图2.6 通配符示例

这个相关内容我们在之前第7章已经讲过(带上限的extend,带下限的super),在此不加赘述。

2.3 委托(delegation)

2.3.1 定义

委派是一个对象请求另一个对象的功能,通过运行时动态绑定,实现对其他类中代码的动态复用的过程。
通俗来说就是我在这个类中借用一下你这个对象,让这个对象执行对应类中的方法。我声明这个对象的过程叫组合,我调用对象方法的时候叫委托。
“委托”发生在object层面
“继承”发生在class层面

2.3.2 委托的分类

委托可以分为两类,分别如下:
依赖 (Dependency):是临时性的delegation。
把被delegation的对象以方法参数方式传入。只有在需要的时候才建立与被委派类的联系,而当方法结束的时候这种关系也就随之断开了。
关联 (Association):是永久性的delegation。
被delegation的对象保存在rep中,该对象的类型被永久的与此ADT绑定在了一起。
两种方法的区别就在于有没有一个变量去存储那个对象。
其中关联也可以分为两类:
组合(Composition): 更强的association,但难以变化。
就是直接在构造方法里新建一个对应的变量;
聚合(Aggregation): 更弱的association,但可动态变化。
这个就是可以在构造方法中传入一个变量就可以了。

2.3.3 复合复用原则(CRP)

CRP鼓励我们多用委托而非继承。
利用委托的机制,我们可以写两棵继承树,将功能的具体实现与调用分离,在实现中又通过接口的继承树实现功能的不同实现方法,而在调用类中只需要创建具体的子类型然后调用即可。
在这里插入图片描述

图2.7 示例

最终可以实现灵活可变的复用。

2.4 黑盒框架与白盒框架

黑盒框架
通过实现特定接口进行框架扩展,采用的是委托、组合机制达到这种目的,通常采用的设计模式是策略模式(Strategy)和观察者模式(Observer);
黑盒所预留的是一个接口,在框架中只调用接口中的方法,而接口中方法的实现就依据派生出的子类型的不同而不同,它的客户端启动的就是框架本身。
白盒框架
通过继承和重写实现功能的扩展,通常的设计模式是模板模式(Template Method);
白盒框架所执行的是框架所写好的代码,只有通过重写、重载方法来实现新的功能,客户端启动的的是第三方开发者派生的子类型。
在这里插入图片描述

图2.8 挺有用的一道题
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值