作业:从类、API、框架三个层面学习如何设计可复用软件实体的具体技术并撰写学习心得
学习心得20200613
软件复用
软件复用(SoftWare Reuse)是将已有软件的各种有关知识用于建立新的软件,以缩减软件开发和维护的花费。软件复用是提高软件生产力和质量的一种重要技术。早期的软件复用主要是代码级复用,被复用的知识专指程序,后来扩大到包括领域知识、开发经验、设计决定、体系结构、需求、设计、代码和文档等一切有关方面。(源于:百度)
复用级别:
我们将软件复用划分为四种类型:
• 代码级复用:就是通过编写大量的公共类,公共函数等等,供开发人员直接使用。
• 组件级复用:通过将功能的组件化封装,对外提供一组或多组的API接口。
• 模块级复用:在我们开发的项目或者产品中,会发现大量重复的功能模块,比如用户管理,机构管理等等,如果我们在这些模块设计的时候,注重一下扩展性,那么可以应用到有其它类似功能的项目中。在这个级别需要一定的项目的积累,否则在模块功能上以及实用性上会遭遇风险。
• 构架级复用:构架级在设计概念上最为高级的一种。它相当于一个平台或者思想,在这个平台上,可以开发出根据此平台思想稳定而又高效的软件产品。打个比方,数据库中的设计范式可以看成是一个小的构架,你按照范式来设计数据库,一般来说设计质量是有保证的。再举个例子,Windows中的消息机制也算是一个构架,就是说在 Windows中所有运行的程序都遵循它的这个机制,并且在这个机制的保证下,运行的顺畅良好,Linux中的IP Chains也是一个非常优秀的网络防火墙构架,这个构架中其他人可以方便进行外挂程序的嵌入。我拿我们将要开发的公司内部的物资管理系统来举个例子,在这个系统中,有一个很重要的功能,那就是审批,如何把审批这个功能做好,有良好的扩展性,并且可以沿用到其他的也有审批功能的项目中,就是一个我们需要思考的问题(此处仅拿此举例,考虑到实际情况并不一定要做到这么高级。
类。
软件复用的主要思想:
将软件看成是由不同功能部分的“组件”所组成的有机体,每一个组件在设计编写时可以被设计成完成同类工作的通用工具,这样,如果完成各种工作的组件被建立起来以后,编写一特定软件的工作就变成了将各种不同组件组织连接体来的简单问题,这对于软件产品的最终质量和维护工作都有本质性的改变。
软件复用的优点:
• 开发成本的降低。如果在一个项目中,能够直接用到以前项目中的代码,控件,模块,作为一个软件企业,显然的可以把开发成本降低。
• 开发层次的提升。大家知道,罗马不是一天建成的,同样的,在软件开发上,也不会一蹴而就的写出高质量的代码。当我们有意识的进行复用时,我们会在每次重复使用某段代码或者控件的过程中,发现其不足的地方,从而加以改进,这样就会逐渐的提升我们的开发层次,提升整个公司开发的层次,使得公司的软件与其他公司有着根本的不同。
• 产品化的必然阶段。在我们公司目前没有明确的开发产品的现阶段,那么对软件复用的使用,就是我们产品化的一个途径,我们在不断的复用的过程中,不断的强化我们的代码的功能,直至有了一定固定功能,运行稳定,扩展性好的软件,这样一个产品就可以自然而然的产生了.
类(Class)
是面向对象程序设计(OOP,Object-Oriented Programming)实现信息封装的基础。类是一种用户定义的引用数据类型,也称类类型。每个类包含数据说明和一组操作数据或传递消息的函数。类的实例称为对象。
类的实质是一种引用数据类型,类似于byte、short、intchar)、long、float、double等基本数据类型,不同的是它是一种复杂的数据类型。因为它的本质是数据类型,而不是数据,所以不存在于内存中,不能被直接操作,只有被实例化为对象时,才会变得可操作。
类是对现实生活中一类具有共同特征的事物的抽象。如果一个程序里提供的数据类型与应用中的概念有直接的对应,这个程序就会更容易理解,也更容易修改。一组经过很好选择的用户定义的类会使程序更简洁。此外,它还能使各种形式的代码分析更容易进行。特别地,它还会使编译器有可能检查对象的非法使用。
类的内部封装了属性和方法,用于操作自身的成员。类是对某种对象的定义,具有行为,它描述一个对象能够做什么以及做的方法(method),它们是可以对这个对象进行操作的程序和过程。它包含有关对象行为方式的信息,包括它的名称、属性、方法和事件。
类的构成包括成员属性和成员方法(数据成员和成员函数)。数据成员对应类的属性,类的数据成员也是一种数据类型,并不需要分配内存。成员函数则用于操作类的各项属性,是一个类具有的特有的操作,比如“学生”可以“上课”,而“水果”则不能。类和外界发生交互的操作称为接口。
类库技术是软件复用的基础,在软件开发过程中,程序员通过调用类库中的函数可以达到软件复用的目的。如:调用C++类库中的输入输出流函数可以实现输入输出的功能。
继承和委托
继承:通过新操作扩展基类或覆盖操作。 委托:捕获操作并将其发送给另一个对象。
(许多设计模式使用继承和委派的组合)
继承:
继承的特性子类拥有父类非private的属性方法。子类可以拥有自己的属性和方法,即子类可以对父类进行扩展。子类可以用自己的方式实现父类的方法。Java的继承是单继承,但是可以多重继承,单继承就是一个子类只能继承一个父类,多重继承就是,例如A类继承B类,B类继承C类,所以按照关系就是C类是B类的父类,B类是A类的父类。提高了类之间的耦合性(继承的缺点,耦合度高就会造成代码之间的联系)。继承可以使用 extends 和implements 这两个关键字来实现继承,而且所有的类都是继承于java.lang.Object,当一个类没有继承的两个关键字,则默认继承object(这个类在 java.lang 包中,所以不需要 import)祖先类。
extends关键字
在 Java中,类的继承是单一继承,也就是说,一个子类只能拥有一个父类,所以 extends 只能继承一个类。
implements关键字
使用 implements关键字可以变相的使java具有多继承的特性,使用范围为类继承接口的情况,可以同时继承多个接口(接口跟接口之间采用逗号分隔)
委派:
• 功能:一个对象请求另一个对象
• 委派是复用的一种常见形式
委派分为显性委派和隐形委派:
显性委派:将发送对象传递给接收对象;
隐性委派:由语言的成员查找规则。
举例:
• 委派设计模式:一种用来实现委派的软件设计模式;
• 委派依赖于动态绑定,要求给定的方法调用可以在运行时调用不同的代码段;
• 委派的过程如下:
Receiver对象将操作委托给Delegate对象,同时Receiver对象确保客户端不会滥用委托对象;
API:应用编程接口
一些预先定义的函数,或指软件系统不同组成部分衔接的约定。用来提供应用程序与开发人员基于某软件或硬件得以访问的一组例程,而无需访问原码,或理解内部工作机制的细节。
程序功能:
远程过程调用(RPC):通过作用在共享数据缓存器上的过程(或任务)实现程序间的通信。
标准查询语言(SQL):是标准的访问数据的查询语言,通过通用数据库实现应用程序间的数据共享。
文件传输:文件传输通过发送格式化文件实现应用程序间数据共享。
信息交付:指松耦合或紧耦合应用程序间的小型格式化信息,通过程序间的直接通信实现数据共享。
当前应用于 API 的标准,API 可以应用于所有计算机平台和操作系统。这些API 以不同的格式连接数据。每种数据格式要求以不同的数据命令和参数实现正确的数据通信,但同时也会产生不同类型的错误。因此,除了具备执行数据共享任务所需的知识以外,这些类型的API 还必须解决很多网络参数问题和可能的差错条件,即每个应用程序都必须清楚自身是否有强大的性能支持程序间通信。相反由于这种API 只处理一种信息格式,所以该情形下的信息交付API 只提供较小的命令、网络参数以及差错条件子集。交付API 方式大大降低了系统复杂性,所以当应用程序需要通过多个平台实现数据共享时,采用信息交付API 类型是比较理想的选择。
API设计原则
• 小巧:一个类的公共成员尽量少。这样做可以使代码更容易理解、记忆、调试和修改。
• 完整:完整意味着该有的功能都应该有。这一点貌似和小巧有所冲突,但是,如果一个成员函数出现在错误的类中,那么大部分使用者应该都不会找到它。
• 简单清晰:正如其他的设计工作一样,你应该应用这些原则,尽量避免特殊情况。使大部分工作变得简单,特殊的部分也需要考虑但不是本文所关注的。解决特殊问题的原则:如非必要,不要将解决特殊问题的方案影响到通用的情况。
• 观:因每个人的经验和背景的有所差异,因此对直观的判断也会不同。API设计得直观的原则可以理解为:一个经验不足的使用者可以不用使用文档而仅使用代码来理解并使用API。
• 容易记忆:为了使API容易记忆,你应当选择一个一致并准确的命名习惯。使用容易辨别的模式和概念,避免缩写。
• 代码可读性:代码只会写一次,但是却会经常去阅读(调试和修改)。可读的代码也许不再需要重写,但生命周期却贯穿整个产品的生命周期。
• 静态多态:相似的类应当有相似的API。可以使用继承来完成这项工作,即运行时多态特性。但是多态也发生设计阶段。例如,你可以改变一个QProgressBar为QSlider,或者是一个QString为QByteArray。你会发现相识的API使得替换工作变得非常容易,因此这里我们叫它“静态多态”。
静态多态也使得记忆和编程变的更加容易。相关的类使用相似的API通常要比每个类独立的设计API要好得多。
通常,在Qt中,如果没有编译的原因,我们更倾向于使用静态多态而不是继承。这使得我们可以控制公共的类的数量,也可以使新用户可以从文档中更容易找到。
• 可见性:典例:实现或者调用某个函数过程
• 实现某个函数的时候,函数的参数类型本来并没有见过,但通过智能感知提示我们能够了解到这个新 API 并正确取到参数中我们期望得到的信息。
• 调用某个函数的时候,我们需要传入本来并没有见过的参数类型,通过智能感知提示,我们能够知道如何构造或获取这些类型然后正确传进去。
• 调用完某个函数后我们得到了返回值,我们本来并没有见过这个类型,但通过智能感知提示,我们能够学习到这个新的类型,并知道如何正确使用这个返回值。
编写API之前我们需要注意:
1.API应该做一件事,且做得很好 2.API应该尽可能小,但不能太小 3.Implementation不应该影响API 4.类的设计:尽量减少可变性,遵循LSP原则 5.方法的设计:不要让客户做任何模块可以做的事情,及时报错
LSP原则
定义1:如果对每一个类型为 T1的对象 o1,都有类型为 T2 的对象o2,使得以 T1定义的所有程序 P 在所有的对象 o1 都代换成o2 时,程序 P 的行为没有发生变化,那么类型 T2 是类型 T1 的子类型。
定义2:所有引用基类的地方必须能透明地使用其子类的对象。
例如:有一功能P1,由类A完成。现需要将功能P1进行扩展,扩展后 的功 能为P,其中P由原有功能P1与新功能P2组成。新功能P由类A的子类B来完成,则子类B在完成新功能P2的同时,有可能会导致原有功能P1发生故障。
解决方案:当使用继承时,遵循里氏替换原则。类B继承类A时,除添加新的方法完成新增功能P2外,尽量不要重写父类A的方法,也尽量不要重载父类A的方法。
框架(Framework):
一组具体类、抽象类、及其之间的连接关系 开发者根据 framework的规约,填充自己的代码进去,形成完整系统。通常通过选择性覆盖来扩展框架;或者程序员可以添加专门的用户代码来提供特定的功能。我们从应用方面和目的方面给出框架的定义:
应用方面:框架是整个或部分系统的可重用设计,表现为一组抽象构件及构件实例间交互的方法。
目的方面:框架是可被应用开发者定制的应用骨架。
换句话说,用户可以扩展框架,但不应修改其代码。为了增加代码的复用性,可以使用委派和继承机制。同时,在使用这两种机制增加代码复用的过程中,我们也相应地在不同的类之间增加了关系(委派或继承关系)。而对于一个项目而言,各个不同类之间的依赖关系就可以看做为一个框架。一个大规模的项目可能由许多不同的框架组合而成。
白盒框架:
• 白盒框架是基于面向对象的继承机制。在这种框架中,父类的方法对子类而言是可见的。子类可以通过继承或重写父类的方法来实现更具体的方法。
• 局限性:父类中的方法子类一定拥有,要么继承,要么重写,不可能存在子类中不存在的方法而在父类中存在。
黑盒框架:
• 黑盒框架时基于委派的组合方式,是不同对象之间的组合。之所以是黑盒,是因为不用去管对象中的方法是如何实现的,只需关心对象上拥有的方法。
• 较白盒框架更为灵活,因为在运行时动态地传入不同对象,实现不同对象间的动态组合;而继承机制在静态编译时就已经确定好。
黑盒框架与白盒框架之间可以相互转换: