《Clean Architecture:软件架构与设计匠艺》 速读
前言
《Clean Architecture:软件架构与设计匠艺》这本书豆瓣评分9.7,很多大神推荐的书,但目前只有英文版,自己花了一些时间看完,做一个笔记,留作后续学习。
第一章 What Is Design and Architecture?(什么是设计和架构)
软件设计与架构之间的关系,作者认为它们是无区别的。这段话后面会有详细描述,主要的意思是作者认为"架构"一词经常用于与低层次细节分离了的高层次上下文中,然而"设计"似乎经常表示在低层次上的结构和决定。
也就是架构是跟更高层次的抽象相关,设计是跟组件实现的细节相关,普遍认为架构师就是跟更高层次的抽象相关,而不是在一线编写代码,作者认为是大错特错。
架构师应该是能力最强的程序员,需要有编程任务而不是PPT工程师!因为你没有自己体验自己设计的架构给业务同学带来的问题,就无法持续的改进自己的架构!
这里我再简单解释一下,什么是架构(我自己的理解)
什么是架构
架构是指在设计和构建系统时所采用的组织方式和结构。架构决定了系统中各部分如何协调工作、如何处理数据和信息、以及如何满足用户需求。
架构可以应用于各种类型的系统,包括软件系统、网络系统、数据库系统、楼宇和城市规划等。它的目的是使系统更具可扩展性、可维护性、性能、可用性和可靠性。
然后通过一个案例研究,作者发现在一家公司,忽略了干净的代码和良好的设计的重要性,以程序员数量为唯一驱动因素,也就是不停地增加程序员来维护日益复杂度上升的软件,导致每次发布的成本都在增加,最终导致了公司的GG。
第二章 软件系统两个价值维度-----行为价值和架构价值。
程序员常常相信他们的工作就是让机器实现需求和修复错误。这是错误的,还有一个重要的任务,就是架构!
这就涉及到一个问题:
功能或架构?两者之中哪个提供更大的价值?是让软件系统能工作更重要,还是让软件系统容易修改更重要呢?
如果你问管理人员,他们经常会说让软件系统工作更重要。轮到开发者,他们经常赞同这种态度。但是这是错误的态度。我可以用简单的逻辑工具检查极端情况来证明它是错误的。
- 如果你给我一个完美工作但是不可能修改的程序,当需求变更,它将不能工作,而我也不能让它工作。因此程序将变得无用。
- 如果你给我一个无法工作但容易修改的程序,那么我可以让它工作,并且随着需求变更保存它可工作。因此程序会一直有用。
你可能觉得这个论证不能令人信服。毕竟,没有一个不能够被修改的程序。但是,有些系统几乎不可能修改,因为修改的代价超过了它的好处。许多系统在其某些功能或配置上达到了这一点。
沟通问题
这里我转化为我的看法,就是前端组需要跟比如UI组、产品组划定职责范围,当然也包括前端团队的沟通机制,要不产品和UI随意的改动极易让我们的系统陷入不停的更改的漩涡,作为架构师,一定要具备一定的沟通能力,想办法跟各个组建立一定的协议,并且保留每次沟通的文档和录音,以备battle用。
无论大小厂,甩锅我相信绝大部分同学都遇到过,所以害人之心不可有,防人之心不可无,对面甩你一口大大的黑锅,你可准备好姿势接住了?
欢迎大家评论区说出你被甩锅的故事!
第二部分 第3章到第6章
第二部分主要讲了编程范式,包括:
- 结构化编程
- 面向对象编程
- 函数式编程
我们分别来简单描述一下:
结构化编程
简单描述就是我们大多数前端程序员的编程方式,就是写流水账,从上到下,是面向过程。
但是这种写法也可以具备很好的拓展性,前提就是模块划分清晰,然后函数抽象好面向过程中的功能,也就是职责单一原则实现好,其实代码质量也是不错的,大家千万不要觉得面向对象,函数式编程就要高级,往往在js里,面向对象、函数式和结构化是一起使用的。
因为本质上,功能性降解拆分仍然是编写代码最佳实践之一,上面谈及的三种方法,只是侧重的功能性拆解的方式和粒度不同而已。不要局限于一种编程方式(这也是js的灵活性带来的好处)
这里就可以产生一个好的面试题,结构化编程、面向对象、函数式编程的特点是什么,然后你有什么想法,比如写react组件的时候,把三者的优点结合起来使用?
面向对象编程
首先什么是面向对象?一种常见的回答是“数据与函数的组合”。
另一种常见的回答是“面向对象编程是一种对真实世界进行建模的方式”,这种回答只能算作避重就轻。“对真实世界的建模”到底要如何进行?我们为什么要这么做,有什么好处?
也许这句话意味着是“由于采用面向对象方式构建的软件与真实世界的关系更紧密,所以面向对象编程可以使得软件开发更容易”——即使这样说,也仍然逃避了关键问题——面向对象编程究竟是什么?也就是说解决了哪些重要的问题。造就它可以对真实世界进行建模!
作者给出的答案是:面向对象编程就是以对象为手段来对源代码中的依赖关系进行控制的能力,这种能力让软件架构师可以构建出某种插件式架构,让高层策略性组件与底层实现性组件相分离,底层组件可必编译成插件,实现独立于高层组件的开发和部署。
所以我个人觉得,只要你的代码实现了上面插件式的架构,就不要限于你是面向对象编程还是其他编程范式,所以上面说面向过程只要你也实现了插件式架构,也是很好维护的。
我们再谈谈面向对象编程的特点:
封装
通过釆用封装特性,我们可以把一组相关联的数据和函数圈起来,我们希望外界代码只能看见部分函数,数据则完全不可见。
我认为面向对象中,对前端唯一有帮助的就是这个特性,为什么呢,因为我崇尚在数据层采用领取设计模型,也就是数据层和UI层是解耦的,数据层是描述业务数据和变化的,UI层只是把变化显示在页面而已,所以数据层暴露给ui层的只有函数。
但是问题来了,UI层拿不到数据咋展示,也就是UI层和数据层耦合的根本原因是这个,所以解耦的办法就是,将数据交给框架,比如react的useState,但是,我们不直接使用setState,而是抽象一层,比如,dispatch(某个行为)(不一定是redux啊,反正能实现这层抽象就行了),然后渲染视图,我还是推荐zustand这个库。
其他两个特性都不需要关系,为啥呢,接着看!
继承
继承的主要作用是让我们可以在某个作用域内对外部定义的某一组变量与函数进行覆盖
个人觉得继承在前端使用范围有限,无论是react还是vue3都推崇的hook,都比继承强太多了!
多态
先说结论:我们前端不必在意多态,它本身自然而然融入在js语言里了。
在 JavaScript 中,多态可以通过重载(Overloading)和重写(Overriding)来实现。
重载是指在一个类中定义多个名称相同,但参数列表不同的函数。在 JavaScript 中,函数的参数列表是不同的,因此可以实现重载。
重写是指子类中定义了与父类相同的函数,并且函数的实现方式与父类的不同。重写可以使用 JavaScript 中的 prototype 继承机制实现。
例如,下面是一个使用多态的例子:
class Animal {
speak() {
console.log('I am an animal');
}
}
class Dog extends Animal {
speak() {
console.log('I am a dog');
}
}
class Cat extends Animal {
speak() {
console.log('I am a cat');
}
}
let dog = new Dog();
let cat = new Cat();
dog.speak(); // I am a dog
cat.speak(); // I am a cat
依赖反转:利用接口可以让依赖具体的事务变为依赖实现
以下是我个人观点:
这个就是使用Typescript的原因,比如A组件依赖B组件,这时候就产生了耦合
所以依赖接口,就把A依赖组件B,变为A依赖实现的接口,只要实现了这个接口,你传B组件还是C组件都无所谓,我要的是规范,不是具体实现。
题外话,解耦的方式常用的有4种:
- 使用接口隔离原则。接口隔离原则是指一个组件应该只实现它需要的接口,而不应该实现不需要的接口。这样可以使组件之间的依赖关系减少,从而降低耦合度。
- 使用依赖注入。依赖注入是指一个组件不直接依赖于其他组件,而是通过外部注入所需的依赖。这样可以使组件之间的依赖关系更加松散,从而降低耦合度。
- 使用中介者模式。中介者模式是指多个组件之间不直接交互,而是通过中介者来交互。这样可以使组件之间的依赖关系更加松散,从而降低耦合度。
- 使用观察者模式。观察者模式是指一个组件可以订阅另一个组件的状态变化,并在状态发生变化时自动更新。这样可以使组件之间的依赖关系更加松散,从而降低耦合度。
函数式编程
函数式编程很牛b,为啥,我举个例子:
假设某个银行应用程序需要维护客户账户余额信息,当它放行存取款事务时,就要同时负责修改余额记录。
如果我们不保存具体账户余额,仅仅保存事务日志,那么当有人想查询账户余额时。我们就将全部交易记录取出,并且每次都得从最开始到当下进行累计。当然,这样的设计就不需要维护任何可变变量了。
但显而易见,这种实现是有些不合理的。因为随着时间的推移,事务的数目会无限制增长,每次处理总额所需要的处理能力很快就会变得不能接受。如果想使这种设计永远可行的话,我们将需要无限容量的存储,以及无限的处理能力。
但是如果真的我们有无限的容量的存储和与之匹配的超强的处理能力,那很多程序都不需要变量了,读取日志就可以完成编程,这样出bug的几率就小很多。有兴趣的同学可以去搜一下redo,undo(就是一个程序的撤回和前进的操作,最方便的方法就是读取历史快照数据,还原现场,秒杀设计模式)
虽然这个不现实,但是我们仍然在某些局部使用,比如编写函数的时候,想想能不能采取中间件的方式,或者compose的方式,以前写过一篇文章,大家有兴趣可以去看
第三部分 设计原则 从第7章 - 第11章
这里主要谈了SOLID设计原则,我转化为我的理解,总结一下:
职责单一原则(Single responsibility principle)
单一职责原则是面向对象设计的基本原则之一。它的本意是每个类应该有且仅有一个原因引起变化。然而,很多人误解了这个原则,将它视作每个类只能有一个功能的限制。
其实,单一职责原则的目的是为了降低类的复杂度,使类变得更加容易理解和维护。如果一个类承担了太多的职责,那么它就会变得非常复杂,很难维护。因此,我们应该尽量将类的职责分解成更小的部分,使每个类只负责一个具体的任务。
另一方面,单一职责原则并不是说每个类只能有一个功能。在实际应用中,一个类可能会有多个功能,但这些功能应该是相互关联的,与该类的主要职责相关。
总结一下:
- 单一职责原则可以简要的理解为,对于变化频繁的内容,并且可以独立出来单独封装成类,那么就需要提取这些内容
- 我们在业务中可以在开始写一个粗粒度的类,当发现远远不能满足业务的变化时,需要更细粒度的划分类里面的内容,就需要提取封装这些内容了
- 这就涉及到两个概念,一是就是不要过度设计,我们不是神,不能预料产品和市场的变化,二是持续重构,当需要重构的时候,重构就好
单一职责原则在业务中的体现:倒金字塔结构——业务逻辑组件需要呈现为倒金字塔结构
-
组成金字塔的砖,说白了就是组件
-
业务逻辑层应该被设计成一个个功能非常单一的小组件,所谓小是指 API 数量少、代码行数少;
-
由于职责单一因此必然组件数量多,每一个组件对应一个很具体的业务功能点(或者几个相近的);
-
于是系统架构就自然呈现出倒立的金字塔形状:越接近顶层的业务场景组件数量越多,越往下层的复用性高,于是组件数量越少。
开闭原则(Open Closed Principle)
- 简单的说,开闭原则就是,添加一个新的功能,应该是通过在已有代码基础上扩展代码(新增模块、类、方法、属性等),而非修改已有代码(修改模块、类、方法、属性等)的方式来完成。(不是绝对的,下一点马上会聊到)
但是开闭原则有一点very very 需要注意!
开闭原则并不是说完全杜绝修改而是以最小的修改代码的代价来完成新功能的开发
- 修改代码有时候在实际的业务场景和个人能力有限的范围内很难做到类或者方法的替换,我们需要做到的是为后面可能的情况留口子,尽量让修改范围变得比较小。
- 刚开始进步,只要代码比以前写的更好维护,更可读,其实对茫茫的像我一样的业务开发仔来说就已经提高非常多的效率了,慢慢积累,慢慢改进,不断进步!
里氏替换原则(了解一下,不是重点)
- 里氏替换原则通俗的来讲就是:子类可以扩展父类的功能,但不能改变父类原有的功能。
- 里氏代换原则告诉我们,在软件中将一个基类对象替换成它的子类对象,程序将不会产生任何错误和异常,反过来则不成立,如果一个软件实体使用的是一个子类对象的话,那么它不一定能够使用基类对象
- 比如说父类有一个setState方法,子类就不要去改写了,改写之后就把原有的setState的意图改变了,导致这个组件的不能够用setState渲染页面
接口隔离(了解即可)
- 客户端不应该依赖它不需要的接口。一个类对另一个类的依赖应该建立在最小的接口上
- 这个原则,如果使用typescript的话,可以理解为一个接口上定义的东西,如果用到的类都没有使用接口上的某些方法或者属性,就删掉它
依赖倒转原则(Dependence Inversion Principle)
- 提倡:高层模块不应该依赖低层模块。两个都应该依赖抽象
- 抽象不应该依赖细节,细节应该依赖抽象
- 面向接口编程,不要针对实现编程
- 举一个跟业务相关的例子
- 业务一依赖业务二,那么业务二变化很可能影响业务一的逻辑, 比如业务一用了业务二的3个方法,业务二重构了,这些方法也变了,是不是很危险呢?
- 这就跟一个类里面,引用另一个类的方法或者属性一样,让自己的不稳定性极具增加。
- 所以业务一应该依赖接口,将业务二这个类提供的数据抽象成接口,让业务一依赖业务二抽象出来的接口,这样业务二无论如何变化,都要提供相同的东西,比如方法都要提供相同输出,输出字符串啊,输出数组(数组成员一般是对象,这些对象的属性也是用接口规范的)。
第四部分:组件构建原则
这一部分其实我觉得上面已经总结了,组件的构建就是实现一个倒金字塔结构就行了,组件耦合的问题,我们在上面也提出了4个解决办法
第五部分 软件架构
软件架构师自身需要是程序员
首先,软件架构师自身需要是程序员,并且必须一直坚持做一线程序员,绝对不要听从那些说应该让软件架构师从代码中解放出来以专心解决高阶问题的伪建议。不是这样的!软件架构师其实应该是能力最强的一群程序员,他们通常会在自身承接编程任务的同时。逐渐引导整个团队向一个能够最大化生产力的系统设计方向前进。也许软件架构师生产的代码量不是最多的,但是他们必须不停地承接编程任务。如果不亲身承受因系统设计而带来的麻烦,就体会不到设计不佳所带来的痛苦,接着就会逐渐迷失正确的设计方向。
软件系统的架构质量是由它的构建者所决定的,软件架构这项工作的实质就是规划如何将系统切分成组件,并安排好组件之间的排列关系,以及组件之间互相通信的方式。
而设计软件架构的目的,就是为了在工作中更好地对这些组件进行研发、部署、运行以及维护。
上面这句话可能会让人很意外,也许你一直认为设计软件架构的目的应该是让一个系统能正常地工作。我们当然需要让系统正常工作,软件架构设计最高优先级的目标就是保持系统正常工作。
然而,一个软件系统的架构质量和该系统是否能正常工作的关系并不大,毕竟世界上有很多架构设计糟糕但是工作正常的软件系统。真正的麻烦往往并不会在我们运行软件的过程中出现,而是会出现在这个软件系统的开发、部署以及后续的补充开发中。
插件式架构
插件式架构是一种设计模式,它通过分离主应用程序和功能插件的方式,使得应用程序更加灵活和可扩展。主应用程序包含基础功能,而插件则提供特定的额外功能。
好处是,插件式架构可以让开发人员很容易地将新功能添加到应用程序中,而无需修改主应用程序的代码。这样,主应用程序的基础代码就可以保持稳定,而新功能的开发和测试也变得更加独立。
例如,假设有一个 web 应用程序,它提供了基础的文本编辑功能。开发人员可以创建一个插件,该插件提供了语法高亮功能。用户可以选择安装或不安装这个插件。如果用户选择安装插件,那么在编辑文本时就会看到语法高亮的效果。如果用户不安装这个插件,那么文本编辑功能仍然可以正常使用,但是不会出现语法高亮效果。
独立性
不要害怕重复。
重复的情况中也有一些是假的,或者说这种重复只是表面性的。如果有两段看起来重复的代码,它们走的是不同的演进路径,也就是说它们有着不同的变更速率和变更缘由,那么这两段代码就不是真正的重复。等我们几年后再回过头来看,可能就会发现这两段代码是非常不一
现在,我们假设某系统中有两个用例在屏幕展现形式上非常类似。每当这种时候,架构师们就很可能非常想复用同一段代码来处理它们的屏幕展示。那么,我们到底是否应该这样做呢?这里是真正的重复,还只是一种表面性的重复?
恐怕这里很可能只是表面性的重复。随着时间推移,这两个用例的屏幕展示功能可能会各自演变,最终很可能完全不同。正是由于这样的原因,我们必须加倍小心地避免让这两个用例复用同一段代码,否则,未来再想将它们分开会面临很大的挑战。
策略与层次
一句话概括,一般来说,低层组件被设计为依赖于高层组件。反之不行。
详细版:
本质上,所有的软件都是一组策略语句的集合。是的,可以说计算机程序不过就是一组仔细描述如何将输入转化为输出的策略语句的集合。
在大多数非小型系统(nontrivial system)中,整体业务策略通常都可以被拆解为多组更小的策略语句。一部分策略语句专门用于描述计算部分的业务逻辑,另一部分策略语句则负责描述计算报告的格式。除此之外,可能还会有一些用于描述如何校验输入数据的策略。
在一个设计良好的架构中,依赖关系的方向通常取决于它们所关联的组件层次。一般来说,低层组件被设计为依赖于高层组件。
我们对“层次”是严格按照“输入与输出之间的距离”来定义的。也就是说,一条策略距离系统的输入/输出越远,它所属的层次就越高。而直接管理输入/输出的策略在系统中的层次是最低的。
业务逻辑
业务逻辑应该是独立的一部分,可以称之为实体。我们前端常常把业务逻辑和UI层混在一起,举个例子,react组件有多少人是把useState写在组件里,而不是抽象为业务逻辑去调用方法,有多少人是fetch或者axios请求放在业务组件的useEffect里!
这都是没有把业务逻辑抽象为业务实体的行为!
业务实体实际上就是计算机系统中的一种对象,这种对象中包含了一系列用于操作关键数据的业务逻辑。这些实体对象要么直接包含关键业务数据,要么可以很容易地访问这些数据。业务实体的接口层则是由那些实现关键业务逻辑、操作关键业务数据的函数组成的。
这些业务逻辑应该保持纯净,不要掺杂用户界面或者所使用的数据库相关的东西。在理想情况下,这部分代表业务逻辑的代码应该是整个系统的核心,其他底层概念的实现应该以插件形式接入系统中。业务逻辑应该是系统中最独立、复用性最高的代码。
整洁架构
图中的总体思想是依赖的方向只能是从外向内。也就是说,外部的圈依赖内部的圈,内部的圈不能依赖外部。也就是说内圈对外圈一无所知,没有任何依赖。外层中命名和数据格式不能影响内层。
下面具体介绍一下各个层。
其中entities和use case在前端很难区分,我简单介绍一下:
在前端开发中,"entities"和"use case"是两个概念,它们在某种程度上是相互关联的。
"Entities"指的是封装了某些特定业务逻辑的对象。这些对象可以被多个应用程序或多个功能使用。例如,在一个应用程序中,可能会有一个"Employee"实体,该实体封装了与员工信息相关的操作(如计算员工的工资、更新员工信息等)。
"Use case"指的是一个系统的特定功能或操作。它表示一个系统的用户(通常是人)如何与系统交互,以便完成某个特定的任务。例如,在一个应用程序中,可能会有一个"登录"用例,该用例描述了用户如何使用该应用程序的登录功能。
也就是Entities是不需要跟交互有关系的,Use case是跟交互有关系的,比如点击事件。
Entities
封装业务规则。 这一层的组成可以是包含方法的对象,也可以是数据结构和方法。这一层封装了最通用和高层级的业务规则。 和业务规则不相关的改变不应该影响到这一层。
Use Cases
这一层封装应用级别的业务规则,这一层实现了系统的use cases。这一层主要是实现对Entities层的编排,通过调用更底层的Entities层的对象来实现一个用例。
这一层的改变不会影响到Entities层。同时,外部的改变,比如数据库/UI的变更也不会影响这一层。 只有这个应用的用例的改变才会引起这一层发生变化。
Interface Adapters
主要用来做Use Cases/Entities层 和 外部系统(展示层,数据库层)之间的数据格式的适配。
这里对应前端的数据层转化逻辑,比如从后端获取的数据再加工,改成ui框架要的数据格式,还有就是把后端获取的数据进行筛选合并等等,前端的UI交互逻辑不在这里,需要沉淀到UI组件库里。
Frameworks and Drivers
这一层是最外面的框架层,比如数据库/web 框架等。
这里对应前端的UI层,其中交互逻辑沉淀到UI组件里。
展示器和谦卑对象
我觉着最重要的一句话就是,强大的可测试性是一个架构的设计是否优秀的显著衡量标准之一。你写的代码架构好不好,好不好测试就是很重要的标准,不好测试,不用说,垃圾架构!
边界
简单的话说,你现实里时间和金钱都是有限的,架构整合适就好,这个完全看个人经验。
这段话的意思是:作为一名架构师,你必须权衡成本,并确定架构边界在哪里,哪些应该被完全实现,哪些应该被部分实现,哪些应该被忽略。
也就是说,架构师在设计系统架构时,必须考虑成本因素,并在合理的范围内选择实现哪些功能。有些功能可能会被完全实现,有些功能可能只会被部分实现,而有些功能则可能会被忽略。架构师需要根据实际情况来权衡成本,并做出决策。
第六部分 实现细节
Web是实现细节
GUI 只是一个实现细节。而 Web 则是 GUI 的一种,所以也是一个实现细节。作为一名软件架构师,我们需要将这类细节与核心业务逻辑隔离开来
这个跟我们之前说的UI层和实体和use case分开是一致的。
其实我们可以这样考虑这个问题:Web 只是一种 I/O 设备。早在 20 世纪 60 年代,我们就已经了解编写设备无关应用程序的重要性。这种独立性的重要性至今仍然没有变化,Web 也不例外。
是这样的吗?有人可能会辩称 Web 这样的 GUI 是非常特殊的,它能力强大,强大到让我们追求设备无关的架构变得毫无意义。当我们考虑到 JavaScript 数据校验的复杂程度、可拖拽的 Ajax 调用,以及无数可以轻松引入的设计组件时,很容易认为追求设备无关性是不现实的。
从某种程度上来说,的确如此。应用程序和 GUI 之间的频繁交互的确是与 GUI 的类型密切相关的。浏览器与 Web 应用之间的交互模式也的确与桌面客户端/服务器之间的交互模式区别很大。想要让浏览器上的 Web 操作模仿我们在 UNIX 中对 I/O 设备那样的操作,将其抽象成界面交互模型几乎是不可能的。
我们其实可以从 UI 和应用程序之间的另一条边界出发来进行抽象化。因为业务逻辑可以被视为是一组用例的集合。而每个用例都是以用户的身份来执行某种操作的,所以它们都可以用输入数据、处理过程以及输出数据这个流程来描述。
也就是说,在 UI 和应用程序之间的某一点上,输入数据会被认为达到了一个完整状态,然后用例就被允许进入执行阶段了。在用例执行完之后,其生成的返回数据又继续在 UI 与应用程序之间传递。
这样一来,完整的输入数据,以及完整的输出数据就可以被标准化为数据结构,并提供给执行用例的进程了。通过这种方法,我们就可以认为用例都是以设备无关的方式在操作 I/O 设备。
应用程序框架是实现细节
我们与框架作者之间的关系是非常不对等的。我们要采用某个框架就意味着自己要遵守一大堆约定,但框架作者却完全不需要为我们遵守什么约定。
请仔细想想这一关系,当我们决定采用一个框架时,就需要完整地阅读框架作者提供的文档。在这个文档中,框架作者和框架其他用户对我们提出进行应用整合的一些建议。一般来说,这些建议就是在要求我们围绕着该框架来设计自己的系统架构。譬如,框架作者会建议我们基于框架中的基类来创建一些派生类,并在业务对象中引入一些框架的工具。框架作者还会不停地催促我们将应用与框架结合得越紧密越好。
对框架作者来说,应用程序与自己的框架耦合是没有风险的。毕竟作为作者,他们对框架有绝对的控制权,强耦合是应该的。
与此同时,作者当然是非常希望让我们的应用与其框架紧密结合的,因为这意味着脱离框架会很困堆。作为框架作者来说,没有什么比让一堆用户心甘情愿地基于他的框架基类来构建派生类更自豪的事情了。
换句话说,框架作者想让我们与框架订终身——这相当于我们要对他们的框架做一个巨大而长期的承诺,而在任何情况下框架作者都不会对我们做出同样的承诺。这种婚姻是单向的。我们要承担所有的风险,而框架作者则没有任何风险。
你必须明白,如果一旦在项目中引入一个框架,很有可能在整个生命周期中都要依赖于它,不管后来情形怎么变化,这个决定都很难更改了。因此,不应该草率地做出决定。
转载自:https://juejin.cn/post/7185476907310252092