本文由 简悦SimpRead 转码,原文地址 jakewharton.com
2022年4月5日
2022年4月5日
方程y=mx+b定义了一条斜截线。这条直线将在b处与y轴相交,而x的每一次变化,其斜率(直线上升或下降的量)都将改变m。
斜率-截距给我提供了一种思考图书馆设计的方法,即相互之间的关系。截距是一个库的初始学习和设置成本,斜率是该库的复杂性如何随时间变化。这里没有真正的单位,数值完全是主观的。让我们来试试吧!
Picasso
整整10年前的今天,我在Square内部介绍了Picasso。作为一个Android的图片加载库,它的主要卖点是低拦截性。它不需要真正的配置,只需要一行代码(甚至在ListView适配器中)。
Picasso.with(context).load("https://...").into(imageView);
在当时,这与现有的库相比是一个令人耳目一新的变化,因为现有的库需要大量的前期和每个请求的配置。
然而,缺点是,随着你的需求增长,复杂度的坡度也会比预期的快。配置全局实例、管理多个实例、拦截请求、转换图像都是可能的,但比起库的设计方式不同,难度更大。
Retrofit
Retrofit是一个用于JVM和Android的声明式HTTP客户端抽象。它需要配置一个中心对象,然后才能使用它来创建服务接口的实例。
interface GitHubService {
@GET("users/{user}/repos")
Call<List<Repo>> listRepos(@Path("user") String user);
}
var retrofit = new Retrofit.Builder()
.baseUrl("https://api.github.com/")
.addConverter(MoshiJsonConverter.create())
.build();
var service = retrofit.create(GitHubService.class);
这种前期配置使Retrofit在Y轴上有更高的截距。对这些API的接触给了你一个发现功能的入口,并鼓励你以一种有效的方式管理它们的寿命,以满足你的使用,使复杂性的斜率不那么陡峭。
Dagger
Dagger是一个基于注解处理器的依赖注入库,用于JVM和Android。除了少量的注解之外,它几乎没有自己的API。为了使用Dagger,你需要学习依赖注入这个概念,学习如何构建其注释所适用的各种类型,然后决定如何将依赖注入融入你的架构。这是我用过的最不容易上手的库,这使它具有极高的概念性。
@Component(modules = {
AppModule.class
})
interface AppComponent {
App app();
}
@Module
final class AppModule {
@Provides static Database provideDatabase() {
return new Database();
}
}
final class App {
private Database database;
@Inject App(Database database) {
this.database = database;
}
void run() {
System.out.println(database.getUsers());
}
public static void main(String... args) {
AppComponent.create().app().run();
}
}
基本上做 "new App(new Database()).run() "就有很多行了!但当然没有什么能保持这么简单。但当然没有什么能保持如此简单。
一旦你将Dagger完全集成到一个大型应用中,添加和连接新的依赖关系就像添加一个参数一样简单。该库会自动计算出如何将两者连接在一起,并在预先定义的生命周期内共享实例。它的复杂度的斜率非常浅。
Picasso、Retrofit和Dagger的斜率截点评估大致是这样的。
这些单位是什么?这并不重要! 这是一个主观的近似概念。
设计
在设计新的库时,斜率截距评估真的很有意义。它可以作为一个框架来讨论你在前面加载给用户的复杂度,以及在持续使用一个库的过程中分摊的复杂度。
某些参数可以全局指定还是应该在每次调用时传递?它应该在两个地方都有覆盖行为吗?是否有一个隐含的默认值,或者你应该总是明确地要求它被提供?
在确定这些问题的答案时,你可以开始将库作为一个整体来看待。多个参数可以成为一个复合类型吗?某些参数是否意味着其他参数的默认值?是否有太多的概念被推入全局配置而不是本地配置?
最后,你可以将你的设计与其他设计进行比较,以确定你是否对其近似的斜率和截距感到满意。构建Picasso是为了对抗图像加载库,其复杂性与Dagger相同。在最初的设计中,我错过了目标,过度修正,使其过于简单。如果更接近Retrofit,对库的长期健康发展来说会是一个更舒适的地方。
分层
理想情况下,每个库都有一个接近零的截距和一个接近零的斜率。也就是说,一个库在开始使用时是微不足道的,它的API可以随着时间的推移适应每一个用例,而不需要学习任何新的东西。
在实践中,由于复杂性的本质,这种情况从未发生过。你不可能建立库来解决非琐碎的任务,同时保持基本的API并支持无数的用例。但你能做的是通过分层提供多条假想的斜截线来作弊。
在不同的抽象层次上提供多个API,可以用一个简单的API解决80%的用例,然后用一个更详细的API解决剩余20%的用例,最后用一个低层次的API解决最后一片。每一层都是建立在下一层的基础上,API的复杂程度也在逐步降低。
例如,在一个HTTP客户端中,你可以为大多数人提供声明式API,为少数人提供命令式API,然后为异国需求提供低级协议处理程序。如果某一层不能满足你的要求,那么你总是可以下降到下一层,以获得更多的控制权,但也更多的责任。
现在你所创建的是与上面完全相同的图表,只是代表一个库和它的三层API。
这当然不是精确的科学。但也许它能帮助你在未来建立一个更好的库。它已经帮助了我!
- Jake Wharton