软件构造第九讲和第十讲

第九讲 面向复用的软件构造技术

不同级别的复用:

  1. 源代码级别的复用
  2. 模块级别的复用:类/抽象类/接口
  3. 库级别的复用:API/包
  4. 系统级别的复用:框架

设计可复用的类

  1. 继承和重写
  2. 重载
  3. 参数多态和泛型编程
  4. 行为子类型与Liskov替换原则(LSP)
  5. 组合与委托

设计可复用库和框架

  1. API和库
  2. 框架

1.什么是软件可复用性

面向复用编程:开发出可复用的软件
例如:开发了一个基于泛型的抽象接口Graph,定义了ADT
针对该ADT,用两种不同的Rep,开发了两个不同的实现ConcreteVertexGraph和ConcreteEdgeGraph
基于复用编程:利用已有的可复用软件搭建应用系统
利用该ADT和两个实现,完成了两个应用的开发

为什么可复用?
降低成本和开发时间
经过充分测试,可靠、稳定
标准化,在不同应用中保持一致

2.如何测定“可复用性”(了解)

小而简单;与标准兼容;灵活可变;可扩展;泛型、参数化;模块化;变化的局部性;稳定;丰富的文档和帮助

3.可复用组件的级别与形态

最主要的复用是在代码层面,但软件构造过程中的任何实体都可能被复用
源代码层面:方法,声明等
模块层面:类和接口
库层面:API
构建层面:框架

代码复用类型
白盒复用:源代码可见,可修改可扩展——继承
黑盒复用:源代码不可见,不能修改——委托

(1)源代码复用 (了解)

(2)模块层面复用:类/接口

复用一个类的方法:
1)继承
可以继承父类已存在的方法,也可重写已存在的行为
2)委托
一个对象为实现其功能的某个子集而依赖于另一个对象(一个实体将某物传递给另一个实体)
显示委托将发送对象传递给接受对象
隐式委托通过成员查找语言规则

(3)库层面复用:API/包

库:提供可复用功能的类和方法的集合

(4)系统层面复用:框架

框架:可定制为应用程序的可重用框架代码
一组具体类、抽象类、及其之间关系的连接关系
开发者根据framework的规约,填充自己的代码进去,形成完整系统
框架调用回客户端代码
白盒框架通过代码层面的继承进行框架扩展
黑盒框架通过实现特定接口/委托进行框架扩展

4.设计可复用类

在OOP中设计可重用类
封装和信息隐藏
继承和重写
多态性、子类型和重载
泛型编程
行为子类型和里氏替换原则
委托和组成

(1)行为子类型和里氏替换原则

行为子类型
子类型多态:客户端可以用同一的方法处理不同类型的对象

//Cat是Animal的子类
Animal a=new Animal();
Animal c1=new Cat();
Cat c2=new Cat();
在使用a的场景,都可以用c1和c2代替不会产生任何问题
a=c1;
a=c2;
//不会报错

(java中可通过静态检查)

  • 子类型中可以增加方法,但不可以删除方法
  • 子类型需要实现抽象类型中所有未实现方法
  • 子类型中重写的方法必须有相同或子类型的返回值或者符合co-variance(协变)的参数
  • 子类型中重写的方法必须使用同样类型的参数或者符合contra-variance(逆变)的参数
  • 子类型中重写的方法不能抛出额外的异常

规约应符合(编译器检测不到,需自行检查)

  • 相同或更强的不变量(子类实现相同的RI或增加新的RI)
  • 相同或更弱的前置条件(子类重写方法时)
  • 相同或更强的后置条件(子类重写方法时)
    子类型的规约要更强,足以替换父类型的规约

LSP(强行为子类型化)

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

协变
父类型——>子类型:规约越来越具体,返回值不变或变得更具体,异常不变或者变得更具体
反协变、逆变
子类型——>父类型:规约越来越具体;参数类型不变或更抽象

