软件建模、设计模式、重构
软件建模
软件建模体现了软件设计的思想,在需求和实现之间架起了一座桥梁,通过模型指导软件系统的具体实现。模型并不是软件系统的一个完备表示,而是所研究的系统的一种抽象。软件建模通过不同的视角去描述一个系统。
软件建模视角:外部视角,交互视角,结构化视角,行为视角。
软件建模方法:结构化方法,面向对象方法,基于构建方法,面向服务方法,面向方面方法,模型驱动方法,形式化方法。
UML特点:面向对象,可视化,表达能力强,独立于过程,独立于程序设计语言,易于掌握使用。
UML内容组成:
事务:结构事务,行为事务,组织事务,辅助事务。
关系:关联,依赖,泛化,实现。
图:静态图(用例图,类图,对象图,组件图,部署图),动态图(顺序图,合作图,状态图,活动图)。
UML 4+1视图:
逻辑视图:类图,对象图。
实现视图:组件图。
部署视图:部署图。
行为视图:顺序图,合作图,状态图,活动图。
用户视图:用例图。
UML在软件开发流程中的应用:
需求分析:用例图。
概要设计,详细设计:类图,对象图,合作图,顺序图,状态图,活动图。
编码阶段:无
测试:类图,组件图,部署图。
视角和类图:
交互化视角:用例图。
结构化视角:类图。
行为视角:顺序图,活动图,状态图。
用例图包含关系:
Include:包含也可以叫Use,表示本用例会用到被包含的其他用例,被包含的用例是可以被重用的。
Extend:扩展用例是可选的,在特定场景下可以补充基础用例,降低基础用例的复杂性。
Generalization:泛化体现了父子关系,表明存在派生和继承。
类图包含关系:
实现(Realization):是一种类与接口的关系,表示类是接口所有特征和行为的实现。例如:借阅者能查找书籍。
依赖(Dependency):是一种使用的关系,有单向依赖和双向依赖,但避免使用双向依赖。例如:借阅者查找结果依赖标题。
泛化(Generalization):是一种继承关系,指定了子类继承父类的所有特征和行为。例如:借阅者是老师或者学生。
关联(Association):是一种拥有的关系,它使一个类知道另一个类的属性和方法。例如:借阅者的借阅记录或预约记录。
聚合(Aggregation):是整体与个体的关系,可以理解成把个体聚集在一起。例如:借阅者的多条借阅记录。
组合(Composition):是整体与局部的关系,整体的对象负责代表局部的对象的生命周期,可以理解成整体是由局部组成的。例如:借阅记录必须要有相关书籍信息。
类图关系连线的箭头,指向内容和范围较小的类
各种关系的强弱顺序:泛化=实现>组合>聚合>关联>依赖
用例图描述系统在干什么。动态模型是描述系统的功能是如何完成的,用顺序图、活动图和状态图从不同的角度来描述对象和对象之间的交互。
动态图作用和场景:
顺序图:强调消息时间顺序的交互图。描述复杂的多对象间交互。并发、分支过多的场景会影响可理解性。
活动图:用于对目标对象计算流程和工作流程建模。描述涉及到复杂的活动步骤的用例。特别并发、分支等场景。
状态图:描述了系统元素的状态条件和响应,它反映了类的对象可能具有的状态,以及引起状态变化的事件。涉及到复杂的状态变化的场景,也适用于并发场景,如网络连接的会话状态等。状态图仅用于具有下列特点的类:具有若干个确定的状态,类的行为在这些状态下会受到影响变为其他状态。
设计模式
所有的设计原则和设计模式都是为了更容易的实现高内聚低耦合。
正交四原则:
-
最小化重复。
-
分离变化。
-
缩小依赖范围。
-
向稳定方向依赖。
SOLID原则.
-
单一职责SRP,一个类,引起它变化的原因只有一个。如果一个变化,导致多处修改,则存在重复。
-
开闭原则OCP,对扩展开放,对修改关闭。多个变化导致一处修改,则对变化的识别不准,容易考虑不周,修改引入。
-
里氏替换原则LSP,基类设定一系列的规范和契约,子类需要遵守。
-
接口分离原则ISP,客户只需要了解必须了解的。不要暴漏的细节。
-
依赖倒置原则DIP,接口为稳定的契约,双方都依赖于抽象,不依赖于具体实现;上层定义接口,下层实现接口。
23种设计模式:
创建型:
- Factory Method(工厂方法):隔离创建对象的细节,使得创建对象的行为可扩展
- Abstract Factory(抽象工厂):该模式抽象出创建一组相关对象的接口,其中每个方法即为factory method
- Builder(建造者):与factory不同的是,该模式包含了对象构造的若干过程,因此天然地与template结合
- Prototype(原型): 用于以某个对象为模子创建一个新对象的场景,例如幻灯片中的母版与普通页、对象的克隆
- Singleton(单例):
结构型:
- Adapter Class/Object(适配器):处理遗留系统的不二法宝,也可以用空方法实现接口作为抽象父类
- Bridge(桥接): 使用关联代替继承,解决类多维度的扩展导致的类爆炸的问题
- Composite(组合):将组件组装为整体使用
- Decorator(装饰):常见于各种wrapper,常用于在原函数执行前后做一些额外的工作
- Facade(外观):封装扇出,利用树状结构减少调用者的复杂度
- Flyweight(享元):复用变化少的对象
- Proxy(代理):是原对象的一个完整的替代品
行为型:
- Interpreter(解释器):一般用于解释执行自定义的某种语法
- Template Method(模板方法):框架与钩子
- Chain of Responsibility(责任链):一组对象按照既定的顺序关联起来,依次处理请求,其中任一对象都有权停止调用传递
- Command(命令): 将行为抽象和解耦
- Iterator(迭代器):封装数据的访问行为(顺序、可见性等)
- Mediator(中介者):用一个中介对象来封装一系列的交互;新增一个模块处理两个模块的交互
- Memento(备忘录):将当前对象的状态信息保存为另一个对象,使得当前对象可以基于状态镜像快速恢复原状态
- Observer(观察者): 订阅/发布模型,用于事件驱动的设计
- State(状态):封装FSM(有限状态机)的状态与状态迁移,每个状态定义了自身的输入与状态迁移
- Strategy(策略):使用接口即使用strategy,用于隔离变化
- Visitor(访问者):数据与行为分离方法。通过这种分离,可达到一个被访问者动态添加新的操作而无需做其他的修改的效果
工厂方法设计模式使用场景:
1:当子类型可能会有很多,以后需要不断增加不同的子类实现时;
2:当一个系统尚在框架设计阶段,还不知道将来需要实例化哪些具体类时;
3:系统设计之初不需要具体对象的概念(或者说没有具体对象的概念);
4:在工厂方法模式中,客户端不需要知道具体产品类的类名,只需要知道所对应的工厂即可,具体的产品对象由具体工厂类创建;
5:在工厂方法模式中,对于抽象工厂类只需要提供一个创建产品的接口,而由其子类来确定具体要创建的对象,利用面向对象的多态性和里氏代换原则,在程序运行时,子类对象将覆盖父类对象,从而使得系统更容易扩展。
抽象工厂设计模式使用场景:
1:创建产品家族,相关产品集合在一起使用的时候;
2:想要提供一个产品类库,并只想显示其接口而不是实现时;
3:通过组合的方式使用工厂时;
4:系统中有多于一个的产品族,而每次只使用其中某一产品族。可以通过配置文件等方式来使得用户可以动态改变产品族,也可以很方便地增加新的产品族;
5:属于同一个产品族的产品将在一起使用,这一约束必须在系统的设计中体现出来。同一个产品族中的产品可以是没有任何关系的对象,但是它们都具有一些共同的约束,如同一操作;
6:系统下的按钮和文本框,按钮与文本框之间没有直接关系,但它们都是属于某一操作系统的,此时具有一个共同的约束条件:操作系统的类型;
7:产品等级结构稳定,设计完成之后,不会向系统中增加新的产品等级结构或者删除已有的产品等级结构。
适配器模式:
场景:现有类的接口不满足需求时。复用现有类,并使不兼容的类一起协同工作。复用现有的子类,通过对象适配器来适配父类接口
优点:提升现有类的复用性。提升系统的扩展性。可避免对现有代码的修改
缺点:过度使用适配器,会引入较多零散的类,导致系统变得凌乱
外观模式:
场景:为一个复杂的系统对外提供一个统一的接口。对子系统进行分层,并简化层次间的接口依赖
优点:降低系统间及系统与外部的耦合度,提升安全性。简化了系统的接口,更易于使用系统
缺点:对统一接口的使用可能会引入较多约束,降低了使用系统的灵活性。不易于修改,子系统的变动可能会引起外观层甚至客户端的改动,违背开闭原则
适配器模式与外观模式对比:
1:二者都是对现有接口进行转换以满足需求
2:适配器模式是将现有接口转变成需要的接口,外观模式是为各子系统的提供一个统一接口,简化系统对外接口
观察者模式:
场景:对象间存在一对多关系,一个对象的状态发生改变会影响其他对象。当一个抽象模型有两个方面,其中一个方面依赖于另一方面时,可将这二者封装在独立的对象中以使它们可以各自独立地改变和复用。
优点:降低了目标与观察者之间的耦合关系,两者之间是抽象耦合关系。目标与观察者之间建立了一套触发机制。
责任链模式:
场景:有多个对象可以处理一个请求,哪个对象处理该请求由运行时刻自动确定。可动态指定一组对象处理请求,或添加新的处理者。在不明确指定请求处理者的情况下,向多个处理者中的一个提交请求。
优点:降低了对象之间的耦合度。发送者和接收者也无须拥有对方的明确信息。增强了系统的可扩展性。可以根据需要增加新的请求处理类,满足开闭原则。增强了给对象指派职责的灵活性。可以动态地改变责任链的顺序,也可动态地新增或者删除责任。责任链简化了对象之间的连接。每个对象只需保持一个指向其后继者的引用。责任分担。每个类只需要处理自己该处理的工作,符合类的单一职责原则。
设计模式小结:
1:适配器模式将某个类的接口转换成客户端期望的另一个接口,消除由于接口不匹配所造成的类的兼容性问题;外观模式为一个复杂的系统对外提供一个统一的接口,将复杂屏蔽在内部,让客户端更易于使用。
2:观察者模式实现了依赖倒置,让数据持有者不再去关注数据消费者;责任链模式将可能新增的处理行为以链条的方式串联起来,为使用者提供了新增行为和排布行为间的先后顺序的扩展机制。
3:行为型模式用于描述多个类或对象之间,怎样相互协作共同完成单个对象都无法单独完成的任务,它涉及算法与对象间职责的分配。能体现出程序在运行时复杂的流程控制。
重构
所谓重构(refactoring) 是这样一个过程: 在不改变代码外在行为的前提下, 对代码做出修改, 以改进程序的内部结构。
产品存量问题:
1:难以添加新特性。
2:存量代码难以理解。
3:难以保证产品的质量。
4:接口混乱。
5:简单堆砌式修改。
6:系统资源不足。
7:员工工作疲惫。
重构的好处:
1:改进软件设计。
2:使软件更容易被理解。
3:帮助你找到bugs。
4:助你提高编程效率。
Program SMaRT:performance高效,portable可移植,security安全,maintainable可维护,readable可读,reliability可靠,testable可测试。
重构等级要求:
L0:功能正确。功能点验收,自动化测试。
L1:无风险漏洞。门禁,静态检查,CodeDex,安全编码规范,CQI。
L2:代码整洁。通用编码规范,CodeStyle,CMetircs。
L3:架构敏捷。持续重构,架构可视化,SAI。
L4:持续演进。
重构场景对人的要求有不同:
1:小颗粒重构(函数级,小模块级),以开发人员为主,committer审核辅导。
2:大颗粒重构(架构级,大模块级),业务专家,软件专家。
重构时机:
1:事不过三。
2:添加功能时,让未来增加新特性时更加快速,更流畅。
3:修补错误时,因代码不够清晰而无法一目了然地发现错误。
4:复审代码时:经验传递,改善设计。
什么时候不适合做重构?
1:代码太混乱,设计完全错误,与其重构,还不如重新开始(重写)。
2:明天就是DeadLine,永远不要做“最后一分钟的修改”
3:重构工作量显著的影响估计,一个任务估计是3天,如果为了重构,需要更多时间(2天或更多)
4:没有任何更好的思路时不可重构。
两顶帽子:添加功能和重构,一次只做一件事。
1:添加新功能时,你不应该修改既有代码,只管添加新功能
2:重构时,你就不能再添加功能,只管改进程序结构
重构流程:
1:深入业务:挖掘问题,深入代码。
2:重构设计:领域建模,重构规划,兼容方案。
3:重构实施:工程基线,代码重构。
4:持续演进:模型演进,人员赋能。
重构手法
重构手法概述:抽取方法,内联方法,抽取类,重命名,移动方法。
十六字真言:旧的不变,新的创建,一步切换,旧的再见。
简化语句:
1:合并条件表达式。
2:移动语句。
3:以卫语句取代嵌套条件表达式。
重组函数:
1:将查询函数和修改函数分离
2:已明确函数取代参数
3:提炼函数:提取功能,将部分代码提取到独立函数,方便复用和维护,提升可读性,提炼到一个独立的函数中, 并以这段代码的用途为这个函数命名,方便理解。
4:内联函数:消除多余的间接性,将函数和被调用的代码合并了,方便理解,重新组织函数的过程中,把多个函数代码内联到一个函数中,方便后续重构。
重组数据:
1:拆分变量:如果变量被多次赋值,且赋值的意义不同,意味着它们在函数中承担了多个职责,容易造成理解困难和修改出错,应该根据不同的职责被拆分成不同的变量,每个变量只承担一个职责。
2:提炼类:单一职责,将不同职责的代码提取到独立的类(模块)。重新组织类,将多个类合并到一起,以便按照其他方向进一步拆分。
3:内联类:消除多余的间接性,消除不在有独立职责的类。重新组织类,将多个类合并到一起,以便按照其他方向进一步拆分。
4:类继承体系重构手法系列
a:函数上移(反 函数下移)。字段上移(反 字段下移)。移除子类(反 以子类取代类型码)。
b:提炼超类,折叠继承体系,以委托取代超类,以委托取代子类。
系统级重构:
拆解后带来的收益:业务复杂度,需求变化频率,平均日活。
拆解中的成本:系统集成关系,数据迁移量,代码改动量。