常用的软件设计方法

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class 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());
}
}
}

改为

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class 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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Post
{
void CreatePost(Database db, string postMessage)
{
if (postMessage.StartsWith("#"))
{
db.AddAsTag(postMessage);
}
else
{
db.Add(postMessage);
}
}
}

改为

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class 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

1
objectA.getObjectB().getObjectC().doSomething();

改为:

1
objectA.doSomething();

例子2

1
2
3
4
5
6
7
8
9
10
11
12
public 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;
}

改为

1
2
3
4
5
6
7
8
9
10
11
public 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、接口隔离原则

不应强制客户端依赖它不使用的方法。也就是说:不要通过添加新方法向现有的接口添加其他功能。
相反,创建一个新接口,让您的类在需要时实现多个接口。

1
2
3
4
5
6
7
8
9
10
interface IPost
{
void CreatePost();
}
// 添加新方法之后,原来实现IPost的所有子类都需要实现新的方法了,没有做到接口隔离
interface IPost
{
void CreatePost();
void ReadPost();
}

改为

1
2
3
4
5
6
7
8
9
interface 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、行业通用方案启发

不同的业务领域,业界或者有分享了一些比较成熟的技术方案,值得我们借鉴学习,我们在设计自己的方案的时候也需要总和考虑下。也许你需要引入新的技术思路来服务当下正在开展的业务。

对账

秒杀系统

如何设计并实现一个秒杀系统?

淘宝大秒杀系统设计详解

或许别人的方案也会有很多缺陷,重点是你需要按照自己的需求和思路进行重新梳理和设计,输出完整的技术方案文档,让大家一起参与讨论。当把这个方案成功应用到项目中之后,那才是你自己积累的宝贵经验。

4、翻翻自己的软件设计百科指南

这部分一般为个人经验积累(包括业务经验和技术经验),越是经验丰富的开发者,设计的方案越完善。

分布式系统

  • 数据完整性处理
  • 考虑分布式锁
  • 考虑消息可靠性
  • 服务接口是否设计合理
  • 是否考虑升级向后兼容
  • 接口幂等处理

账户系统

  • 关键数据是否脱敏
  • 账户系统数据表设计模型
  • 支付加解密是否安全
  • 支付流程常见缺陷
  • 支付密码设计

希望大家能及时更新自己的软件设计工具箱,让自己的工作经验真正的成为自己具有竞争力的的专业能力。

在做好了软件设计之后,对整个系统进行一次梳理,输出具体的系统架构图。方便更加清晰的说明:

  • 当前设计的功能模块在整个系统中所处的位置;
  • 当前设计的功能模块与其他系统部分的交互上下文;
  • 当前系统组成的技术架构;

梳理完成之后,能进一步加强对系统业务、架构的全局认识。

arthinking wechat
欢迎关注itzhai公众号