惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

博客园 - 聂微东
S
Schneier on Security
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
Latest news
Latest news
Cyberwarzone
Cyberwarzone
Cisco Talos Blog
Cisco Talos Blog
T
The Exploit Database - CXSecurity.com
T
Tenable Blog
I
Intezer
T
Threat Research - Cisco Blogs
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
C
Cybersecurity and Infrastructure Security Agency CISA
P
Privacy International News Feed
P
Palo Alto Networks Blog
The Register - Security
The Register - Security
IT之家
IT之家
Google DeepMind News
Google DeepMind News
C
Cyber Attacks, Cyber Crime and Cyber Security
L
LINUX DO - 最新话题
S
Securelist
WordPress大学
WordPress大学
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
Hugging Face - Blog
Hugging Face - Blog
N
News and Events Feed by Topic
H
Help Net Security
Project Zero
Project Zero
K
Kaspersky official blog
博客园 - 三生石上(FineUI控件)
L
LINUX DO - 热门话题
大猫的无限游戏
大猫的无限游戏
G
GRAHAM CLULEY
F
Full Disclosure
博客园 - 叶小钗
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
TaoSecurity Blog
TaoSecurity Blog
N
News | PayPal Newsroom
Forbes - Security
Forbes - Security
博客园 - 司徒正美
I
InfoQ
Recent Announcements
Recent Announcements
Attack and Defense Labs
Attack and Defense Labs
P
Privacy & Cybersecurity Law Blog
S
Security @ Cisco Blogs
人人都是产品经理
人人都是产品经理
The GitHub Blog
The GitHub Blog
Simon Willison's Weblog
Simon Willison's Weblog
G
Google Developers Blog
N
Netflix TechBlog - Medium
U
Unit 42
阮一峰的网络日志
阮一峰的网络日志

元视角

