软构复习(二)

1.可复用的度量、形态与外部表现
有四种层面上的复用:
源代码级别;
模块级别:类、抽象类、接口;
库级别:API、包;
系统级别:框架。

可复用的一些外在表现:
类型可变;
功能分组;
实现可变;
表示独立;
分解普通行为。

复用的软件构造有两种:
一种是面向复用编程:开发可复用的软件;
另一种是基于复用编程:利用已有的可复用软件搭建应用系统。

这里有一点自己的理解:可复用,比如说我们有一堆应用场景,他们之间有
相同的地方也有不同的地方,那么我们怎么开发能够是我们可以写更少的代码
,或者说怎么复用呢,一种模式就是:
接口
抽象类 抽象类 抽象类
一般类 一般类 一般类
在这里进行一下分析,首先提取出所有场景都具有的地方,属性和方法(一般是方法)
,然后写在接口里,在然后,虽然说所有的应用场景的所有共同方法已经被我们找到了
,但是,某几个场景下还是有相似的地方,我们可以继续写在子接口,或者抽象类里面
,然后再针对每种场景下具体的实现我们要完成实现类。在这里还要提醒一下,这一步
是面向复用的编程,根据后面我们还需要使用委派的方法,那么这里面最好设计的是一些
在实际场景中可以用的模块,然后再利用这个已经设计好的API完成各个场景的实现。

为什么要复用呢?
减少成本和开发时间;
复用的软件已经经过了充分的测试,可靠且稳定;
标准化,因为是复用,或者设计复用,所以规则会更标准。

但是复用也有一定的代价:
开发可复用的软件:开发成本高,而且性能差,因为要适应普通的场景,缺少针对性;
基于复用开发:往往直接拿来是没法用的,需要适配,毕竟是别人开发的使用
,并不是为了你而开发。

复用很好,但是我们如何来定义复用程度呢?
一个好的可复用软件应该满足:小、简单;与标准兼容;灵活可变;可扩展;
泛型、参数化;模块化;变化的局部性;稳定;丰富的文档和帮助。

一句话,软件构造中的所有东西可以复用,复用的目的就是为了增加开发效率。

白盒复用:也就是复用源代码,也可以修改,这个很低级,需要对源代码有充分的了解,都不如自己写了。
黑盒复用:内部看不见,通过API接口来使用他,当然你没办法修改,也就可能
	和你的软件存在一些不适用。

代码级别的复用:最低级的复用。
1.如何冲互联网上快速找到需要的代码片段;
2.如何冲源代码中检测出克隆代码。

模块级别的复用(类、接口):
怎么复用类的方法:
继承:怎么就相当于你有了那个类的属性,方法,也就不需要多写了,或者重写那些方法,但是吧
一般用这种方式的话表示你要设计的类有很多地方要用到那个类里面的东西。
委托:这个就好,相对于继承来说,你可能只需要用那个类的一些方法,这样的话
用委托就好些了。
一些理解:继承的话更加有结构性,管理起来啊,拓展起来啊都会好一下,但是继承的类是这个类的父类
,这会让整个结构的复杂度提高,难以理解。这时候就需要用委托了,因为你只是要用一些,
甚至有一次就不用了,那我不能当你儿子对吧,那就叫委托,我是你朋友,求你办点事。两者各有各的好处。

库级别的复用:为什么这么说呢,我觉得和模块级别的复用并没有很大的差异,但是不一样在于,这个复用的更多,你的软件里面大
东西都是复用的某一个库,但是既然你能够在这个1库里面用这么多的东西,就说明,要么这个库的复用度特别高,也就是说
完全没有针对性,说明这个库完成的是一个很常用的东西;还有一种情况,这个库和你的软件有很大的关系,比较说你的软件
是搞图像识别的,而这个库是opencv,想必你应该明白了,你在图像识别里面用到的很多函数,类啊什么的都是用的他的。