class T{
void c(String s){...}
}
class S extends T{
@Override void c(Object s){...}
}
目前Java遇到这种情况,会当做重载看待

数组是协变的,Number,List,Set支持协变

Number[] numbers=new Number[2];
number[0]=new Integer(10);
number[1]new Double(3.14);
// 数组元素看具体实现,元素类型可以是Number的子类

Integer[] myInts={1,2,3,4};
Number[] myNumber=myInts;//myNumber数组元素已确定为Integer
myNumber[0]=3.14//运行时出错

泛型中的LSP
泛型类不支持协变,编译器会报错

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

List<Integer> myInts=new ArrayList<Integer>();
myInts.add(1);
myInts.add(2);
List<Number> myNums=myInts;//编译器报错

类型擦除
泛型的类型参数在运行时被具体的类替代

泛型中的通配符?

<? super A> :<>中可填A及其父类(父类的父类...) <? extends A>:<>中可填A及其子类 例如: List和List都是List<?>的子类型

List ——>List<? extends Integer> ——> List<? extends Number> ——>List<?>
List ——>List<? super Number> ——>List<? super Integer> ——>List<?>
List ——>List<? extends Number>
List ——>List<? super Integer>

(2)委托

接口 Comparator<>
int compare( T o1,T o2)
如果你的ADT需要比较大小,或者放入Collections或Arrays进行排序,可实现Comparator接口并重写compare()函数

//需要比较的类
public class Edge{
Vertex s,t;
double weight;;
...
}
//构造用于实现比较的类
public class EdgeComparator implements Comparator<Edge>{
@Override
public int compare(Edge o1,Edge o2)if (o1.getWeight() > o2.getWeight() )
		return 1;
	else if(o1.getWeight() == o2.getWeight() )
		return 0;
	else 
		return -1;
	}
}
//排序函数
public  void sort(List<Edge> edges){
Comparator comparator=new EdgeComparator();
Collections.sort(edges,comparator);
}

接口 Comparable
另一种比较方法,让你的ADT实现Comparable接口,然后重写compareTo()方法
与使用Comparator的区别是:不需要构建新的Comparator类,比较代码放在ADT内部。

public class Edge implements Comparable<Edge>{
Vertex s,t;
double weight;
...
public int compareTo(Edge o){
if(this.getWeight()>o.getWeighgt())
	return 1;
else if(this.getWeight()==o.getWeighgt())
	return 0else return -1;
}
}

委托
也叫做Composite Reuse Principe(CRP)
如果一个类不需要继承另一个类的全部方法,通过委托机制调用部分方法,从而避免大量无用的方法。
委托:两个类A和B,在B中(has_a or use_a)
继承:B(is_a)
委托发生在object层面,而继承发生在class层面
组合优于继承原则
例子:

interface Flyable{
	public voif fly();
}
interface Quackable{
	public void quack();
}
class FlyWithWings implements Flyable{
	@Override 
	public void fly(){
		System.out.println("fly with wings");
	}	
}
class Quack implements Quackable{
	@Override 
	public void quack(){
		System.out.println("quack like duck");
	}
}

interface Ducklike extends Flyable,Quackable{}   //接口的组合,定义了行为的组合
public class Duck implements Ducklike{//从组合接口中派生出具体类
	Flyable flyBehavior;              //委托
	Quackable quackBehavior;	//委托
	void setFlyBehavior(Flyable f){//设置委托对象实例
		this.flyBehavior = f;
	}
	void setQuackBehavior(Quackable q){//设置委托对象实例
		this.quackBehavior=q;
	}
	//通过委托实现具体行为
	@Override
	public void fly(){
	this.flyBehavior.fly();
	}
	@Override
	public void quack(){
		this.quackBehavior.quack();
		}
	}

Client code:
//对接口编程
Flyable f=new FlyWithWings();
Quackable q=new Quack();