.NET 生态下的 Agent 框架选型:从 ReAct 到原生推理 - 元视角 从「能用」到「好用」:LLM 流式响应实现方式的探索之路 - 元视角 当我用 2000 条聊天记录,让 AI 为我画一幅自画像 - 元视角 基于 Supabase 的 AI 应用开发探索 - 元视角 微博 × MCP:社交媒体新玩法解锁 - 元视角 四点钟海棠花未眠 - 元视角 Semantic Kernel × MCP:智能体的上下文增强探索 - 元视角 基于 K-Means 聚类分析实现人脸照片的快速分类 - 元视角 容器技术驱动下的代码沙箱实践与思考 - 元视角 温故而知新:后端通用查询方案的再思考 - 元视角 浅议 CancellationToken 在前后端协同取消场景中的应用 - 元视角 Semantic Kernel 视角下的 Text2SQL 实践与思考 - 元视角 关于 ChatGPT 的流式传输,你需要知道的一切 - 元视角 RAG 的是与非、Rewrite 和 Rerank - 元视角 使用 EFCore 和 PostgreSQL 实现向量存储及检索 - 元视角 基于 LLaMA 和 LangChain 实践本地 AI 知识库 - 元视角 使用 llama.cpp 在本地部署 AI 大模型的一次尝试 - 元视角 如何为 Git 配置多个 SSH Key - 元视角 C# 使用 LibUsbDotNet 实现 USB 设备检测 - 元视角 基于 C# 实现样式与数据分离的打印方案 - 元视角 基于 SVG 的图形交互方案实践 - 元视角 前端视频播放技术概览 - 元视角 温故而知新,再话 Python 动态导入 - 元视角 后 GPT 时代,NLP 不存在了? - 元视角 视频是不能 P 的系列:使用 Milvus 实现海量人脸快速检索 - 元视角 GDI+下字体大小自适应方案初探 - 元视角 小爱音箱集成 ChatGPT 的不完全教程 - 元视角 程序员视角下的三体世界随想 - 元视角 关于 Docker 容器配置信息的渐进式思考 - 元视角 在 Docker 容器内集成 Crontab 定时任务 - 元视角 为你的服务器集成 LDAP 认证 - 元视角 似花还似非花 - 元视角 视频是不能 P 的系列:使用 Dlib 实现人脸识别 - 元视角 浅议分布式链路追踪与日志的整合 - 元视角 关于 Git 大文件上传这件小事 - 元视角 .NET 进程内队列 Channel 的入门与应用 - 元视角 使用 Fody 实现 .NET 的静态编织 - 元视角 .NET Core + ELK 搭建可视化日志分析平台(下) - 元视角 聊一聊前端图片懒加载背后的故事 - 元视角 支持外部链接跳转的 Vue Router 扩展实现 - 元视角 视频是不能 P 的系列:OpenCV 和 Dlib 实现表情包 - 元视角 不得不说的 ASP.NET Core 集成测试 - 元视角 再议 DDD 视角下的 EFCore 与 领域事件 - 元视角 Vue.js 前端项目容器化部署实践极简教程 - 元视角 再见,人间四月天 - 元视角 Python 图像风格化迁移助力画家梦想 - 元视角 利用 ASP.NET Core 中的标头传播实现分布式链路追踪 - 元视角 利用 gRPC 实现文件的上传与下载 - 元视角 七种武器:延迟队列的原理和实现总结 - 元视角 gRPC 流式传输极简入门指南 - 元视角 Envoy 集成 Jaeger 实现分布式链路追踪 - 元视角 浅议非典型 Web 应用场景下的身份认证 - 元视角 gRPC 借助 Any 类型实现接口的泛化调用 - 元视角 分布式丛林探险系列之 Redis 集群模式 - 元视角 分布式丛林探险系列之 Redis 主从复制模式 - 元视角 通过 Python 预测 2021 年双十一交易额 - 元视角 gRPC 搭配 Swagger 实现微服务文档化 - 元视角 SSL/TLS 加密传输与数字证书的前世今生 - 元视角 使用 Python 自动识别防疫健康码 - 元视角 你不可不知的容器编排进阶技巧 - 元视角 ASP.NET Core 搭载 Envoy 实现 gRPC 服务代理 - 元视角 再话 AOP,从简化缓存操作说起 - 元视角 ASP.NET Core 搭载 Envoy 实现微服务身份认证(JWT) - 元视角 ASP.NET Core 搭载 Envoy 实现微服务的监控预警 - 元视角 ASP.NET Core 搭载 Envoy 实现微服务的反向代理 - 元视角 ASP.NET Core gRPC 打通前端世界的尝试 - 元视角 EFCore 实体命名约定库:EFCore.NamingConventions - 元视角 ASP.NET Core gRPC 集成 Polly 实现优雅重试 - 元视角 ASP.NET Core gRPC 健康检查的探索与实现 - 元视角 ASP.NET Core gRPC 拦截器的使用技巧分享 - 元视角 SnowNLP 使用自定义语料进行模型训练 - 元视角 使用 HttpMessageHandler 实现 HttpClient 请求管道自定义 - 元视角 ABP vNext 的实体与服务扩展技巧分享 - 元视角 ABP vNext 对接 Ant Design Vue 实现分页查询 - 元视角 源代码探案系列之 .NET Core 跨域中间件 CORS - 元视角 源代码探案系列之 .NET Core 限流中间件 AspNetCoreRateLimit - 元视角 源代码探案系列之 .NET Core 并发限制中间件 ConcurrencyLimiter - 元视角 通过 EmbededFileProvider 实现 Blazor 的静态文件访问 - 元视角 低代码,想说爱你不容易 - 元视角 记一次失败的 ThoughtWorks 面试经历 - 元视角 从 C# 1.0 到 C# 9.0,历代 C# 语言特性一览 - 元视角 通过 Python 分析 2020 年全年微博热搜数据 - 元视角 基于 Python 和 Selenium 实现 CSDN 一键三连自动化 - 元视角 使用多线程为你的 Python 爬虫提速的 N 种姿势,你会几种? - 元视角 实现网页长截图的常见思路总结 - 元视角 温故而知新,由 ADO.NET 与 Dapper 所联想到的 - 元视角 视频是不能 P 的系列:OpenCV 人脸检测 - 元视角 作为技术宅的我,是这样追鬼滅の刃的 - 元视角 使用 Python 抽取《半泽直树》原著小说人物关系 - 元视角 厉害了!打工人用 Python 分析西安市职位信息 - 元视角 使用 dotTrace 对 .NET 应用进行性能分析与优化 - 元视角 一道 HashSet 面试题引发的蝴蝶效应 - 元视角 基于选项模式实现.NET Core 的配置热更新 - 元视角 Dapper.Contrib 在 Oracle 环境下引发 ORA-00928 异常问题的解决 - 元视角 .NET Core 中对象池(Object Pool)的使用 - 元视角 利用 MySQL 的 Binlog 实现数据同步与订阅(下):EventBus 篇 - 元视角 利用 MySQL 的 Binlog 实现数据同步与订阅(中):RabbitMQ 篇 - 元视角 利用 MySQL 的 Binlog 实现数据同步与订阅(上):基础篇 - 元视角 记一次从已损坏的 Git 仓库中找回代码的经历 - 元视角 .NET Core 原生 DI 扩展之属性注入实现 - 元视角
浅析 WPF 中 MVVM 模式下命令与委托的关系 - 元视角
飞鸿踏雪 · 2016-07-21 · via 元视角

