一,导论
-
软件危机的主要表现:
- 软件不能按时完成;
- 软件超出预算;
- 软件不能正常运行;
- 软件难以维护(使用周期短)
-
问题根源:
- 开发效率低,产品质量差,产品难维护。
-
对策:通过适当的需求过程、设计方法,确保开发正确的软件产品。
-
软件产品的审美标准:
- 有弹性
- 可重用
- 易维护
-
软件设计的困难:复杂性高,易变化
- 降低复杂性,采取模块化设计,关注点分离
- 抵抗易变性,模块设计采取:高内聚低耦合
- 高内聚、低耦合是任何模块设计的基本原则。
-
图示:
-
面向对象方法的优点
- 容易理解(解决复杂性问题)
- 容易适应变化(解决易变化问题)
- 有利于提高开发效率与质量(降成本)
-
面向对象方法的优点,来源于面向对象的程序设计特点:抽象,封装,继承,多态
二,软件设计过程与UML简介
1.软件过程概念
- 软件过程是开发软件产品的(具体的)活动序列(即有先后顺序的活动集合)
- 软件过程决定了软件产品的质量。
- 软件方法:比过程更抽象的开发思想。
- 软件过程模型:对过程的抽象,也称为软件生命周期模型。
- 软件过程框架:定义软件过程的基础。
- 软件过程模式:针对软件开发的某个困难被证明有效的开发过程。
2. 瀑布模型与迭代模型
- 瀑布模型是最早提出,影响深远的模型。
- 迭代开发模型是现在流行的软件过程模型。
瀑布模型
- 瀑布模型主要活动:
- 需求分析
- 设计
- 构建
- 系统集成与测试
- 系统维护
- 瀑布模型先后顺序:依次线性推进
- 瀑布模型意义:提供了一个结构化的框架,容纳软件开发的基本活动,对理论研究和理解有很大贡献
- 瀑布模型缺点:不能适应需求的变化,对需求难以理解的系统也不适合。
- 瀑布模型也叫作**“线性顺序模型”**
迭代模型
- 迭代模型是通过多次反复,逐步完成系统功能。
- 每次迭代中,活动与瀑布模型中的活动类似,也可容纳其他活动。
- 迭代式开发的每一次迭代,其过程结构一致。
比较
瀑布模型 | 迭代模型 |
---|---|
难以适应需求变化 | 可以适应需求变化 |
风险很高,成功率低 | 降低了风险,成功率高 |
与结构化方法同生 | 与面向对象的方法同兴 |
3. 统一过程
- 阶段:
- 初始阶段
- 细化阶段
- 构建阶段
- 交付阶段
4. UML
- 结构图:类图, 对象图, 组件图, 部署图
- 行为图:用例图,活动图,顺序图, 通信图, 状态图
- 模型管理图:包, 子系统, 模块
三,UML概述
用例图
活动图
交互图: 顺序图与通信图
类图/组件图
包图
状态图
部署图
四,面向对象编程特点
1. 类——抽象、封装、继承、多态
- 类:对象的抽象。类的实例即对象。面向对象的语言以类为代码单元
- 封装:只能通过类的公开方法读写其数据。
- 继承:派生类拥有基类全部属性;派生类对象可以赋值给基类对象,反过来不行。
- 多态是代码自动定位查找技术,也称为动态绑定、后绑定。
2.关联—类的相互关系
- 关联:成员类型为另一个类;
3. 接口—类与类之间的契约
4. 属性—程序运行的参数
5. 反射—降低耦合的终极手段
五, 对象设计SOLID原则
- SOLID为我们提供了这样的准则和思考方法。
1. 单一职责原则
- 一个类绝不要因为多于一个原因变化。即绝对保证引起类变化的原因是单一的。
- 尽管一个类可以做很多事,但不要这样做。这会引起长远的不稳定。
- 一个类负责一个职责,使变化的维度单一。
- 并不意味着一个类只能有一个方法,而是众多的方法必须是为了一个目的。(高内聚原则)
- 如果一个类有多个职责,就应该拆分为多个类。
2. 开闭原则
- 软件项(类、模块、函数等)应该可以扩展,但不必(为扩展而)修改。
- 为模块扩展功能时,不需要修改原来的代码
3. Liskov替换原则
- 引用了基类的功能在使用派生类对象代替基类后应不受影响,且不须知道具体为何种派生类
- 满足LSP原则主要以继承为基础,通过抽象和多态实现。但如果继承层次不当,在满足LSP原则时有可能破坏开闭原则(OCP)。
4. 接口隔离原则
- 接口分离的意思是让每一个接口所含方法具有合理的内聚性。
- 无关的方法如果被包含在一个接口中,对于依赖这些接口的使用者会很困惑,影响其复用性;
- 对于实现这些接口的类,会增加复杂性。
5. 依赖倒置原则
- 高层模块不应依赖低层模块。不仅如此,二者都要依赖抽象性。
- 通常,我们在多层体系结构程序中,高层模块依赖低层模块
- 但实际上,在实现这种层模型时,层与层之间并不是、也不应直接依赖。
- 如果直接依赖,则低层的变化会影响高层的代码实际上,层之间依赖接口。即:高层定义接口,低层实现接口。高层按接口使用低层功能
- 实际上是低层依赖高层的接口,因此称为依赖倒置。
六,模式概念与结构模式
-
一个模式的名称,代表了一套解决办法、做法。
-
架构模式是软件架构设计的经验总结;
-
设计模式是微观结构设计经验的总结。
-
模式、惯用法等都是设计原则、理念的体现
-
使用模式的好处往往是带来灵活性(可扩展性)。但也会使代码变得复杂,执行效率可能降低;
-
如果系统需要的灵活性是推测的,则应仅保持必要的灵活性。(不要过度使用模式)
设计模式按使用目的可分为:创建模式,结构模式,
- 行为模式创建模式:关于如何创建对象的模式
- 结构模式:处理类或对象的组合的模式
- 行为模式:类或对象之间怎样协作和分配职责的模式。
按范围可分为:基于类的模式,基于对象的模式。
- 类模式:通过对象所属类之间的静态集成确定对象之间的关系,在编译时已确定;
- 对象模式:处理对象之间的关系,运行时可变化,具有动态性。(通过抽象类、接口实现)
- 大部分设计模式属于对象模式,少量为类模式。
适配器模型
- 名称:适配器(Adapter)问题:
- 需要使用的类,其接口不符合客户类要求。
- 解决:使用一个适配器类实现客户类要求的接口,同时又继承使用类或者聚集使用类。
- 背后的动机:可扩展性。即增加新的使用类,不需要改动客户类。
- 解决:通过适配器对象,将外部构件的原有接口转化为系统需要的接口
- 适配器(Adapter):使用接口和多态,对外部构件中的不同API增加一层间接性。(依赖倒置原则)
组合模式
- 问题:如何能够像处理非组合对象一样,多态地处理一组对象或对象的组合结构?
- 解决方案:定义组合对象和原子对象的类,使它们实现同一接口。
- 关键点是定义一个组合类,它不但能实现新的组合要求,而且这个类本身要包含其它的原子规则对象,以此确保原有的同一接口维持不变,即组合类自身实现同一接口。
代理模式
- 问题:不希望或不可能直接访问真正的主题对象时,应该怎么办?
- 解决方案:通过代理对象增加一层间接性。代理对象实现与主题对象相同的接口,并且负责控制和增强对主题对象的访问。
- 代理模式有远程代理、重定向代理、虚代理、保护代理、智能引用等。它们的结构如下图:
行为模式
- 行为模式往往涉及到算法,关注对象间的职责分配。行为模式描述了对象或类之间的通信模式,即联系方式。
- 行为模式包括观察者、模板方法、命令、策略、访问者、中介、状态、迭代器等。
模板方法模式
- 问题:当一个算法的基本步骤确定,但每一步的操作可以扩展时,如果用不同的类表示算法的不同扩展,如何避免相同部分代码的重复?
- 解决方案:将算法的不变部分用基类实现。基类方法中定义算法的步骤,将不同步骤用钩子方法表示,并在算法中调用;让子类复用基类代码,仅重写(覆盖)基类定义的钩子方法就可以扩展算法类。
策略模式
- 问题:完成某一功能有不同的方法(如不同的算法)。如何使程序可以在执行中动态改变所使用的方法?
- 解决方案:使用类封装功能的实现方法(算法),为实现功能的算法设置公用接口。上下文类仅依赖公用接口。不同的算法实例就可以在运行中相互替换。
观察者模式
- 问题:一个对象(被观察者)状态的变化,往往要影响多个依赖它的对象(观察者)。如何实现被观察者与观察者之间的松耦合,并在被观察者状态改变时通知观察者?
- 解决方案:定义被观察对象状态变化观察者接口,在被观察对象中包含观察者列表;观察者必须实现观察者接口,并加入到被观察对象的观察者列表中;当被观察对象的状态改变时,逐个调用观察者列表中各观察者的方法,以通知这些观察者对象。
访问者模式
- 问题:在软件中,由一些对象形成的结构(集合体)含有需要统一处理的数据。各个对象是不同类型的实例,具有不同的操作。如果将统一处理数据的功能包含在各个类型中,会造成代码重复,也失去灵活性(当处理方法改变时,所有类型代码都要修改)。如何避免此种情况?
- 解决方案:将统一数据处理功能用依赖对象类型的专门类实现;在对象类型中,依赖数据处理类。(双向依赖)
状态模式
- 问题:软件中,有些功能的执行依赖对象的某个状态。面向过程的程序语言代码中,会使用多分支转移语句。在面向对象的程序中,要避免这种丑陋的代码。
- 解决方案:将功能所依赖的对象的每一个状态,抽象为一个类。每个状态类中,含有各种功能的操作。在需要实现功能的客户代码中,直接调用上下文中目前状态的操作,由多态性决定具体执行的功能,不再需要使用分支转移语句。
装饰模型
-
名称:Decorator
-
问题:希望动态地为对象(非整个类)添加功能。不应使用继承的方式静态地扩展类。
-
解决:通过装饰类聚集需要装饰的对象,同时给客户类提供与被装饰类相同的接口。
-
类图:
-
一个装饰类可以对一组有相同接口的被装饰类进行装饰,这避免为了增加少量新功能,就建很多被装饰类的子类。
-
当需要对被装饰类做不同装饰时,可以添加不同的装饰类。
工厂方法模式
- 问题:在一个框架中,需要创建所用对象时,如果对象是与(基于此框架开发的)具体应用有关的,框架中只能定义该对象的抽象基类,当然不能创建该抽象基类的实例。如何在框架中将创建与具体应用有关的对象的工作延缓到(基于框架的)具体应用中?
- 解决方案:在框架中使用工厂方法返回抽象基类的对象,让具体应用重写此工厂方法返回与应用有关的实际对象。
抽象工厂模式
- 问题:有些应用框架需要支持不同的产品系列。每一种产品系列含有多种产品,需要配套使用。如何保证使用不同产品系列时,所产生的系列产品相互配套?
- 解决方案:让生产产品的具体工厂与所要生产的产品系列对应。一个系列的产品使用一个具体工厂,生产该系列所有产品,保证配套。
单实例模式
- 问题:程序中,为了数据一致性或者节省资源,如何保证一个类只有一个实例存在?
- 解决方案:将类的构造方法不对外公开;增加一个对自己的引用(唯一实例);使用一个工厂方法返回唯一实例。
对象池模式
- 问题:有些对象的实例总数有限制,创建需要耗费较长时间。如何节省创建这类对象的时间?
- 解决方案:使用一个对象池类专门负责管理这种对象。客户类需要使用对象时,向对象池申请;客户类使用完毕,向对象池归还对象。对象池可以限制对象的数量,让希望获取对象使用的客户类排队等候;对象池也节省了创建对象的时间。
- 对象池类设计规则:
- 对象必须是可以重置的;
- 对象池应该有一个最小容量和一个最大容量
- 对象池在开始时应该含有已知最小数量的对象。
创建型模式总结
- 创建型模式是关于对象创建的模式工厂方法模式是为了解决在框架代码或者其他地方无法创建具体对象,需要将对象的创建推迟到框架的应用代码或者扩展代码中完成。
- 工厂方法模式因使用工厂方法而得名。叫虚构造器也很贴切。
- 抽象工厂模式解决需要使用的多种产品,在不同的系列中要配套出现和使用的问题。使用一个构造器类创建一个系列的多种产品。增加一个产品系列则添加一个构造器类。
- 单实例模式解决在应用中需要确保某个类只有一个实例的问题。确保一个类只有一个实例是为了节省资源或者保证数据的一致性和实现全局的共享。
- 对象池模式解决需要创建的对象比较耗时,同时往往又有数量限制(远程连接数,数据库连接数等)。对象池可以节省对象创建时间,同时也可以很好控制对象的总数。
设计模式总结
- 设计模式一般是为了得到某方面灵活性、可变性。使用设计模式的效果是想要某方面可以变化而不用重新设计。
- 依据要达到的变化性目的,可将设计模式分为创建型模式、结构型模式和行为模式。
- 从使用范围分,设计模式主要用于类就是类模式,通过继承关系实现,是静态的;主要用于对象,就是对象模式,通过关联或依赖实现,是动态的。
- 所学模式中,模板方法和工厂方法是类模式。适配器模式可以用类继承实现,也可以用关联实现。其他所有模式都是对象模式。
- 适配器:让不适用的接口变成可用;
- 装饰器:动态地为类添加功能;
- 组合:对树形结构中的简单类和复合类用统一的方法处理。
- 代理:代理对象隐藏了真实对象,实现了真实对象接口。他可以将功能委托给真实对象实现,也可以为真实对象提供附加功能;
- 模板方法:在基类中规定算法结构或步骤,子类扩展可变步骤;
- 观察者:允许一个对象将自己的状态变化通知依赖于其状态的对象;
- 策略:对算法进行封装,进而可以替换;
- 状态:用类封装状态,依赖于状态的对象与状态类相关联并可实现状态的转变;
- 访问者:通过双向依赖实现不同数据结构上不同的处理方法的双委派。
十三,软件架构概念与设计技术
- 软件架构涵盖关于软件系统组织的那一系列重要决策,包括组成系统的结构元素及其接口的选择;
- 按这些元素之间的协作规定的行为;这些结构和行为元素如何构成更大子系统;以及指导这种组织的架构风格。
- 软件架构还涉及功能,可用性,弹性,性能,重用,可理解性,经济和技术约束,权衡和审美问题。
与架构有关的概念
- 框架(Framework):框架是软件开发的基础,半成品。由具体的类库、组件等组成。
- 框架分层次:
- 基础框架,是我们开发应用的基础。如Java框架, .NET框架;
- 技术框架:提供某一技术问题解决方案。ORM框架,测试框架,依赖注入框架
- 系统框架,针对某一类应用。Ruby on Rail,SSM框架,ASP.NET MVC框架;
- 应用框架:特定应用领域的半成品,如用友ERP框架,OA框架,游戏引擎。
- 框架提高了软件的开发效率,也提高软件的质量。
- 框架与架构的比较:架构是软件高层设计方案,框架(特别是技术框架、应用框架)体现或实现了某种架构。
- 将架构中不变部分用代码实现,变化点、扩展点用接口定义,留给框架使用者实现,并提供便于实现的辅助工具,就得到框架。
- 参考架构:针对特定问题给出的可复用架构设计方案。参考架构只是设计方案(不含代码或组件),供解决类似问题时参考。
- 架构模式:解决架构问题的已被证实有效的解决方案。架构模式是架构设计经验的总结。针对某些问题,历史上形成了公认的有效解决办法,就是架构模式。
架构设计原则
- 架构设计,也称高层设计,总体设计
- 架构的重要性:
- 架构是软件的基础。基础不牢,地动山摇
- 如果不对架构详加考虑,可能会使软件不稳定、无法支持现有或未来的业务需求、或者难以在生产环境中部署或管理。
- 基本思想:迭代。架构设计也需要反复,螺旋式提升。
架构设计技术
- 输入、输出和设计步骤
- 输入:用例、使用场景、功能需求、非功能需求(包括性能、安全性、可靠性等质量属性)、技术约束、部署环境及其他约束;
- 输出:对架构重要的用例列表,需要特别关注的架构问题,满足设计过程中定义的需求和约束的候选架构方案。
- 步骤:循环往复的5步
- 识别架构目标;
- 识别关键场景;
- 建立应用概貌;
- 识别关键问题;
- 定义候选方案。
十四,逻辑架构、分布式系统与中介模式
架构模式概述
-
架构设计主要有:
- 逻辑架构:逻辑上如何划分包、子系统、层等;
- 物理架构:物理上如何分布系统的组件、进程;
- 针对特定问题的架构:交互,扩展,通信,事务处理
-
逻辑架构模式:分层模式,管道与过滤器模式等
-
物理架构模式:C/S架构,三层架构,B/S架构,面向服务的架构,中介模式等。
-
扩展模式:插件架构模式
-
交互模式:MVC模式,表现-抽象-控制模式
分层模式
- 解决方案:将系统在垂直方向上分为多层,高层利用低层,低层为高层提供服务;高层依赖低层,低层不依赖高层。
- 效果:分解复杂性;层与层之间低耦合,使得修改一层不会波及其他层;通过良好接口设计,可以实现层的替换。
C/S、B/S及三层架构
中介模式
-
解决方案:在客户和服务器之间,引入中介(Broker)。提供服务的一方将服务注册到中介;使用服务的一方将对服务的请求传给中介,由中介将请求发给相应的服务器,得到结果后再返回给使用服务的一方。服务使用者不需要清楚服务提供者的物理特性,仅仅通过逻辑名称就可以通信(提供服务的地方对客户是透明的),这减少了系统组件之间的依赖。
-
客户方代理代表了服务器,客户使用客户方代理就像使用服务器;服务器方代理代表了客户,服务器向服务器方代理传结果就像给客户传结果。
-
优点:
- 中介模式成功实现了客户和服务器上功能与通信的关注点分离;
- 服务用接口定义,客户仅依赖服务器接口,因此可以改变服务器实现而不影响客户实现
- 客户不需要知道服务的物理地址,只需要知道服务的逻辑名称就可以通过本地代理访问服务;
- 客户与服务器之间的通信依靠串行化的数据,不依赖具体的实现平台,可以实现异构系统
- 此分布方式可以用于特别大型的系统
-
缺点:
- 每台计算机上的本地中介很关键,需要有容错性,否则会导致本机上的组件失效;
- 中介的间接性增加了通信的开销,系统性能会降低
- 中介可能成为性能的瓶颈,需要保证其数据传输吞吐率
- 由于代理依赖中介,如果中介更换或改变,代理也要改变。
十五,SOA模式
-
主要诉求:松耦合,跨平台,易重用,易扩展
-
采用分布式系统架构。系统由服务提供者、服务使用者和服务目录三种角色构成;
-
-
对服务的具体要求:
- 分布。在网络上提供服务。
- 组件形式。服务是一个组件,相对独立和封闭。
- 无状态。服务中不保存某个会话(某次调用)数据,便于被很多并发用户共享。
- 松耦合。服务间相互无关联。服务使用者可以根据需要调用任何服务。
- 可替换。服务有标准接口,可以被替换。
- 位置透明。对于服务使用者,服务在哪里运行是透明的。
- 平台无关。服务和服务的使用者、不同服务可以基于不同的软硬件平台。
- 服务有标准的接口定义。使用者按照接口定义使用服务。服务的内部实现被隐藏。
- 服务目录(服务注册或服务中介)。服务一般在目录中注册,便于发现。
-
SOA架构主要优点有:
- 服务组件封装了一项业务功能,加强了业务与技术之间的结合。便于快速组建业务流程,修改业务流程,适应业务变化;
- 将系统功能分解成小的服务,实现了模块化
- 服务基于接口定义,大规模复用方便。接口不变时,可以替换服务的实现。
- 跨平台性。可继承遗留系统,保护以往投资。
-
SOA架构主要缺点有:
- 系统层次结构复杂;
- 需要使用多种协议,增加了通信开销;
- 对数据传输速率要求较高;
- 服务定义基于业务的细分。业务建模影响了架构的成败。
-
SOA是一种重要的分布式系统架构,在企业信息系统、政务平台等方面应用广泛,需要认真领会和实践。
十六,插件及MVC模式
插件模式
- 问题:为了保持运行稳定和尽可能长的使用年限,应用软件应该可以添加新的功能。如何在架构上保持应用软件可以扩展,但不需要修改已有功能(开闭原则)?
- 解决:应用软件应在未来可能发生变化或需要扩展的地方定义接口。现有实现已给出这些接口的当前实现。未来扩展或改变功能时重新实现这些接口,就可以直接使用,不需要修改已有功能。
- 参与者:应用程序;插件管理器;插件接口;插件。
- 优点:
- 实现关注点分离(每个插件都是独立的)
- 性能好
- 扩展已有软件,不需要了解已有软件代码;所开发的插件不依赖已有软件。
- 应用程序只加载需要用到的插件,可保持精简
- 降低了维护费用便于复杂软件的分解
- 插件可以形成多个并行的版本
- 插件可以单独测试
MVC模式
-
解决方案:将图形界面程序分成模型、视图、控制器三个模块。数据及数据处理由模型负责;视图显示数据;用户输入由控制器处理。三者实现了关注点分离。
-
模型不依赖控制器和视图,但视图依赖模型,控制器依赖视图与模型。视图可以监听模型的变化,控制器接收视图传入的用户的控制。
-
-
MVC架构的优点:
- 一个模型可以有多个视图。为模型创建新视图无须重写模型。模型数据变化时将通知视图刷新自己。
- 模型可复用。因模型独立于视图,故可将模型独立地移植到新的平台工作(如不同的数据库)而不影响视图和控制器。
- 可提高开发效率和质量。开发界面显示时仅需考虑如何布局;开发模型时,仅需考虑业务逻辑和数据维护。这样能分离关注点,提高开发效率和质量,且有利于开发的组织。
十七,架构设计文档化
架构因素
- 大部分是质量需求,其他部分可概括为约束。
- 对于约束,可以具体说明其度量值;
- 对于质量需求,可以用“质量场景”描述。
- 格式如:<刺激> <可度量的响应>
- 构因素描述了架构要解决的问题。针对这些问题的解决,实际上是一系列技术决策,构成了架构设计的主要内容。
架构文档
- 软件架构文档(SAD)描述有关架构的总体构想,包括架构设计的关键决策。从本质上讲,SAD是对架构性决策(如技术备忘录)的总结
- SAD由多种架构视图组成。架构视图即从特定视角观察系统架构所得到的概况。只强调关键信息或想法,忽略其它。每个视图反映系统架构的某个方面。
- 架构视图主要关注结构、模块性、基本构件和主要控制流等方面。架构视图也解释了采用各种架构技术的动机。
逻辑视图
- 最重要的层、子系统、包、框架、类、接口等的概念性组织。它们概括了主要软件元素的功能。
- 逻辑视图还使用交互图展示系统关键方面的重要用例实现场景。
进程视图
- 进程和线程。使用UML类图和交互图描述了它们的职责、协作以及分配给它们的逻辑元素(层、子系统、类等)。
部署视图
- 进程和构件在处理节点上的物理部署以及节点之间的网络配置。
数据视图
- 数据流、持久性数据模式、对象与持久性数据之间的模式映射,对象到数据库、存储过程以及触发器的映射机制。
- 使用UML类图可以描述数据模型,UML活动图可以描述数据流。
安全视图
- 概述了安全模式和架构中实施安全的控制点。
实现视图
- 实现视图展示实现模型。
- 实现模型包含源代码和可执行文件等。
- 实现视图应该说明软件提交物的关系,如何组织提交物,如何创建提交物等。
- 实现视图使用文字或UML包图和构件图描述。
开发视图
- 开发视图概括了开发者创建开发环境时需要知道的信息。例如所有的文件如何组织成目录,为什么这样组织?如何进行构建和冒烟测试(smoke test)即快速测试?如何使用版本控制系统等。
用例视图
- 用例视图概括了架构上最为重要的用例和它们的非功能性需求。
各种视图一般用UML图表示,再辅以文字说明设计的动机
十八,详细设计方法与文档
- 总体设计(架构设计)详细设计(软件设计)
实现用例的步骤一般为:
- 确定用例实现与用例之间的对应关系。
- 识别实现一个用例需要哪些对象参与。
- 识别参与用例实现的各对象,有何动态关联。
- 确定有动态关联的参与对象之间,相互发送怎样的消息,消息的先后顺序,并用交互图加以描述。
- 根据4.的结果,进一步得到类的设计(在静态设计基础上添加操作)。
与分析活动不同的是,详细设计要具体到可以用代码实现。这就要考虑实际使用的工具、架构、框架、组件等。