Duck d=new Duck();
d.setFlyBehavior(f);
d.setQuackBehvior(q);
d.fly();
d.quack();

委托的类型
支持一对多的委托

  • 依赖——临时性的委托
    在客户端,将类a作为参数传入B的方法

    class Duck{
    void fly(Flyable f){
    	f.fly();
    	}
    }
    Client code:
    
    Flyable f=new FlyWithWings();
    Quackable q=new Quack();
    
    Duck d=new Duck();
    d.fly(f);
    d.quack(q);
    
  • 关联——永久性的委托
    类B属性中有类A的对象

    • 组合——更强的关联,紧绑定,难以变化
	 class Duck{
		 Flyable f =new FlyWithWings();//f不能指向其他地方,相当于前面加了一个final
		 void fly(){
			 f.fly();
			 }
		}
Client code:
Duck d=new Duck();
d.fly();//只能调用FlyWithWings中的fly
- 聚合——更弱的关联,松绑定,可动态变化
	 class Duck{
		 Flyable f;//f可以指向任何对象
		 void Duck(Flyable f){
			 this.f=f;
		}
		void setFlyBehavior(f){
			this.f=f;
		}
		 void fly(){
			 f.fly();
		}
	}
Client code:
Flyable f=new FlyWithWings();
Duck d=new Duck(f);//调用void Duck(Flyable f)
d.fly();
d.setFlyBehavior(new CannotFly());//调用void setFlyBehavior(f)
d.fly();//调用void fly()

6.设计系统级别的复用API库和框架

白盒——继承
黑盒——委托

第10讲 面向可维护性的构造技术

1.软件可维护性和演化

软件可维护性的类别
纠错性
适应性
完善性
预防性

软件维护不仅仅是运维工程师的工作,而是从设计和开发阶段就开始了

2.可维护性的度量

可维护性的别名:可扩展性,灵活性,可适应性
模板模式,可管理性,支持性
一些常用可维护性的度量:

  • 圈复杂度——测量代码结构复杂度,越复杂,白盒测试路径覆盖测试用例越多
  • 代码行数
  • 可维护性指数(MI)
  • 继承的层次数
  • 类之间的耦合度
  • 单元测试的覆盖度

3.模块化设计和模块化原则

模块化编程:
高内聚——都是为了完成一个任务
低内聚——模块间联系薄弱
分离关注点——每个模块只完成一个功能
信息隐藏——不需要知道具体实现

(1)五个评价模块化程度的标准

可分解性——大的组合可分解为小的
可组合性——大的组合是由小的组成
可理解性
可持续性——发生变化时受影响范围最小
出现异常之后的保护——出现异常后受影响范围最小

(2)五个模块化设计规则

直接映射
尽可能少的接口
尽可能小的接口
显式接口
信息隐藏

(3)耦合和内聚coupling and cohesion

耦合:模块间的联系——继承和委托(委托包括关联和依赖)
内聚:每个类为同一功能服务
耦合越高,内聚越低

4.面向对象设计原则:SOLID

(SRP)单一责任原则
(OCP)开放-封闭原则
(LSP)liskov替换原则
(ISP)接口聚合原则
(DIP)依赖转置原则

SRP 单一责任原则

最简单的原则,确实最难做好的原则
一个类一个责任
不应有多于一个的原因使得一个类发生变化

OCP (面向变化的)开放-封闭原则

类应该是对扩展性开放,对修改封闭
可以扩展模块的行为,但是不能修给模块本身的代码
关键的解决技术:抽象技术
继承或者委托

LSP liskov替换原则

子类型必须能够替换其基类型
派生类必须能够通过其基类型的接口使用,客户端无需了解二者之间的差异
见第九讲

ISP 接口隔离原则

不能强迫客户端依赖于他们不需要的接口,只提供必需的接口

DIP 依赖转置原则

抽象的模块不应该依赖于具体的模块,具体应依赖于抽象
委托时,要通过接口建立联系,而非具体子类