最后一种就是系统级别的复用:框架:一组具体类、抽象类、及其之间的连接关系
这个是什么呢,框架,那么他和你的软件一定有着很紧密的联系,为什么这么说,你的软件的框架和他的差不多,就相当于你的软件
使他的一个实现一样,这还不能说明问题吗?上面对于框架的定义就可以看出,当然,有些东西你不用实现了,大家都一样,就是他
具体类,还有些事抽象类,你应该明白,这个东西就是你需要实现的。

对于框架有两种:
	白盒框架:通过代码层面的继承进行框架拓展,这个在我看来就是可拓展性
	另外一种是黑盒框架:通过接口/delegation进行框架扩展。你不知道里面的实现是什么,
	或者说你需要实现。

可复用性的外部表现:
类型可变:不能只适用于某一个类型;
功能分组:这样用的来的话可以很方便,因为你分开了,我可以用我想用的;
实现可变:那我肯定要有自己的实现啊,因为我的场景和你的场景又不一样;
表示独立:这个时很关键的,因为我不能透露给你我的具体实现,防止你考虑了
里面的具体实现而当我改变后你的实现也要跟着改变,我改变,但是我
还是会符合我的spec的要求,但是你用的不是spec里面的,那我就保证不了了。
共性抽取:如果没有共性,我复用个毛。

2.面向复用的软件构造技术
用别人的软件大家都会,但是要开发一个可以被大家复用的有点困难。
这里我们将介绍如何开发一个可复用的类、API、framework。

设计一个可复用的类:
继承与重写、重载、参数多态与泛型编程、行为子类型与LSP、组合与委托。
设计可复用的库与框架:
API与library
framework
java collections framework

设计一个可复用的类:
行为子类型与LSP:lsp是里氏替换原则,表示用子类替换基类而不会改变程序的特性
;而行为子类型是为了满足里氏替换原则,当满足了行为子类型,就满足
里氏替换原则。
行为子类型的要求:
子类型可以增加方法,但是不可删;
子类型需要实现抽象类型中的所有未实现方法;
子类型中重写的方法必须有相同的或子类型的返回值或者符合co-variance的参数;
子类型中重写的方法必须使用同样类型的参数或者符合contra-variance的参数;
子类型中重写的方法不能抛出额外的异常。
在spec中可以这样理解:
更强的不变量;
更弱的前置条件;
更强的后置条件;
更少的异常;

	强行为子类型化:
		前置条件不能强化;
		后置条件不能弱化;
		子类型方法参数:逆变;
		子类型方法的返回值:协变;
		异常类型:协变;
		不变量要保持。
	自己的一点理解:前四个是要满足子类型的要求的,后面做了一些强化,比如说异常类型
		保持协变,可以把它和返回值那个等同,因为spec里面要求抛出的是某种异常
		,你在子类中也必须要抛出这种异常,也就是更加具体,也就是协变。而不变量
		为什么要保持,和原来的是一样的,可以增加但是不能改变。
	LSP讲到这里了,就要提一下,因为我们是要求用java来编写的,但是java不支持逆变,因为
	java有一个重载原则,如果支持了逆变,那么重载就会变得很复杂。

	泛型中的LSP:即使两个类满足子类型,但是泛型不满足。原因是因为类型擦除。
		类型擦除:泛型只存在于编译阶段,在运行时会被擦除。泛型不能使用instanceof
			,因为这个实在静态编译阶段,但是可以用getclass。
		解决方法:因为我们需要用到子类型,所以我们可以使用通配符来实现,通配符有
			三种形式:
				list<?>:无限定通配符;适用情况,方法的实现不依赖于类型
					参数或者只依赖于Object类中的功能。
				<? super A>:下限通配符。
				<? extends A>:上限通配符。

		PECS:produce->extend/consumer->super;我理解是子类型可以替代父类型,但
			时父类型不能够替代子类型,为什么这样说呢,这样理解:
			List<? extend Cat>这样定义的话,因为泛型在某一时刻只能是某一种类型
			,这里通配符的意思是在定义不同的类型,那么这样子的话,因为泛型在编译阶段
			还没有被擦除,所以编译器是不知道他到底是什么类型的,那么你就不可以在里面
			加东西,为什么呢,因为类型可能很低,你要加入的东西可以是这个类型的父类型
			,父类型是不能够代替子类型的,所以肯定给你编译不通过;同理,List<? spuer Cat>
			,在这个里面不能使用get,为什么呢,因为返回的类型可能很高,你要接受的东西可能
			是他的子类型,子类型是不能够接收父类型。总结一句就是父类型用来接收,子类型用来
			传入。