各位朋友大家好,我是 Payne,欢迎大家关注我的博客,我的博客地址是http://qinyuanpei.com。最近因为项目上的原因开始接触 WPF,或许这样一个在现在来讲显得过时的东西,我猜大家不会有兴趣去了解,可是你不会明白对某些保守的项目来讲,安全性比先进性更为重要,所以当你发现银行这类机构还在使用各种“复古”的软件系统的时候,你应该相信这类东西的确有它们存在的意义。与此同时,你会更加深刻地明白一个道理:技术是否先进性和其流行程度本身并无直接联系。由此我们可以推论出:一项不流行的技术不一定是因为它本身技术不先进,或许仅仅是因为它无法满足商业化的需求而已。我这里的确是在说 WPF,MVVM 思想最早由 WPF 提出,然而其发扬光大却是因为前端领域近年来比较热的 AngularJS 和 Vue.js,我们这里表达的一个观点是:很多你以为非常新潮的概念,或许仅仅是被人们重新赋予了新的名字,当你理清这一切的来龙去脉以后,你会发现这一切并没有什么不同。这符合我一贯的主张:去发现问题的实质、不要被框架束缚、通过共性来消除差异,所以在今天这篇文章里,我想说说 WPF 中 MVVM 模式下命令与委托的关系。

什么是 MVVM

既然提及 MVVM,那么我们就无可避免的需要知道什么是 MVVM。我们在本文开篇已经提到,MVVM 这个概念最早由微软提出,具体来讲是由微软架构师 John Gossman 提出的。我个人更喜欢通过将 MVC、MVP 和 MVVM 这三者横向对比的方式来加强理解,因为这从某种意义上来讲,这是一个逐步改进和演化的过程。我们常常谈及软件的三层架构,我们常常对 MVC 耳濡目染以致将其神化,可事实上它们是某种在思想上无限接近的理念而已。

MVC模式示意图 MVC模式示意图

首先,我们从最简单的 MVC 开始说起,作为最常用的软件架构之一,我们可以从上面的图示中看到,MVC 其实是非常简单的一个概念,它由模型(Model)、视图(View)和控制器(Controller)三部分组成,建立在一个单向流动的通信基础上,即 View 通知 Controller 响应用户请求,Controller 在接到 View 的通知后会更新 Model 内的数据,然后 Model 会将新的数据反馈给 View。我们发现这个设计可以使软件工程中的关注点分离,我们注意到通过 MVC 模式,我们实现了视图和模型的分离,通过控制器这个胶水层让两者间接联系起来,所以 MVC 的优点是让各个模块更好的协作。那么,它的缺点是什么呢?显然,视图和控制器是高度耦合的,因为控制器中无可避免地要访问视图内的元素,所以控制器注定无法在这尘世间独善其身。要知道最早的 MVC 架构是基于观察者模式实现的,即当 Model 发生变化时会同时通知 View 和 Controller,所以我们很快就可以认识到:我们从古至今的所有努力,都是为了让视图和模型彼此分离,我们在这条路上越走越远,幸运的是一直都不忘初心。

MVP模式示意图 MVP模式示意图

