今天谈谈开发中比较重要的东西:代码设计
进入正题:
先了解敏捷开发、业务、功能、模块、组件的概念:
敏捷开发:软件开发过程的本身的不可预见性,很多用户在项目开始时不可能对于这个项目有着一个完整而明确的预期。很多对软件的预期都在后期的修改和完善过程中产生。 特点:
1)计划赶不上变化,开发人员在开发初期无需做出很多文档
2)沟通多、简明、需要多反馈、维护困难(文档不足)
用户需求:描述的是用户的目标或用户要求系统能完成的任务。
业务需求:描述的是公司如何解决用户的问题,满足用户的欲望。公司目前经营的业务。
功能需求:公司产品实现的软件功能。
模块:针对的是开发人员,开发人员根据功能需求,将功能进一步划分为功能模块。
组件:在设计角度上看,组件是达到可复用要求的模块。
设计模式:
在此不过多阐述,有兴趣的可以查看这篇文章:
http://www.cnblogs.com/maowang1991/archive/2013/04/15/3023236.html
权威点的解释可以看菜鸟教程:http://www.runoob.com/design-pattern/design-pattern-tutorial.html
设计原则:
总结下可以从三个层面来记忆(注意界限,记忆上可以这么记,理解上则不行):代码层面、模块层面及类层面
(代码层面)开闭原则:对扩展开放,对修改闭合。
对扩展开放:这意味着模块的行为是可以扩展的。当应用的需求改变时,我们可以对模块进行扩展,使其具有满足那些改变的新行为。也就是说,我们可以改变模块的功能。
对修改闭合:对模块行为进行扩展时,不必改动模块的源代码。
(模块层面)依赖倒置原则:
A.高层次的模块不应该依赖于低层次的模块,他们都应该依赖于抽象。
B.抽象不应该依赖于具体实现,具体实现应该依赖于抽象。
进一步理解:高层次的模块可以理解成框架,即框架不应该依赖于具体业务,而是依赖于业务的抽象。抽象即对事物的总结,在平常开发中将产品的设计图总结出可实用的框架即是抽象的结果。
(类角度)类单一职责原则:
A.一个类只有一个引起这个类变化的原因。
B.一个类中应该是一组相关性很高的函数和数据的封装。
类单一原则应该比较好理解,可以扩展下:即一个类只完成一个功能,如果做不到一个类只完成一个功能,最少要保证一个方法只完成一个功能。反例(忘了哪里摘来的):T负责两个不同的职责:职责P1,职责P2。当由于职责P1需求发生改变而需要修改类T时,有可能会导致原本运行正常的职责P2功能发生故障。
(类角度)迪米特法则:
也叫“最少知道原则”,一个类尽量少的与其他类发生关系,每一个类对其他类都只有最少的联系,而且局限于与本类密切相关的类。
与“类单一原则”互补,“类单一原则”主内,“迪米特法则”主外,都是在阐述在类定义时应该注意的。
(类角度,抽象类)里氏替换原则:
任何基类可以出现的地方,子类一定可以出现。只有当衍生类可以替换掉基类,软件单位的功能不受到影响时,基类才能真正被复用,而衍生类也能够在基类的基础上增加新的行为。里氏代换原则是对“开-闭”原则的补充。实现“开-闭”原则的关键步骤就是抽象化。而基类与子类的继承关系就是抽象化的具体实现,所以里氏代换原则是对实现抽象化的具体步骤的规范。
(类角度,接口)接口隔离原则:
一个接口完成的功能尽可能的单一,不要让一个接口承担过多的责任。
这个原则主要是为了防止为了偷懒,直接使用一个接口,实现多个接口应该实现的方法。
最近一次技术分享,分享了代码设计。也有些新的点需要记录下。整体如下:
一、产品角度思考代码设计:
角色:用户、boss、产品经理、程序员、测试
一般产品演进路线:
用户:适合自己,越用越开心
boss:满足用户,持续产品迭代,产品演进是条线段
产品经理:围绕线段的曲线,可能中断(向程序员妥协)
程序员:围绕产品经理路线的曲线
测试:围绕程序员的bug展开话题
优化产品演进路线:
程序员:围绕产品展开架构,并及时提醒产品经理/boss按照产品的走向,纠正天马行空的思想
纠正方法论:开闭原则
二、开闭原则:
开闭原则(OCP)是面向对象设计中“可复用设计”的基石,是面向对象设计中最重要的原则之一,其它很多的设计原则都是实现开闭原则的一种手段。对于扩展是开放的,对于修改是关闭的,这意味着模块的行为是可以扩展的。当应用的需求改变时,我们可以对模块进行扩展,使其具有满足那些改变的新行为。
站在boss/产品经理角度:
新增功能应当不影响之前的代码;修改功能,可以接受修改周期变长。
问题:
明明是新增功能,却变成了修改功能:
原因1:开始方案设计没有到位,没有按照产品需求路线走
原因2:采用取巧的方式设计代码。
原因3:代码逻辑没有分干净,没有面向对象思维,类/模块之间耦合严重,脱离不开
原因4:因为一些(时间、懒)原因,强行在代码深处打上 if else来达到实现效果
问题处理:
针对原因3:(低耦合、高内聚)注意模块和功能的划分,严格控制模块间耦合问题。耦合在入口文件,除了入口文件,增删该模块不会对其它模块造成影响
针对原因4:架构持续演进,尽可能地适应可能到来的新需求变更。
三、业务横向拆分:模块拆分、功能拆分
拆分原则:
围绕产品(需求分析文档)划分模块,如果模块内置功能较多,则模块内部再次拆分。
注意事项:
1)不要单纯以界面划分作为模块分割的依据,界面只可以作为模块功能的需求
2)划分为业务模块(产品功能)和组件模块(非业务功能,可以脱离业务独立存在,可以被其它项目使用)
项目划分2D图
注意:模块内部层级,可以被一级模块直接划分;模块内部划分可以直接使用按照类型划分,或者按照MVP、MVVM等方式划
项目划分3D图
注意:站在不同角度,不同层级都可以表示整个项目,不同层级并没有必然的联系。上方被下方依赖。
四、纵向分层(依赖倒置、里氏替换原则):
依赖倒置原则:
A.高层次的模块不应该依赖于低层次的模块,他们都应该依赖于抽象。
B.抽象不应该依赖于具体实现,具体实现应该依赖于抽象。
里氏替换原则:
任何基类可以出现的地方,子类一定可以出现。只有当衍生类可以替换掉基类,软件单位的功能不受到影响时,基类才能真正被复用,而衍生类也能够在基类的基础上增加新的行为。里氏代换原则是对“开-闭”原则的补充。实现“开-闭”原则的关键步骤就是抽象化。而基类与子类的继承关系就是抽象化的具体实现,所以里氏代换原则是对实现抽象化的具体步骤的规范。
解释:
代码逻辑划分:抽象层/接口层/实现层 (EIT造型)
抽象层:是相对于可能的实现层来说,它是抽象层,Java实现可以是abstract/interface/class
接口层:连接抽象层/实现层,传达抽象层的命令(interface/abstract/class),代码中常见的有以实现类的父类作为接口。
实现层:当前要做的功能实现(class/abstract/interface)
注意业务层级和架构层级区别:
对业务层来说:抽象层是实现层的属性,是实现层要完成特定功能的“后台”。
举例:
1、普通的Listener
2、MainActivity/ActivityThread Activity
3、MVP
组件抽离:
按照其提供服务的对象,给予层级显示
综合:
1、设计软件模块和功能,需要贴合产品需求
2、(低耦合、高内聚)模块和功能(类、方法),独立。增删改模块或者功能,不会造成兄弟和父级模块或者功能不能使用
3、架构持续演进,时刻准备着可能到来的新需求
4、时刻保持面向对象的思想,尊重类、模块、层,维护好每一个对象的职责。