委托和组合:委托发生在对象层面,而继承发生在类层面?很多设计模式将继承和委托结合使用。


委派模式:通过运行时动态绑定,实现对其他类中代码的动态复用。

CRP:客户端依赖于接口实现,接口里面的实现依赖于另外的接口(委托),委托的接口里面再有实现。
	这样做的好处是当发生变化时,不需要要改变客户端,而且一般来说客户端是不能改变的,然
	通过修改委托的实现,达到实现接口功能的改变,接口虽然不变,但是实现变了,不变的是代
	码,变化的是功能。这样就形成了这样一张关系图:
	客户端 ——>接口——>实现——>接口——>实现。因为现实中功能有很多是相同的,而事物
	不同,我们需要的是实现事物,但是不同的事物之间也有很多相同之处。

委托的程度:
	Dependency:A use B;
	Association: A has B;
	Composition/aggregation: A owns B; 	
Dependency:临时性的委托,通过方法的参数或者在方法的局部中使用发生联系;
Association:永久性的委托,通过成员变量;
Composition:更强的association,但难以变化,在内部初始化,不需要外部传入,称为组合;
Aggregation:更弱的association,可同态变化,在外部传入,称为聚合。

设计一个可复用的类库和框架(系统层面的复用):
设计API:API是程序员最重要的资产和“荣耀”,吸引外部用户,提高声誉。
设计框架:
白盒框架:基于继承;
黑盒框架:基于委托。

3.面向可复用的设计模式:
面向结构的设计模式:
面向行为的设计模式:

为什么需要好的设计模式:
易于复用、维护、扩展。除了类本身,设计模式更抢到多个类/对象之间
关系和交互过程——比接口/类复用的粒度更大。

设计模式分类:
创建型模式;
结构型模式;
行为类模式。

结构型模式:
适配配模式:(为了使用) 为了能够复用而做的操作。
将某个类/接口转换为client期望的其他类型,从而解决接口
之间不兼容的模式,通过增加一个接口,将已存在的子类封装
起来,client面向接口编程,从而隐藏了具体子类。我的理解
客户端不能够直接只用某个类,我们设计一个接口,client面向
接口,接口有一个实现,这个实现不是那个类,但是用的是那个
类的功能,就像我在P2设计的action和board相似。意思就是
相同的功能,但是实现的形式不一样,参数存在不同,但我因为
我们想要复用,就需要在中间加一个适配器。

装饰器模式:(为了适应变化,满足更多的要求)	为什么不用继承实现而用委派,因为继承是在编译阶段
		完成,而委派是在执行阶段,相比较而言,委派具有更
		好的灵活性,正好适合于这个模式,因为装饰的东西是

、 千变万化的。
用每个子类实现不同的特性。对每一个特性构造一个子类,通
过委派机制增加到对象上。每个装饰器都应该只有一个特性。

外观模式:(为了client使用)	是为什么方便client使用而做的操作,因为接口太多,client
		想要使用比较复杂,而且让他明白了内部的构造也提高了
		耦合度,使得代码维护起来比较困难。
	将多个复杂的接口统一到一起形成一个简单接口,然后client与这
	这个简单的接口关联,这样可以降低client和模块之间的耦合度。
	主要是方便client的使用。

行为类模式:
策略模式:(为什么适应变化,满足更多的要求)多个算法来完成不同的任务,但是client根据需要动态切换算法。
通常与工厂方法连用。

模板模式:(相当于黑盒框架)做事情的步骤相同,当时具体方法不同。有一个统一的模板
	,用于控制步骤,每一步的具体操作是不一样的。

迭代器模式:(为什么能够实现迭代器功能,没什么好说的)为了让client能够遍历放入容器中的一组ADT对象。这个容器
	是你实现的,既然是集合,最好有一个迭代器,这样的话你就需
	实现迭代器。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值