go语言在solid原则中的优势

57次阅读

共计 2684 个字符,预计需要花费 7 分钟才能阅读完成。

[读书笔记]SOLID 准则

SOLID 准则是用来做什么的?

solid 准则次要是通知咱们如何把数据和函数组织成为类,以及如何把这些类连接起来成为程序。

PS. 这里的类不肯定是面向对象的,只是示意数据和函数的一种分组。

solid 准则外围是形容软件的中层构造指标,致力于实现:

  1. 是软件更容易被扩大。
  2. 使软件更容易被了解。
  3. 使软件更容易被复用。

PS. 还流传这一些其余版本的 SOLID 准则,比方退出了迪米特法令。

SOLID 准则指的是什么?

SOLID 准则是指五个设计准则,每个设计准则的首字母拼起来,刚好是 SOLID 这个单词。

这些设计准则次要有。

  • SRP: 繁多职责准则。每个软件模块有且只有一个须要被扭转的理由。
  • OCP: 开闭准则。软件系统应该容许通过新增代码来批改原有零碎行为,而不是通过批改现有代码。
  • LSP: 里氏替换准则。实现某些接口的组件,必须同时恪守同一个约定,以便让这些组件能够互相替换。
  • ISP: 接口隔离准则。只依赖本人须要的局部。
  • DIP: 依赖倒置准则。调用方不应该依赖于被调用方的实现,而应该依赖于接口。

准则实际

SRP: 繁多职责准则

每个软件模块有且只有一个须要被扭转的理由。

指南: 不要强行复用代码。

这种逻辑在咱们的代码外面应该很常见,就是说咱们计算员工薪资须要用到员工的工作时长数据,另一方面,企业报表零碎也须要一份企业员工的工作时长数据,这两份数据兴许一开始的计算逻辑是雷同的。然而这样的复用十分危险,因为背地这两个逻辑服务的对象是不同的,他们很有可能提出不同的计算方法,比方薪酬部门提出每天晚上 10 点当前的工作工夫不须要计算在内????。

因而,咱们在面临这种需要时须要审慎,思考分明。

这个逻辑变动产生的时候变动的起因 (需求方) 是否雷同。

并不是说所有的复用都是不对的,是来自不同需求方的雷同逻辑不应该被复用,好比某一个角度雷同的两个物体其实不是等价的。

OCP: 开闭准则

软件系统应该容许通过新增代码来批改原有零碎行为,而不是通过批改现有代码。

开闭准则是一个大的指导方针。不蕴含具体的实现领导,所以这里不独自讲了。

LSP: 里氏替换准则

实现某些接口的组件,必须同时恪守同一个约定,以便让这些组件能够互相替换。

指南: 不要毁坏接口语义约定

这个准则了解起来其实就是不要毁坏约定,曾经广泛应用在软件架构畛域,不局限在独自的接口层面了。典型的例子是正方形 / 长方形问题。

Rectangle r = ...

r.SetW(5)

r.SetH(2)

assert(r.area() == 10);

长方形继承自正方形会突破这个测试用例,然而正方形又“假装”成长方形,这是不对的。正确的做法应该是:正方形能够通过组合而的形式把长方形适配成一个正方形,User 也不应该相似应用长方形一样来应用正方形。

  • ISP: 接口隔离准则。

只依赖本人须要的局部。

指南: 尽量应用小接口

尽量应用小的接口,而不是大接口,在这方面 go 语言是很好的践行者,golang 激励应用小的接口通过组合的形式来构建大的接口。咱们能够看到官网包外面存在大量的小接口。

//database/sql/driver 包

type ConnBeginTx interface {

BeginTx(ctx context.Context, opts TxOptions) (Tx, error)

}

type ConnPrepareContext interface {

PrepareContext(ctx context.Context, query string) (Stmt, error)

}

下面这两个接口都是数据库的 Conn 须要实现的接口(如果反对事物的话),然而官网定义采纳了将接口拆分开来的策略。

下图是 stdlib、k8s 和 docker 外面的 interface 定义,画出了上面这幅接口个数与接口中 method 个数关系的折线图:

小接口办法少,职责繁多;易于实现和测试,通用性强(如:io.Reader 和 Writer),易于组合(如:io.Reader)。

不过要想在业务畛域定义出适合的小接口,还是须要对问题域有着透彻的了解的。往往无奈定义出小接口,都是因为对畛域的了解还不到位,没法形象到很高的水平所致。

延长一下,咱们业务零碎架构的时候,Repository 应该是一个大的接口么?

type Repository interface {

SaveOrder(order Order)

SearchOrder(title string) []Order

}

这种设计并不是一个好的设计,SaveOrder 和 SearchOrder 很有可能用的是不同的存储形式(如果是 mysql 和 ES)。这时候咱们 Repository 的实现就须要同时依赖 Mysql 和 ES 组件。

这外面其实违反了繁多职责准则和接口隔离准则。

  1. SaveOrderUseCase 仅仅用到了 Repository 外面的 SaveOrder 办法,然而却同时依赖了 SearchOrder 接口,这违反了接口隔离准则。
  2. 搜寻用户的逻辑变更须要批改 RepositoryImpl,可能会影响到 SaveOrder 的逻辑,这违反了繁多职责准则。

劣势: 后置的接口隔离

go 语言在接口隔离原则上有一个很大的劣势,就是能够不依赖 Repository 的设计。

SaveOrderUseCase 齐全能够本人定义一个 SaveOrderRepository,外面只有 SaveOrder 的办法,这时候 RepositoryImpl 仍然是满足 SaveOrderRepository 的,能够间接拿来注入到 SaveOrderUseCase 中,如下图。

  • DIP: 依赖倒置准则。

调用方不应该依赖于被调用方的实现,而应该依赖于接口。

指南: 依赖接口而不是具体的实现。

依赖倒置不是依赖注入,更不是主动注入。这里略微解释一下

  • 依赖倒置: 应该依赖接口而不是具体实现。
  • 依赖注入: 依赖了接口当前,在运行以前必须的注入这个接口的具体实现。
  • 主动注入: 依据接口类型,名字等,通过技术手段找到适合的对象注入。

在下面的接口隔离的例子中就是恪守了依赖倒置的,咱们能够看到 usecase 外面没有依赖具体的 RepositoryImpl,而是依赖了 Repository 接口。

go 劣势: 能够倒置没有接口的实现

退出 Repository 是一个内部的包,而且这个包同时实现了 SaveOrder 和 SearchOrder 两个办法,然而没有定义任何接口,这时候 go 语音仍然是能够拿来应用的,如下图。只须要依赖方本人定义一个 SaveOrderRepository 办法即可。实时上,通过本人在包内定义接口的形式,能够放弃十分好的“正交性”,让每个包间接缩小任何依赖,包含接口层面的依赖。

正文完
 0