接下来,我们为了彻底地让视图和模型分离,我们发明了新的软件架构:MVP。虽然从感性的认识上来讲,它是将 Controller 改名为 Presenter,然而从理性的认识上来讲,它在让视图和模型分离这件事情上做得更为决绝果断。通过图示我们可以发现,视图和模型不再发生直接联系,它们都通过 Presenter 相互联系,而且各个部分间的通信都变成了双向流动。我们可以很快意识到,现在全新的控制器即 Presenter 会变得越来越“重”,因为所有的逻辑都在这里,而视图会变得越来越“轻”,它不再需要主动去获取模型提供的数据,它将被动地接拥抱变化,因为现在在视图里基本上没有任何业务逻辑。现在我们可以预见,人类会在隔绝视图和模型这件事情上乘胜追击,人们会尝试让 Controller/Presenter/ViewModel 变得越来越臃肿,我想说的是,求它们在得知这一切真相时的心理阴影面积,我们试图让每一个模块各司其职、通力协作,结果脏活累活儿都交给了 Controller/Presenter/ViewModel,我想说这件事情做的真是漂亮。

MVVM模式示意图 MVVM模式示意图

历史总是如此的相似,人类在作死的道路上匍匐前进,继续发扬改名的优良传统,这一次是 Presenter 被改名为 ViewModel,在命名这件事情上,我认为程序员都是有某种强迫症因素在里面的,所以当你发现一个事物以一个新的名字出现在你的视野中的时候,通常它会有两种不同的结局,第一,陈酒换新瓶,我们贩卖的不是酒是情怀;第二,看今天的你我怎样重复昨天的故事,我这张旧船票还能否登上你的客船。幸运的是,MVVM 相对 MVP 的确发生了些许改变,一个重要的特性是双向绑定,View 的变化将自动反映在 ViewModel 中,而显然 ViewModel 是一个为 View 打造的 Model,它可以容纳更多的普通的 Model,因此从某种意义上来说,ViewModel 依然作为连接 View 和 Model 的桥梁而出现,它是对 View 的一种抽象,而抽象有两层含义,即数据(Property)和行为(Command),一旦你明白了这一点,ViewModel 无非是一个特殊而普通的类而已,特殊是因为它需要实现 INotifyPropertyChanged 接口,普通是因为它继承了面向对象编程(OOP)的基本思想。

更像 MVC 的 MVVM

到现在为止,我们基本上理解了 MVC、MVP 和 MVVM 这三者间的联系和区别,可是这样真的就是最好的结果吗?我们首先来思考一个问题,即什么样的代码应该写在控制器里。比如我们在对项目进行分层的时候,到底应该让控制器负责哪些任务?我们可以让 Controller 处理单独的路由,同样可以让 Controller 参与视图逻辑,甚至我们在编写 Model 的时候,我们可以有两种不同的选择,第一,编写一个简单的数据聚合实体,具体逻辑都交给控制器来处理,我们将这种方式称为贫血模型;第二,编写一个持有行为的数据聚合实体,控制器在业务逻辑中调用这些方法,我们将这种方式称为充血模型。所以,在这里我们纠结的地方,其实是选择让控制器更“重”还是让模型更“重”,我曾经接触过 1 年左右的 Android 开发,我认为 Android 工程是一个相对符合 MVC 架构的设计,可是我们难免会发现,作为控制器的 Activity 中的代码非常臃肿,因为我们在这里需要和视图、模型关联起来,所以综合现有的这些软件架构思想,我们发现模型和视图相对来讲都是可以复用的,可是作为连接这两者的 Controller/Presenter/ViewModel 是非常臃肿而且难以复用的,所以我怀疑我们是否是在真正的使用 MVVM。

我不知道 MVVM 架构正确的使用方法是什么样的,因为这是我第一次接触到这样一个新的概念,就如同很多年前,我在学校图书馆里看到的一本讲 Web 开发的书中描写的那样:当我们不了解 MVC 的时候,我们理所当然地认为通过文件夹将项目划分为 Model、View、Controller,这样好像就是 MVC 啦。可是事实真的是这样吗?以我目前公司项目的情况而已,我认为它更像是使用了双向绑定的 MVC,因为你经常可以在 ViewModel 中看到,某个属性的 Get 访问器中各种被 if-else 折磨的“脏”代码,而在 ViewModel 中我基本上看不到 Model 的身影,并且因为使用了 Binding 的概念严重弱化了 ViewModel 作为类的基本属性,因此它没有构造函数、没有初始化,我们可以在 Get 访问器中看到各种硬编码,因为视图上的需求经常变动,所以当整个项目结束的时候,我本人是非常不愿意去看 ViewModel 这部分的代码的,因为项目上要求避免写 Code-Behind 代码,所以大量的事件被 Command 和 UIEventToCommand 代替,这样让 ViewModel 变得更“重”了。原本我们希望的是让这三者各司其职,结果现在脏活累活儿全部变成了 ViewModel 一个人的。虽然双向绑定可以避免去写大量赋值语句,可是我知道 ViewModel 内心深处会表示:宝宝心里苦。