5.语法驱动构造

终止节点、叶节点
无法再往下扩展,通常表示为字符串,用引号标注,如‘http’和‘:’
产生式节点、非终止节点
遵循特定规则,利用操作符、终止节点和其他非终止节点,构造新的字符串。
产生式的形式
非终止节点 ::=终止节点、非终止节点和操作符构成的表达式
根节点
操作符

  • 连接 x : := y z
  • 重复 x : := y*
  • 选择 x : := y | z
    后缀操作符*,?,+有最高优先级别,连接次之,选择最后
    更多的操作符
  • Optional(0或1发生)用 ?表示
    x : := y? 一个x是一个y或者是一个空串
  • 1或者更多次发生,用+表示
    x : := y+ 和 x : := y y*等价
  • 一个符号类[…],由[…]中任意一个字符组成的长度为一的串
    x : := [a-c] 等价于 x : :=‘a’ | ‘b’ | ‘c’
    x : := [aeiou] 等价于 x : := ‘a’ | ‘e’ | ‘i’ | ‘o’ | ‘u’
  • 一个反向字符类[…],表示一个长度为一并且不由[…]中字符构成的字符串
    x : :=[^ a-c] 等价于 x : := ‘d’ | ‘e’ | ‘f’ | ‘g’ | …
    递归

正则语法
简化之后可以表达为一个产生式而不包含任何非终止节点
正则表达式 (简写为regex)
去除引号和空格,从而表达更加简洁

  • . 任何一个单个字符
  • \d 任何一个数字,等价于[0-9]
  • \s 任何空格字符,包括space,tab,newline
  • \w 任何单词字符,包括下划线,等价于[a-zA-Z_0-9]
  • . , (, ) ,* ,+,… 对运算符或特殊字符进行转义,以便逐字匹配
Original:
		' http:// ' ( [a-z]+ ' . ' )+ [a-z]+ ( ' : ' [0-9]+ ) ? ' / '
Compact:
		http://([a-z]+.)+[a-z]+ (:[0-9]+) ?/
With escape:
		http://([a-z]+\.)+[a-z]+ (:[0-9]+) ?/

Pattern是对正则表达式进行编译之后得到的结果
Macher:利用Pattern对输入字符串进行解析
PatternSyntaxException是unchecked异常,表明在正则表达式匹配时有语法错误
在java中使用正则表达式

用单个空格代替多个空格
String singleSpacedString=string.replaceAll(" +"," ");
匹配一个URL
Pattern regex=Pattern.compile("http://([a-z]+\\.)+[a-z]+ (:[0-9]+) ?/");
Mather m=regex.matcher(string);
if(m.matches()){
	//then string is a url
}
ConstructDescription
[abc]ab,or c
[^abc]除了a,b,c以外任一字母
[a-zA-Z]a到z,或A-Z
[a-d[m-p]]等价于[a-dm-p]
[a-z&&[def]]等价于[def]
[a-z&&[^bc]]等价于[ad-z]
[a-z&&[^m-p]]等价于[a-lq-z]
metacharacter元字符
<([{^-=$!]})?*+.>
两种将元字符当做普通字符使用的方式:
  • 在元字符前加\
  • 用\Q和\E闭合
ConstructDescription
.任何一个字符
\d[0-9]
\D[^0-9]非数字
\s一个空格字符[ \t\n\x0B\f\r]
\S一个非空格字符[^\s]
\w[a-zA-Z_0-9]
\W[^\w]
GreedyReluctantPossessiveMeaning
X?X??X?+X,一次或没有
X*X*?X*+X,零或多次
X+X+?X++X,一次或多次
X{n}X{n}?X{n}+X,重复n次
X{n,}X{n,}?X{n,}+X,至少n次
X{n,m}X{n,m}?X{n,m}+X,至少n次但不多于m次
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

wind~飘

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值