如将本文作为包老师课的知识框架作业的参考,请加以改动再进行提交,谢谢配合~
本文将软件体系结构的主要笔记整理出来,主要用于自己的复习和回顾。部分内容参考自:包尔老师的软件课程吧
软件体系结构 ≈ 软件设计
详细阐述软件工程的设计阶段
汇编语言assemble language - 因为非常复杂,只能写一些非常小的程序,比如汉诺塔
1970 - 汇编->C,即高级程序语言,从C语言开始能写比较大的程序,但没有库函数,很多时候需要从头写。从C语言开始,就有了程序设计的问题,出现了面向结构的设计理念。
1980 - C++,java开始发展。(有很多库函数,出现了面向对象的设计理念)
面向对象和结构化的区别:
结构化程序分析设计时,每个模块按照顺序实现功能。面向对象的特征则是封装,可以进行信息的隐藏,也提高了复用性和可维护性。
面向对象的设计思想,从80年代开始,一直沿用到现在。目前多数软件都是面向对象的。
1995 ->框架:已经设计好的工具,便于我们实现一般性的软件。
开发时,每个模块把需要补充的东西填进去,就可以运行。
组件与库函数的区别:库需要与程序绑定在一起,引入到程序里面,一起编译,才算是真正用起来;组件则是独立于程序,多数是网上的服务,编译自己的程序后,在运行时直接调用组件就可以了。程序对组件的依赖程度更低。
技术是便于我们开发程序,但没有便于我们设计程序。当新技术出现后,程序设计变得越来越重要了。
未来->Model-Driven Development: MDA
程序:比较小的软件;软件/系统:比较大的软件。
软件抽象程度越来越高,不需要考虑细节,软件的实现变得越来越简单,软件的设计就越来越复杂和重要。
机器语言->汇编语言->高级语言advanced language->应用程序框架
结构化设计->面向对象的程序设计->面向方面的编程aspect-oriented programming
考试可能出的简答题:为什么软件设计越来越重要?
程序或计算系统的软件体系结构是系统的一个或多个结构,它包括软件元素、这些元素的外部可见属性以及它们之间的关系。
体系结构是系统的组织结构。一个体系结构可以被递归地分解为通过接口、连接部件的关系和组装部件的约束进行交互的部件。通过接口交互的部件包括类、组件和子系统。
软件体系结构是系统的基本组织,体现在其组件、组件之间的相互关系和环境,以及控制其设计和发展的原则中。
软件体系结构的组成:
元素elements:函数、接口、程序、类模块、层、子系统、客户端/服务端
属性properties:提供服务、性能特征、纠错处理、共享资源的使用
关系relationship:元素的组合机制和风格样式
软件体系结构的定义:组件以及组件之间的关系。Compoents comprised in the system, and the relationships or interaction mechanisms of those components.(注意:定义中还应该加上属性)
组件:
负责某些职责的系统的逻辑和功能单元
在不同的场景中可能是不同的特定对象:模块,子系统,层,包,类
可以分成更小的组件单位
组件之间的连接是多种多样的:顺序/三角/层次等
属性:分为功能性属性和非功能性属性
框架:针对特定问题的可重用应用程序基础结构。有必要的基本组件和连接器的指定问题基于它开发的应用程序的上下文或环境。J2EE框架、net框架等。
更多利益相关者:最终用户、购买者、项目经理、系统工程师、软件开发者、软件架构师architect(注意:架构才是architectur)、运维工程师、其他开发人员
每个利益相关者对于软件的要求不同,关注的角度不同。如用户关注好用;购买者关注价格;项目经理关注任务分配;系统工程师(把软件系统安装在硬件上,架设服务器等)关注布设的简单程度、开发人员希望功能简单、架构师需要综合考虑、维护人员希望容易维护等等。
反馈循环:之前的系统开发成什么样,也会影响现在的系统开发成什么样。系统开发出来后,反而会对体系结构的影响因素有影响。
从技术上来说,是承上启下的作用,将需求转化成设计,又能指导实现
从组织上来说,为stakeholders提供了一个交流的平台,满足所有stakeholder的需求。
研究一些比UML更好的软件设计的描述语言
验证和评估的研究
ICSE:软件工程会议
模型是什么?
对现实世界的一个简化,一个缩小版;
模型提供了系统的一个蓝图;
模型可以是结构化的,也可以是动态的。
模型的作用:
模型帮助我们将系统形象化,或者将系统形象化;
模型为我们提供了一个模板,指导我们构建一个系统;
模型允许我们指定系统的结构或行为;
模型可以把我们的决定记录下来。
体系结构视图模型:软件设计的模型,使用工程制图的形式,因此称作软件体系结构视图模型。
Philippe Kruchten
问题:不同的stakeholder有不同的软件设计理念和需求。
解决方法:看人下菜碟,根据不同的人,给出多个不同的模型。不需要给出太多模型,只需要给出4+1模型。
脚本模型:直接描述软件的功能性需求,属于需求分析的模型,而不是软件设计的模型。因此不和设计的模型放在一起。但是设计是从需求来的,因此它属于“4+1”中的“1”。
逻辑模型:描述系统有哪些组件,组件如何连接。从脚本模型,功能性需求,设计出来的。是设计模型中最主要的,剩下三个都是从逻辑模型里来的,描述的是非功能需求。逻辑模型主要是给最终用户看的,最终用户关心系统的功能。
开发模型:便于开发时分配任务。是给项目经理和软件开发人员看的。
过程模型:描述系统性能,影响软件速度的因素,如创建进程、多数描述的是组件之间发送消息。给系统集成的人看的。
物理模型:描述系统如何布设在硬件上,系统如何与硬件进行交互的。给系统工程师看的,软件如何放在硬件之上。
example:
脚本模型:时序图scenarios view。使用用例图描述脚本模型比用时序图更加合适。
逻辑模型:将组件和组件之间的连接画出来。应该画的是类图,而这里画出的是包图,非常简略。
开发模型:将上述的四个子系统分成了两组,用户交互/数据发送和网络交互的两组。
过程模型:描述系统有多少个线程。
物理模型:描述了与硬件的交互。
非常不符合规范、简单的例子,细节的设计都不涉及到,但它确实是按照“4+1”模型的思路画出来的。这是为什么呢?
因为这只是告诉我们这些模型大概应该什么样,却没有告诉我们具体的规范。没有一个标准、协议,作一个行业的规范。而规范就是下面要说的UML。
UML出现的时间:1975-80年,80年后就成为了统一的一个标准
语言:用于交流。编程语言是人和计算机交流,统一建模语言是stakeholder之间交流。定义了基础元素和语法,把基础元素和语法结合起来,形成段落/程序/各种图。
基础元素modeling elements:类图中的类、接口、用例等
联系relationships:泛化、实现、联系(聚合和组合)、依赖
关系密切程度:依赖<聚合<联系<组合
UML图和4+1模型有什么关系?
diagram 一个UML图就是一个模型
经典的13种UML图
只讲9种:静态图5个,动态图4个
静态图:用例图、类图、对象图、组件图、
动态图:时序图、阶段图、活动图
用例图user case diagram
用于4+1的脚本模型。
类图class diagram
用于4+1的逻辑模型。
对象图object diagram
和类图的区别:一个类只有一个方框,一个类有多个对象,一个对象一个方框。用得不多,主要是作为类图的补充。
组件图component diagram
最后的可执行文件,包括一些动态链接库,html文件,它们之间的调用关系由组件图给出。用得也不多。
部署图deployment diagram
描述软件和硬件如何交互。软件布设到多少个服务器上。4+1中的物理模型一般就使用部署图。
时序图sequence diagram
最上面的方框:对象,而不是类
矩形的长条:对象的生命周期
中间的连线:对象之间发送的消息,通信
4+1中的过程模型一般使用时序图,过程与性能相关,性能与对象之间的通信相关。
协作图collaboration diagram
也是描述对象,对象之间怎么发消息,相当于一个压缩版的时序图。只是把顺序标注出来,没有按照时间来画,将细节去掉了。用得也不多,一般使用时序图。
状态图statechart diagram
整个系统有不同的状态,可以把不同状态之间的转化描述出来,用于特定的系统,比如系统有对外正常开放的状态和维护期间的状态;进程的内核有就绪、运行、挂起等状态。一般来说4+1模型也不用,除非系统的状态很多。
活动图activity diagram
系统的输入、中间的活动到输出,整个流程的图。4+1模型一般也不用。因为4+1一般是面向对象的设计,活动图针对的是结构化的设计。面向对象关注的是类,结构化关注的是一个个结构模块。
注:包图一般用于开发模型。
Rational’s 4+1模型
实际上就是换了个名字
设计模型-逻辑模型
实现模型-开发模型
交互模型-过程模型
部署模型-物理模型
用例视图-脚本模型
质量属性=非功能性需求
质量属性是什么?性能(高并发、大流量)、安全性(受到攻击、数据泄漏)、有效性(可以持续运行多少天)、可扩展性、可用性(用户友好性)等
软件质量属性与软件工程哪个环节最相关?需求分析。
在需求分析阶段,确定质量属性可能不是特别准确,需要把一部分工作放在设计阶段。多个质量属性,一个质量属性有多个描述。软件设计就需要满足质量属性的多方面的要求。
软件建模是对软件设计的描述,质量属性脚本就是质量属性的描述。
质量属性脚本 = 质量属性模型
模型、UML图、脚本其实都是模型,是设计、功能性需求、非功能性需求的建模。
外界输入 输入的源
软件的部分 运行的环境情况
系统的反馈 反馈的度量标准
案例 - 有效性的质量属性脚本:
输入:来了一个没有预料到的消息,进程正常运行
输出:继续运行,不受干扰
系统在不同场景下 做出什么样的具体的反应
可用性 → 具体描述1 → 质量属性脚本1
→具体描述2 → 质量属性脚本2
→具体描述3 → 质量属性脚本3
案例:软件在受到黑客攻击时,可以正常运行而没有任何停顿。
受到黑客攻击 → 进程/正常运行 → 可以正常运行而没有任何停顿
(输入的源)→(软件关注的点和运行环境)→ (响应的度量)
软件的一般性的质量属性脚本 general availability scenario
针对某一个质量属性,只有一个一般性的质量属性脚本(一对一),但可能有多个质量属性脚本(一对多)。
将某一个质量属性的所有质量属性脚本合起来,就是一般性的质量属性脚本。
吞吐量throughput:单位时间处理的任务量
响应时间:处理时等待的时间
死线deadline:必须在规定的时间内完成
请求的负载量request load:系统可以支持多少个请求
系统连接数connections:有多少个用户可以同时连接,和socket相关
连接量≠请求量,连接上以后可能不请求,因此请求量<=连接量
数据大小data size:处理的数据大小
部署deployment:系统可以同时部署在几台机器上使用
软件是否容易修改
受到黑客、病毒攻击后,系统是否能很好的应对
策略:
authentication验证:登录的用户名密码
authorization授权:不同用户有不同的角色等级
encryption加密:系统信息传输(内部或者交互)的过程中加密
不被欺骗non-repudiation:消息的发送方和接收方有身份的验证。比如pcp的握手
监听auditing:保存日志log,可以回滚到先前的状态
包含两个方面。可依赖性reliability:保证运行一段规定时间,不出问题;可恢复性recoverability:如果出了问题,需要多长时间可以恢复。恢复的时间越短,有效性越好。
质量属性之间是相互制约的,没法都达到最优,只能平衡。这与不能使所有利益干系人都完全满意是一样的。
使用一个比较大的组件(类),减少组件之间的通信,对性能有帮助,但降低了系统的可修改性。
引入数据拷贝,两个数据库,能保证系统的有效性。如果数据出问题,可以转到备份上,不会对用户的使用产生影响。但降低安全性,造成了数据泄露的可能。
增加系统的验证,提升系统的安全性。但降低系统的性能。
rigidity僵硬性:难以修改
软件修改(升级):1、 确定需要修改(增加)哪些功能;2、修改原有设计;3、在新设计的基础上,修改原有代码
比如:原有设计中,1、组件的依赖关系强烈,谁也离不开谁,这样增加或者修改组件就会很麻烦;2、或者只有一个组件,组件特别大,功能很多
fragility脆弱性:修改完容易崩溃。本质还是不好修改,修改完有问题。
immobility不可移动性:原有系统的组件不可对新的系统复用。本质和不和修改也很类似。
viscosity黏性:随着软件系统的升级修改,它的僵硬性、脆弱性和不可移动性都越来越强,就是黏性强。这里所说的是系统设计的黏性。还有一种是软件开发环境的黏性,就是设计、开发相关人员、项目经理等的人员工作效率不高。
needless complexity不必要的复杂性:本来应该是设计为简洁,容易理解,但设计出来很复杂
needless repetition不必要的重复性:重复设计
opacity不透明性:设计自己能看懂,别人看不懂。比如组件命名有问题
最重要的就是前三个
组件之间的依赖特别强:耦合高
组件特别大,包含功能特别多:聚合低
耦合coupling:组件两两之间的依赖关系。
聚合cohesion:组件本身功能数量就是聚合度,数量多,越发散,就是聚合低。功能少,关系密切,越接近,就是聚合高。
单一责任:对于任意一个组件,要高聚合(功能单一或几个相近的功能)
开放闭合:对扩展开放,对修改关闭
(单一和开闭是最重要最广泛性的原则)
里氏替换:针对子类父类(继承)的规定
依赖倒置:开闭原则的特殊情况
接口隔离原则:单一责任原则的特殊情况
单一责任原则:一个组件的功能越少越好,每个组件只有一个责任。
修改的影响范围变小,修改起来更加容易一些
每个类都只有一个函数,会使系统非常庞大而臃肿,会造成不必要的复杂性。要设计符合到什么程度,还要具体项目分析。
注:从设计原则开始,都是以类为例子来讲的,但设计原则也可以应用到包的设计、函数的设计,各个层次的设计都可以应用。
开放闭合原则:对扩展开放,对修改关闭。
到了修改函数的层次,就算是修改了。如果需要修改组件,要阅读代码,就是不好的设计。如果只需要增加组件,就是好的设计。
保证开闭原则的方法:
里式替换原则:父类的对象应该能被子类的对象替代而不产生问题(看父类的对象其实是子类对象)。
出现问题的原因是:子类重写了父类的一些方法,看着是父类对象,以为调用的是父类的方法,其实是子类对象,调用的是子类的方法。因此为了避免出现问题,设计的时候尽量不要让子类重写父类的非抽象的方法。
示例:定义一个长方形Rectangle,正方形Square继承它,此时我们使用LspTest类调用getArea()方法,我们设置了长10和宽5,但是正方形实例的输出结果和长方形实例的输出结构不一致,即子类的实例代替父类的实例,但是系统的行为发生了变化,这样我们就不能说正方形是长方形的子类,正方形继承长方形的继承关系并不是一个质量良好的继承关系。
在修改中,不再重写SetWidth和SetHeight两个函数,并在正方形类中写了一个新的函数SetEdge,使用这个函数同时设定长宽为边长的值。
类B继承类A时,尽量添加新方法,而非重写父类A的已经实现的方法,尽量少的重写,避免重写时发生的错误。
依赖倒置原则:一个类A依赖于另一个类B的时候,可以在A和B之间加入一个B的接口,让A依赖于这个接口,B实现这个接口。这样可以降低A和B的耦合性,所以依赖反转原则可以看作是特殊的开闭原则。
反转:从A依赖B,反转为B依赖B的接口。
接口分离原则:单一责任原则的子原则。如果一个接口对应好几个类,那么应该把这个接口分割为多个,每一个对应一个类。该原则可以保证接口的聚合性,所以可以看作是特殊的单一责任原则。
打包原则:一组特殊的设计原则,是关于包层面设计的原则,即已经完成了类层面的设计之后,怎样把类放到不同的包里面。
为什么要把类放在包里?在已经完成实现的阶段,可以复用,也可以在修改时直接把包拿出来改。在开发的阶段,从项目管理的角度,方便分配任务和开发,分配给一个人的类放在一个包里,每个人负责一个包,便于实现和维护。
在4+1模型中,开发模型使用包图,逻辑模型使用类图。
包聚合原则:关注的是类应该放在哪个包里。
可复用的类:功能有一般性,可以被复用。
不可复用的类:与业务相关,难以复用。
复用-发布等价原则:把可复用的类放到一组包中,把不可复用的类放到另一组包中。
共同复用原则:把一起复用的类放到同一个包中。
共同闭包原则:把将来会一起更新的类放到同一个包中。
一种情况下会被同时修改的一些类,就先把他们放在同一个包。另一种情况下被同时修改的类放到另一个包里。
前两条原则都是从复用角度进行分组的,第三条原则从维护角度出发,但实际上三者是相通的,即将依赖关系强的类分到一个包里,以此提高包的内聚降低包的耦合,便于后期的复用和维护等管理工作。
如果需要,就取交集。
包耦合原则:根据包之间的关系,对所打包的类进行调整。
早晚综合征
你是否有过这样的经历:工作了一整天,完成了一些工作,然后回到家,第二天早上却发现你的东西不再管用了?” 为什么它不工作? 因为有人比你待得晚改变了你所依赖的东西! 我称之为‘事后综合症’。
早晚综合征的两种解决方法:(可能会考一个简答题或选择题)
无环依赖原则:这样构建一个图,让每一个包对应一个图的节点,一个包依赖于另一个包就给相应的节点之间连一条有向边。因此,如果这个图有环,那么修改环上的任意一个包,都会影响到其它包,说明包之间的依赖关系比较高,需要想办法去掉图中的环。
两个解决方法:
在打包的时候减少包的耦合度,保证包的耦合度低,以减少早晚综合征。
稳定依赖原则:如果一个包被多个包依赖,并且它自身依赖于很少的包,那么它就是稳定的,不容易受到其他包影响;反之,它就是不稳定的,容易受到其他包影响。因此,如果一个包依赖于另一个包,那么前者应该相对不稳定,后者应该相对稳定,这样它们之间才不容易相互影响,也就是依赖关系才比较低。
依赖应该是从不稳定的包到稳定的包。被多个包依赖(不经常修改),且不依赖于其他的包(不受其他包修改的影响),就是稳定的。
量化:不稳定性的计算公式
Ce: Efferent Couplings出耦合度(包里有多少个类依赖于包外的类)
Ca: Afferent Couplings入耦合度(包外有多少个类依赖于包里的类)
I = Ce/ (Ca+ Ce): Instability不稳定性
I = 0表示高稳定性
I = 1表示低稳定性
Pc: Ca=3,Ce=1,I=1/4
Pa: Ca=0,Ce=2,I=1
Pb: Ca=0,Ce=1,I=1
Pd: Ca=1,Ce=0,I=0
如果出现了不稳定的包依赖于稳定的包,有两种解决方法:(与无环依赖相同)
稳定抽象原则:如果一个包包含多个抽象类,它的抽象度就高;反之,它的抽象度就不高。如果一个包依赖于另一个包,并且依赖关系源自包里面类的继承关系,那么前者应该抽象度低,后者应该抽象度高,而根据稳定依赖原则,前者应该相对不稳定,后者应该相对稳定。因此,抽象度低的包应该是不稳定的,抽象度高的包应该是稳定的。
抽象包:跟包里抽象类的数量有关。如果数量多,这个包就是抽象的,如果数量少,这个包就是不抽象的。
Na: Number of abstract classes in the package包中抽象类的个数
Nc: Number of concrete classes in the package包中实体类的个数
A = Na/ (Na+ Nc): Abstractness抽象度
A = 1表示高抽象度
A = 0表示低抽象度
稳定抽象原则比较难调整,需要从设计之初修改。不会考如何调整。
体系结构风格≈概要设计
体系结构风格就是一些比较好的设计。比如客户端服务器风格。
和设计模式的区别:体系结构风格适用于概要设计的,设计模式是用于详细设计的。
常见的体系结构风格:(知道是什么就行了,不需要了解太多细节)
组件就是过滤器,组件的连接通过管道。数据输入到过滤器,过滤器进行处理,通过管道输入到下一个过滤器…最后通过管道输出。
管道过滤器风格的组件是过滤器,即处理数据的模块,组件的关系是管道,即传输数据的模块,该风格把数据经过滤器A处理后,通过管道A传输到过滤器B,然后再由过滤器B处理,以次类推。
可能不是线性的,是循环或者有分支。
存储器风格的组件是一个共享数据模块,以及多个客户端模块,多个客户端模块与共享数据模块相连,每一个客户端对共享数据的修改都对其他客户端可见。
有两个变种:
黑板风格Blackboard style:数据变化时,管理数据的组件会主动通知使用数据的组件变化。
存储风格Repository style:数据变化时,不会主动通知,只当使用的时候才会知道有数据变化。
组件是客户端模块和服务器模块,服务器模块负责数据存储,以及部分数据处理,客户端模块负责另一部分数据处理。
前面所述的存储器风格相当于特殊的客户端服务器风格,服务器模块只负责数据存储,而不负责数据处理。
有两种分类方法:
根据服务器模块的数量,分为两个变种:
两层客户端服务器Two Tier
三层客户端服务器Three Tier
根据客户端负责数据处理量的多少,分为两个变种:
本门课程是把CS风格分为了胖客户端和瘦客户端,而有的课则把客户端服务器风格分为CS风格(胖客户端)和BS风格(瘦客户端)。
组件是模型模块、视图模块、控制器模块,该风格与客户端服务器风格的关系是,该风格相当于把客户端模块划分为视图模块和控制器模块,而模型模块与客户端模块对应。
CS风格与MVC风格的对应关系:瘦客户端是S对应M,C应对V和C。胖客户端是S对应M,C对应V和C和M的一部分。
CS的可复用性和可修改性没那么强,因此可以把C对应V和C两个组件,增加可复用性和可修改性。
组件是层级模块,高层的模块依赖于低层的模块。
有两个变种:
组件是Peer即同伴模块,同伴模块之间是平等的,任何一个同伴模块既依赖于其他同伴模块,使用其他同伴模块提供的服务,又被其他同伴模块所依赖,为其他同伴模块提供服务。
组件是事件即消息的分发模块,以及消息的处理模块,消息到来之后,分发模块把消息发送给处理模块,处理模块进行处理。
有两个变种:
组件除了客户端模块、服务器模块,还有目录模块,目录模块记录网络上各个服务器模块的信息,为客户端模块的不同请求提供相应的服务器模块的地址,供客户端模块快速访问相应的服务器模块。
设计模式≈详细设计
设计模式是一些成熟的、可供设计师参考的详细设计的部分。
考试大概占30分,最主要的就是和大作业一样,给一些需求,根据需求来用设计模式设计出软件。即把需求和设计模式对应准确。这门课讲13个设计模式。
三种类别:
组件是工厂类,该模式把软件中负责创建对象的部分单独作为一个类,可以减少软件各个类之间整体的耦合度。
区别:对工厂方法模式来说,工厂类一次只创建一个类的一个对象;而对抽象工厂模式来说,工厂类一次可以创建多个类的一组对象。
案例:左边是工厂方法,右边是抽象工厂。
组建是单例类,该类只能创建一个对象,如果再次调用则不能创建对象,这可以满足软件某个类只有一个对象的要求。
两种方法:
组件是建造者类,有时软件需要把多个类的对象合并到一起形成一个复合类,比如需要把车轮、车身、发动机三个类的三个对象合并成一个汽车对象,那么该模式就把合并对象这一操作放到一个单独的建造者类之中,这样可以增加相关类的聚合度,降低相关类之间的耦合度。
简化类图:Director是使用建造者类的类,没有必要;Product是被创建对象的类,也没有必要;中间也没有必要继承。其实建造者模式关键就是有一个类来负责建造。
建造者类负责把已经创建好的对象整合到一起。
组件是适配器类,如果已有一个已经实现好的类,但是它的函数与定义好的接口即所需要使用的函数略有区别。
那么根据开闭原则,不应该修改这个已经实现好的类,而是要在类和接口之间加一个适配器类进行转换。
转换的方式分为两种:一是让适配器类继承已经实现好的类,再让适配器类实现接口,二是让适配器类包含已经实现好类的对象,再让适配器类实现接口。
组件是组合类,该类包含一组函数,用于把该类的多个对象连接起来,构建对象树。
例子:composite类的addComponent()函数就是把对象连接起来的函数。而leaf叶子结点没有这个功能。
画类图的技巧:如果一个类和它实现的接口类/父类的所有的实现类/子类都有关联,那么这些关联关系可以都简化为这个类和它实现的接口类/父类的关联。(另一种情况:一个类和一个接口类/父类的所有实现类/子类相关联,那么这些关联关系都可以简化为这个类和这个接口类/父类的关联。)
组件是装饰器类和它的子类,当有时候需要给一个已经实现的类做一系列变化的时候,有时候又需要做另一系列变化的时候,可以使用装饰器类和它的子类,让装饰器类包含这个已经实现类的对象,也包含装饰器类自身的对象,让每一个子类包含一种变化。这样就可以使得子类1的对象包含子类2的对象,而子类2的对象又包含子类3的对象,以此类推,最后子类n包含已经实现类的对象,即给已经实现的类先做了子类n的变化,又做了子类n-1的变化,以此类推,最后做了子类1的变化。
装饰器模式:解决了用户界面做一定修饰的问题。而且是多种样式的修饰,而且最后的结果是多种修饰的组合。
当要实现多种修饰的组合的时候,第二种装饰的类要被第一种装饰的类所用,即第二种装饰的类要和第一种装饰的类相关联,也可以反过来。
组件是外观类,某个模块里面的类和外面的类交互比较复杂的时候,为了简单起见,可以给模块增加一个外观类,让里面的类和外面的类通过这个外观类进行交互。
组件是代理类,该类包含一个已经实现类的对象,并作一种变化。从结构上来说,代理模式可以看作是简单的装饰器模式,但是后者偏重于对象外观的装饰,而前者偏重于对象展示方式访问权限等非外观的装饰。
问题:希望给一个类增加一些功能,而不影响该类的调用。
在原有的基础上增加了一个新的类,和旧的类有关联关系。新的类是旧的类的代理。
新的类:
应用:安全性、图片加载(先展示一个不清晰的图,再一点一点加载出清晰的图)
注意代理模式和装饰器模式的区别
组件是数据处理类,当需要根据数据的不同使用完全不同方法处理数据的时候,可以把每一种方法放到一个数据处理类里面,然后串联这些数据处理类。这样数据先输入第一个数据处理类,如果该类能够处理,则处理完毕,如果不能处理,则把该数据转发到下一个数据处理类,以次类推。
管道-过滤器模式和责任链模式的区别:管道-过滤器模式是每个过滤器都要处理数据,而责任链模式是看看能不能处理,哪个类能处理就哪个类处理。
组件是中介者类,当多个类之间的交互比较复杂的时候,可以让这些类不直接交互,而是通过中介者类交互,这样可以降低设计的复杂性。
中介者模式和外观模式的区别:后者关注的是模块内外类的交互,而前者是任意类的交互。
外观模式是结构模式而中介者模式是行为模式的原因:外观模式是子系统结构相关的。中介者模式是和类的交互相关的。
中介者模式和代理模式的区别:名字的区别。中介和代理可能有歧义。
组件是观察者类和被观察类,如果需要被观察类的对象变化能够引起观察者类的对象变化,那么就需要在观察者类中引用被观察类的对象,以获得变化,也需要在被观察类中引用观察者类的对象,以通知发生了变化。
画类图时,要把里面的函数写出来,才能体现观察和被观察的关系。
关键的函数:
Subject被观察者的getState()得到被观察者的状态。Notify()是通知观察者。
Observer观察者的函数update()。
问题:一个系统有多个状态,每个状态都有相应的比较复杂的操作,应该如何设计这个系统?
组件是状态类,当某个类在不同状态下,需要有不同的数据处理方式时,可以把每一种处理方式放到一个新的类中,这样便于增加或更新状态和相应的处理方式。
为每个状态增加一个状态类,直接调用某个状态对应状态类的对象的操作。
组件是策略类,当某个类在不同输入数据下,需要有不同但相似的数据处理方式时,可以把每一种处理方式放到一个新的类中,这样便于增加或更新数据和处理方式,然后让这些类继承自一个统一的父类以供调用。该模式与状态模式相似,都为不同的数据处理方式增加了新的类,但是状态模式是基于状态的;该类与责任链模式也相似,把每一种数据处理方式放到一个类里面,但是责任链模式是数据处理方式差别很大时所使用的。
判断的不是状态,而是输入数据的类型。针对数据类型,构建策略类,和状态模式类似。
工厂方法模式/抽象工厂模式:需求里有与创建对象相关,除了单例模式之外的。
单例模式:保证类只创建一个对象。
建造者模式:把几个对象组合在一起,构造一个复合对象。(不要和组合模式弄混)
适配器模式:有接口和类,接口和类不匹配。加一个适配器类。
组合模式:有一个类要创建多个对象,要构建对象之间的连接关系,比如树形连接关系。
装饰器模式:需求里有与外观相关的,比如界面和图形,就是装饰器模式。在图形的基础上增加一些新的样式,增加的外观可能要进行组合。(不要和代理模式弄混)
外观模式:有子系统内外消息的通信比较乱。(如果类之间的通信很乱,可能是外观模式或中介者模式。而外观模式就是涉及到子系统的)
代理模式:增加的是与外观无关的,比如功能上的关系,增加的功能不会有相互组合。图片加载展示或安全性的。
责任链模式:体系结构风格的题就是管道-过滤器风格,设计模式就是责任链模式。有不同的输入类型,先给第一个类看能不能处理,然后给第二个类看能不能处理……
中介者模式:没有子系统内外消息的通信比较乱。
观察者模式:系统一个类的变化,另外一个类也会进行相应的变化。
状态模式:系统有不同的状态,不同的状态有不同的操作。
策略模式:有不同的输入类型,不同的功能来处理。(不要和责任链模式弄混)
当一个公司需要开发一系列不同但是相似的软件的时候,并不是单独开发的,而是开发这些软件的核心部分,然后在核心部分的基础上补充其他部分,来构成不同软件,这样可以很大程度上节约成本提高开发效率。因此,公司需要有核心部分开发组开发核心部分、产品开发组开发其他部分并构成软件,以及管理组协调核心部分开发组和产品开发组之间的关系。
软件产品线是一系列的软件系统,有共同点也有区别。
一系列产品共有的部分可以复用,随着产品数量增多,开发新的产品花的时间变少。
三大开发组:
软件产品线体系结构是对上述一系列软件核心部分的设计,既然是对核心部分的设计,那么它是没有包含对核心部分之外的其他部分的设计的,所以需要针对具体的软件来具体补充。
正常的软件体系结构是一个整体,像一块完整的蛋糕,而软件产品线的核心部分的体系结构是不完整的,像一块有洞洞的奶酪。只有共有的部分,留下了需要具体问题具体分析的地方。