如果说 WPF 对技术圈最大的贡献,我认为这个贡献不在双向绑定,而是它真正意义上实现了设计和编程分离,我们必须承认设计和编程都是一项创造性活动,前者趋向感性,而后者趋向理想,在没有实现这两者分离的时候,程序员需要花费大量时间去还原设计师的设计,可是对程序员来讲,一段程序有没有界面设计在某些场合下是完全不重要的,在没有界面设计的情况下,我们可以通过单元测试来测试代码的可靠程度,相反地在有了界面设计以后我们反而不容易做到这一点,所以你问我 WPF 对技术圈最大的贡献是什么,我会回答它解放了程序员,可以让理性思维去做理性思维更适合的事情。我不太喜欢声明式编程,这里是指 WPF 中 XAML 这种继承自 XML 的标记语言,因为 Visual Studio 对 XAML 没有提供调试的支持,所以当你发现视图显示出现问题的时候,你很难分清楚是前台视图绑定出现错误还是后台 ViewModel 出现错误,只要你输入符合 XML 规范的内容程序都会编译通过而非引发异常,因为它是用反射所以性能问题广为人所诟病,其次 ViewModel 中通知前台属性发生变化时需要使用 OnPropertyChanged,该方法需要传入一个字符串类型的值,通常是指属性的名称,可是如果你定义了一个字符串类型的属性,当你在这里传入这个属性的时候,因为它是字符串类型所以不会引发编译错误,可是我觉得这个东西还是比较坑。

委托与命令

好了,现在我想说说 WPF 中的命令和委托,事实上在我计划写这篇文章前,我对这里无比好奇,可当我发现这东西的实质以后,我忽然觉得花费如此大的篇幅来讲解这样一个概念,这是不是会显得特别无聊。我们的项目上使用的是一个叫做 MVVM light 的框架,当然我们没有使用它的全部功能,公司的前辈们非常猥琐地从这个开源项目中挑了些源代码出来,这里我不想提及关于这个框架本身地相关细节,因为我认为理解问题的实质比学会一个框架更加重要。首先,WPF 为每一个控件都提供了一个 Command 的依赖属性,因为任何实现了 ICommand 接口的类都可以通过绑定的方式和前台关联起来,我们这里对比下命令和路由事件的区别可以发现,路由事件必须写在 Code-Behind 代码中,而命令可以写在 ViewModel 里,所以直观上来讲命令更加自由灵活。下面我们以一个简单的例子来剖析这两者间的关系。

我们知道使用 Command 需要实现 ICommand 接口,所以实现起来是相对容易的,我们这里继续沿用 MVVM light 中的 RelayCommand 这个名字:

public class RelayCommand : ICommand
{
    private readonly Action<object> m_execute;
    private readonly Predicate<object> m_canExecute;

    public RelayCommand(Action<object> execute)
    {
        this.m_execute = execute;
    }

    public RelayCommand(Action<object> execute, Predicate<object> canExecute)
    {
        this.m_execute = execute;
        this.m_canExecute = canExecute;
    }

    public bool CanExecute(object parameter)
    {
        if (m_canExecute == null)
            return true;

        return m_canExecute(parameter);
    }

    public event EventHandler CanExecuteChanged
    {
        add { CommandManager.RequerySuggested += value; }
        remove { CommandManager.RequerySuggested -= value; }
    }

    public void Execute(object parameter)
    {
        this.m_execute(parameter);
    }
}

