常用的软件设计方法
1、设计启发
要点
描述
面向对象SOLID原则(设计模式六大原则)
Single Responsibility Principle:单一职责原则Open Closed Principle:开闭原则Liskov Substitution Principle:里氏替换原则Law of Demeter:迪米特法则Interface Segregation Principle:接口隔离原则Dependence Inversion Principle:依赖倒置原则
DRY原则(Dont’t Repeat yourself)
KISS原则(Keep it Simple, Stupid)
面向接口编程
Yagni(You Aren’t Gonna Need It)
避免过度设计,良好的设计应该是编写出可维护的代码《修改代码的艺术》,不断的重构优化得到的。《重构-改善既有代码设计》
高内聚,低耦合
探寻通用设计模式
相关网站:设计模式
软件设计读物
《领域驱动设计》,《UNIX编程艺术》
保持设计模块化
单一职责的提现业务模块化、功能模块化(包图,C4模型图,用例图) --> lib包 --> 微服务
考虑不同的软件架构
分层架构、事件驱动架构、微核架构、微服务架构、云架构(软件架构入门)
1.1、SOLID
SOLID原则
1.1.1、单一职责原则
一个类或一个模块应该只有一个,而且只有一个要改变的理由。
例子1
123456789101112131415class User{ void CreatePost(Database db, string postMessage) { try { db.Add(postMessage); } catch (Exception ex) { db.LogError("An error occured: ", ex.ToString()); File.WriteAllText("\LocalErrors.txt", ex.ToString()); } }}
改为
12345678910111213141516171819202122232425class Post{ private ErrorLogger errorLogger = new ErrorLogger(); void CreatePost(Database db, string postMessage) { try { db.Add(postMessage); } catch (Exception ex) { errorLogger.log(ex.ToString()) } }}class ErrorLogger{ void log(string error) { db.LogError("An error occured: ", error); File.WriteAllText("\LocalErrors.txt", error); }}
1.1.2、开闭原则
对扩展开放,对修改封闭。
例子1
1234567891011121314class Post{ void CreatePost(Database db, string postMessage) { if (postMessage.StartsWith("#")) { db.AddAsTag(postMessage); } else { db.Add(postMessage); } }}
改为
123456789101112131415class Post{ void CreatePost(Database db, string postMessage) { db.Add(postMessage); }}class TagPost : Post{ override void CreatePost(Database db, string postMessage) { db.AddAsTag(postMessage); }}
1.1.3、里氏替代原则
程序中的对象应该可以替换其子类型的实例,而不会改变该程序的正确性。
1.1.4、迪米特法则
Law of Demeter
目的:松散耦合;
只与你的直接朋友谈话;
别跟陌生人说话;
例子1
1objectA.getObjectB().getObjectC().doSomething();
改为:
1objectA.doSomething();
例子2
123456789101112public boolean isValidEmployee(Employee employee) { // Notice method chaining String primaryEmailAddress = employee.getEmail().getPrimaryEmailAddress(); // Notice method chaining long mobile = employee.getContactNumber().getMobile(); // some good conditions if (primaryEmailAddress != null && mobile != 0) { return true; } return false; }
改为
1234567891011public boolean isValidEmployee(Employee employee) { boolean isValidPrimaryEmailAddress = employee.isValidPrimaryEmailAddress(); boolean isValidMobile = employee.isValidMobile(); // some good conditions if (isValidPrimaryEmailAddress && isValidMobile) { return true; } return false; }
1.1.5、接口隔离原则
不应强制客户端依赖它不使用的方法。也就是说:不要通过添加新方法向现有的接口添加其他功能。
相反,创建一个新接口,让您的类在需要时实现多个接口。
12345678910interface IPost{ void CreatePost();}// 添加新方法之后,原来实现IPost的所有子类都需要实现新的方法了,没有做到接口隔离interface IPost{ void CreatePost(); void ReadPost();}
改为
123456789interface IPostCreate{ void CreatePost();}interface IPostRead{ void ReadPost();}
1.1.6、依赖倒置原则
依赖倒置是一种解耦软件模块的方法。通常使用依赖注入来实现。主要实现以下两点:
高级模块不应该依赖于低级模块。两者都应该取决于抽象。
抽象不应该依赖于细节。细节应取决于抽象。
依赖注入仅通过将类的构造函数作为输入参数“注入”类的任何依赖项来使用。这在Spring框架中很常见。
1.2、DRY原则
Software Design Principles DRY and KISS
简单来说,就是不要写重复代码。为了做到DRY,请将系统划分为多个部分,将您的代码和逻辑划分为更小的可重用单元,在需要的位置调用他。
不要写冗长的方法,而应该将它划分逻辑并尝试复用方法中的现有部分。
1.3、KISS原则
Software Design Principles DRY and KISS
KISS原则是描述性的,以使代码简单明了,易于理解。
每种方法应该只解决一个小问题,而不是很多用例。如果方法中有很多条件,请将它们分解为更小的方法。它不仅更易于阅读和维护,而且可以帮助更快地发现错误。
1.4、Yagni(You Aren’t Gonna Need It)
Yagni
这是一个声明,我们现在认为我们软件需要的某些功能现在不应该被开发出来,因为“你不需要它”。
这是避免过度设计的XP实践方法。
取而代之的,你需要考虑后续程序的扩展性,以及重构的难度,以便在需要时引入该功能。
2、设计实践
2.1、分而治之
没有人的头脑能大到装得下一个复杂的程序的全部细节,需要把程序分解为不同的关注区域,然后分别处理每一个区域。
为此可以考虑使用C4模型方法,从总体到细节进行设计。
总体设计工具
C4模型-系统上下文:梳理正在构建的软件,以及系统与用户及其他软件系统之间的关系;
UML-用例图:通过用例图收集系统的需求,识别系统的参与者;
UML-通信图:聚焦对象(参与者)之间的通信,梳理清晰对象之间的关系;
UML-活动图:为业务流程建立模型,梳理出待开发的业务流程;
…
详细设计工具
C4模型-容器图:将软件系统放大,设计组成该软件系统的容器(应用程序,数据服务,微服务等);
C4模型-组件图:将单个容器放大,以显示其中的组件,将这些组件映射到代码库中的真实抽象,类似于UML中的组件图;
C4模型-代码:如果有必要,可以继续放大组件,为该组件设计详细的类图;
UML-组件图:将系统分解为各种高级职能的组件,每个组件在整个系统中负责一个明确的目标,并且以特定的接口与其他系统要素进行交互;
UML-类图:类似于C4模型的代码,更加进一步描述系统的静态视图,显示静态视图元素之间的协作关系,方便更有效的使用面向对象语言构建软件应用程序;
UML-序列图:描述参与者和对象之间发生的晓旭的交互顺序;
…
2.2、自上而下的设计
因为人的大脑在同一时间只能集中关注一定量的细节,如果你从一般的类触发,一步步把他们分解为更具体的类,就不用被迫同时处理过多的细节。
一层一层的往下设计,知道你认为接下来写代码比继续分解更容易的时候。
2.3、自下而上的设计
有时候自上而下的设计会显得过于抽象,很难入手去做,这个时候可以采用该方法;
对这个系统该做的事情知道些什么,你可能会找出一些能够分配给具体类的底层的职责,然后在从顶上去观察系统就会明朗很多;
另一些情况下,设计的问题主要是由底层决定的,底层的技术细节、SDK、接口决定了你的设计的很大一部分,这个时候,也需要自下而上的进行设计了。
3、行业通用方案启发
不同的业务领域,业界或者有分享了一些比较成熟的技术方案,值得我们借鉴学习,我们在设计自己的方案的时候也需要总和考虑下。也许你需要引入新的技术思路来服务当下正在开展的业务。
对账
如何做一个对账系统
[支付系统的对账处理与设计](https://www.cnblogs.com/davidwang456/p/6408369.html)
…
秒杀系统
如何设计并实现一个秒杀系统?
淘宝大秒杀系统设计详解
或许别人的方案也会有很多缺陷,重点是你需要按照自己的需求和思路进行重新梳理和设计,输出完整的技术方案文档,让大家一起参与讨论。当把这个方案成功应用到项目中之后,那才是你自己积累的宝贵经验。
4、翻翻自己的软件设计百科指南
这部分一般为个人经验积累(包括业务经验和技术经验),越是经验丰富的开发者,设计的方案越完善。
分布式系统
数据完整性处理
考虑分布式锁
考虑消息可靠性
服务接口是否设计合理
是否考虑升级向后兼容
接口幂等处理
…
账户系统
关键数据是否脱敏
账户系统数据表设计模型
支付加解密是否安全
支付流程常见缺陷
支付密码设计
…
希望大家能及时更新自己的软件设计工具箱,让自己的工作经验真正的成为自己具有竞争力的的专业能力。
在做好了软件设计之后,对整个系统进行一次梳理,输出具体的系统架构图。方便更加清晰的说明:
当前设计的功能模块在整个系统中所处的位置;
当前设计的功能模块与其他系统部分的交互上下文;
当前系统组成的技术架构;
梳理完成之后,能进一步加强对系统业务、架构的全局认识。