我们可以看到这里有两个重要的方法,Execute 和 CanExecute,前者是一个 void 类型的方法,后者是一个 bool 类型的方法。当我们需要判断控件是否应该执行某一个过程的时候,CanExecute 这个方法就可以帮助我们完成判断,而 Execute 方法显然是执行某一个过程的方法,可以注意到通过委托我们让调用者更加自由和灵活地传入一个方法,这是我喜欢这种设计的一个地方,因为我的一位同事就对普通的路由事件表示无法理解。

这里需要说明的是 CanExecuteChanged 这个事件,这个和 INotifyPropertyChanged 接口中的 PropertyChanged 成员类似,是在当 CanExecute 发生变化的时候通知视图的,我对这里的理解是 CanExecute 本身就具备对某一个过程是否应该被执行的支持,可是遗憾的是在,在我参与的项目中,人们更喜欢声明大量的布尔类型变量来处理这里的相关逻辑,因此无论是对 Property 还是 Command 而言,在 ViewModel 里都是看起来非常丑陋的代码实现。

好了,现在对我们而言,这是一个非常愉快的旅程,因为在完成对 RelayCommand 的定义以后,我们绑定命令和定义命令的过程是非常简单的。除此以外,WPF 提供了一个 RoutedCommand 类,该类实现了 ICommand 接口,我怀疑 MVVM light 中的 EventToCommand 正是通过这种思路实现了路由事件到命令的转换,因为只有 RoutedCommand 具备访问 UI 事件的能力,这里我们仅仅提出问题,进一步的思考和验证我们可以留到以后去做。下面我们来看看如何声明和绑定命令:

public RelayCommand ClickCommand
{
    get
    {
        return new RelayCommand((arg)=>
        {
            MessageBox.Show("Click");
        });
    }
}

显然这个 ClickCommand 将作为一个属性出现在 ViewModel 中,我选择了一个我最喜欢用的方法,或许这样看起来非常低端。可是在调试界面的过程中,它要比断点调试更为直接和直观。当我们的 ViewModel 中出现这样的只读属性的时候,直接在 Get 访问器中定义它的返回值似乎是最直接有效的方案,可问题是 Get 访问器应该是非常“轻”的,因为大量业务逻辑的渗透,现在连这里都不能保留其纯粹性了吗?这让我表示非常郁闷啊。

<Window x:Class="WPFLearning.Window1"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Window" Height="300" Width="300">
    <Grid>
        <Button Content="Button" HorizontalAlignment="Center" 
        	VerticalAlignment="Center" Command="{Binding ClickCommand }"/>  
    </Grid>
</Window>

现在你可以发现,委托和命令结合得非常好,当你发现这一切如此美妙的时候,回归本质或许是我们最喜欢的事情,就像纯粹的你我一样,在这个世界上,我们彼此装点着各自生命里美好的风景,执著而勇敢、温暖而明媚,那些周而复始的日子里,总能听到梦想开花的声音。

小结

在这篇文章里我们讨论了 MVC、MVP、MVVM 各自架构变迁的前因后果,由此我们知道了软件设计中,一个典型的设计目标是让视图和模型分离,可我们同样发现,带着这个目标去设计软件的时候,我们基本鲜有更换视图的时候,虽然从理论上来讲,所有的业务逻辑都是在 ViewModel 中,视图和模型应该是可以进行更换的,可是你告诉我,有谁会为同一个软件制作不同的界面呢?难道我们还能期望通过一个静态工厂,来为不同的平台返回不同的视图,然后理论上只要适配正确的控制器就可以实现软件对不同平台的“自适应”,可是软件开发领域发展至今,最有可能提供完整跨平台方案的 Web 技术目前都无法满足这个需求,所以我们是否应该去怀疑这个设计的正确性呢?同样的,以 Java 的 SSH 三大框架为代表的“配置文件”流派,认为应该将数据库的相关信息写在配置文件里,这样可以满足我们随时切换到不同数据库产品上的需要,可是你告诉我,这样的应用场景多吗?所以,技术本身的设计并没有问题,我们需要思考的是,是否应该被框架和架构束缚,说到底我们是为了设计出更棒的软件产品,以此为目标,其实框架和架构更应该衍生为一种哲学意义上的思想,我们想让每一行代码都充满智慧的光芒,它骄傲却不孤独,因为总有人理解它、懂它。