去model化和数据对象

简介: 原文简述去model化这个说法其实有点儿难听,model化就是使用数据对象,去model化就是不使用数据对象。所以这篇文章主要讨论的问题就是:数据传递时,是否要采用数据对象?这里的数据传递并不是说类似RPC的场景,而是在单个工程内部,各对象之间、各组件之间、各层之间的数据传递。

原文

简述

去model化这个说法其实有点儿难听,model化就是使用数据对象,去model化就是不使用数据对象。所以这篇文章主要讨论的问题就是:数据传递时,是否要采用数据对象?这里的数据传递并不是说类似RPC的场景,而是在单个工程内部,各对象之间、各组件之间、各层之间的数据传递。

所谓数据对象,就是把不同类型的数据映射到不同类型的对象上,这个对象仅用于表达数据,数据通过对象的property来体现。瘦Model、贫血模型就属于这一类。

去Model化,就是不使用特定对象迎合特定数据的映射的方式,来表达数据。比如我们可以使用NSDictionary,或者其他手段例如reformer、virtual record,来避免这种数据映射对象。

关于这个问题的讨论涉及以下内容:

  • 如何理解面向对象思想
  • 为什么不使用数据对象
  • 去Model化都有哪些手段

通过以上三点,我希望能够帮助大家建立对面向对象的正确理解,让大家明白如何权衡是否要采用对象化的设计。以及最终当你决定不采用对象化思想而采用非对象化思想时,应该如何进行架构设计。

如何理解面向对象思想

面向对象的思想简单总结一下就是:将一个或多个复杂功能封装成为一个聚合体,这个聚合体经过抽象后,仅暴露少部分方法,这些方法向外部获取实现功能所需要的条件后,就能完成对应功能。传统的面向过程只针对功能的实现做了封装,也就是函数。经过这层封装后,仅暴露参数列表和函数名,用于外部调用者调用并完成功能。

我们可以推导出:函数封装了实现功能所需要的代码,因此对象实质上就是再次对函数进行了封装。将函数集合在一起,形成一个函数集合,面向对象思想的提出者把这个函数集合称之为对象,把对象的概念从理论映射到实际的工程领域,我们也可以叫它

然而我们很快就能发觉,只是单纯地把函数集合在一起是不够的,这些函数集有可能互相之间需要共用参数或共享状态。因此面向对象的理论设计者让对象自己也能够提供属性(property),来满足函数集间共用参数和共享状态的需求。这个函数集现在有了更贴切的说法:领域。因此当这个领域中的个别函数不需要共用参数或共享状态,仅仅是提供功能时,这些相关函数就可以体现为类方法。当领域里的函数需要共用参数或共享状态时,这些函数的体现就是实例方法。

这里补充一下,领域的概念我们更多会把它理解得比较大,比如多个相关对象形成一个领域。但一个对象自身所包含的所有函数也是一个领域,是大领域里的一个子领域。

以上就是一个对面向对象思想的朴素理解。在这个理解的基础上,还衍生出了非常多的概念,不过这并不在本文的讨论范围中。

总之,一个对象事实上是对一个较小领域的一种封装。对应到本文要讨论的问题来看,如果拿一个对象去表达一套数据而非一个领域,这在一定程度上是违背面向对象的设计初衷的。你看着好像是把数据对象化了,就是面向对象编程了,然而事实上并非如此。Martin Fowler早年也在他的《Anemic Domain Model》中提出了一样的看法:

The fundamental horror of this anti-pattern is that it's so contrary to the basic idea of object-oriented design; which is to combine data and process together. The anemic domain model is really just a procedural style design, exactly the kind of thing that object bigots like me (and Eric) have been fighting since our early days in Smalltalk. What's worse, many people think that anemic objects are real objects, and thus completely miss the point of what object-oriented design is all about.

为什么不使用数据对象

根据上一小节的内容,我们可以把对象抽象地理解为一个函数集,一个领域。在这一节里,我们再进一步推导:如果这些函数集里的所有函数,并不都是处在同一个问题领域内,那么面向对象的这种实践是否依旧成立?

答案是成立的,但显然我们并不建议这么做。不同领域的函数集如果被封装在了一起,在实际工程中,这种做法至少会带来以下问题:

  1. 当需要维护某个问题领域内的函数时,如果修改到某个需要被共用的参数或者需要被共享的对象,那么其他问题领域也存在被影响的可能。牵一发而动全身,这是我们不希望看到的。
  2. 当需要解决某个问题时,如果引入了某个充满了各种不同问题领域的函数集,这实质就是引入了对不同问题领域解决方案的依赖。当需要做代码迁移或复用时,就也要把不相关的解决方案一并引入。拔出萝卜带出泥,这也是我们不希望看到的。

当需要维护某个问题领域内的函数时,如果修改到某个需要被共用的参数或者需要被共享的对象,那么其他问题领域也存在被影响的可能。牵一发而动全身,这是我们不希望看到的。

我们在进行对象化设计时,必须要分割好问题域,才能保证设计出良好的架构。

业界所谓的各种XX建模、XX驱动设计、XXP,大部分其实都是在强调合理分割这一点,他们提供不同的方法论去告诉你应该如何去做分割的事情,以及如何把分割出来的部分再进一步做封装。然而这些XX概念要成立的话,就都一定需要具备这样一个前提条件:一个被封装出来的对象的活动领域,必须要小于等于当前被分割出来的子问题领域。如果不符合这个前提条件的话,一个大的问题领域即使被强行分割成各种小的问题领域,这些小的问题领域还是依旧难以被封装成为对象,因为对象的跨领域活动势必就要引入其它领域的问题解决方案,这就使得分割名不副实。

然而,一个被封装出来的对象的活动领域,必须要小于等于当前被分割出来的子问题领域这个前提在实际业务场景实践中,是否一定成立呢?如果一定成立的话,那么这种做法和这些方法论就是没问题的。如果在某些场景中不成立,对象化设计在这些场景就有问题了。

事实上,这个前提在实际业务场景中,是不一定成立的。在实际业务场景中,一个数据对象被多个业务领域使用是非常常见的。一个数据对象在不同层、不同模块中被使用也是非常常见的。所以,如果两个业务对象之间需要传递的仅是数据,在这个场景下就不适合传递对象化的数据。

当需要解决某个问题时,如果引入了某个充满了各种不同问题领域的函数集,这实质就是引入了对不同问题领域解决方案的依赖。当需要做代码迁移或复用时,就也要把不相关的解决方案一并引入。拔出萝卜带出泥,这也是我们不希望看到的。

这种场景其实就很好理解了。实际工程中,对象化数据往往不是一个独立存在的对象,而是依附于某一个领域。例如持久层提供的对象化数据,往往依附于持久层。网络层提供的对象化数据往往依附于网络层。当你的业务层某个模块使用来自这些层的对象化数据时,将来要迁移这个模块,就必须不得不把持久层或者网络层也跟着迁移过去。迁移发生的场景之一就是大型工程的组件化拆分,实施组件化时遇到这种问题是一件非常伤脑筋的事情。

小结

所以,在数据传递时,我不建议采用对象化设计,尤其是数据传递的两个实体是跨层实体或者跨模块实体时,对象化设计对架构的伤害非常大。

从实际而非理论的角度上讲,数据对象的使用主要存在这些问题:

  1. 数据对象并不符合面向对象的设计初衷
  2. 数据对象有变为支持多领域对象的可能
  3. 数据对象使得领域间依赖性变强

去Model化都有哪些手段

字典流

这种做法是最原始最简单的做法,我就不多说了。

reformer

reformer是这样的工作原理:

                        ------------------      ------------------
                        |                |      |                |
                       .|   Reformer_A   | .... |     View_A     |
                      . |                |      |                |
                     .  ------------------      ------------------
                    .
------------------ .    ------------------      ------------------
|                |.     |                |      |                |
|   APIManager   |......|   Reformer_B   | .... |     View_B     |
|                |.     |                |      |                |
------------------ .    ------------------      ------------------
                    .
                     .  ------------------      ------------------
                      . |                |      |                |
                       .|   Reformer_C   | .... |     View_C     |
                        |                |      |                |
                        ------------------      ------------------

APIManager提供了来自网络层的数据。Reformer_A,Reformer_B,Reformer_C,分别代表不同的领域。View_A,View_B,View_C,分别就是各领域对不同的数据应用之后产生的结果。在讲网络层的文章中,我设计了reformer的方式来实现非对象化。更详细的讲述和实际的Demo文章里都有,我在这里就不多说了。

Virtual Record

Virtual Record事实上把reformer和某个领域相关对象集合在了一起。Virtual Record和reformer的区别在于:reformer更加有利于单数据对应多对象的场景,Virtual Record更加有利于多数据对单对象的场景

------------------      ------------------
|                |      |                |
|  DataCenter_A  | .....| VirtualRecordA |.
|                |      |                | .
------------------      ------------------  .
                                             .   
------------------      ------------------    .  ------------------
|                |      |                |     . |                |
|  DataCenter_B  |......| VirtualRecordB |.......|     View_B     |
|                |      |                |     . |                |
------------------      ------------------    .  ------------------
                                             . 
------------------      ------------------  .
|                |      |                | .
|  DataCenter_C  | .....| VirtualRecordC |.
|                |      |                |
------------------      ------------------

事实上这幅图有个地方画的不太贴切,Virtual Record其实只是View_B的一个protocol,它并不是一个实例,所以才Virtual。关于Virtual Record的详细解释和案例,在讲持久层的文章里有。

总结

将数据对象化事实上是一个不符合面向对象思想的做法。

这种说法看起来很反直觉,但事实上如果你对面向对象有深入的理解,就能够明白其中的原因。这种不符合面向对象思想的做法,也导致了工程实践上代码的高耦合和组件难以复用的情况,这都是我们不希望看到的。我在这篇文章里提供了几种去Model化的做法,但看起来这应该不是所有的手段,很有可能还有其它方法。未来如果我遇到了其他场景想到了其它方法的话,会对它进行补充。如果各位读者还有不同的方法或其它的问题,也欢迎在评论区一起交流。

目录
相关文章
|
JSON 数据格式
LangChain-02 JsonOutputParser
LangChain-02 JsonOutputParser
125 2
|
11月前
|
机器学习/深度学习 人工智能 编解码
OminiControl:AI图像生成框架,实现图像主题控制和空间精确控制
OminiControl 是一个高度通用且参数高效的 AI 图像生成框架,专为扩散变换器模型设计,能够实现图像主题控制和空间精确控制。该框架通过引入极少量的额外参数(0.1%),支持主题驱动控制和空间对齐控制,适用于多种图像生成任务。
283 10
OminiControl:AI图像生成框架,实现图像主题控制和空间精确控制
|
11月前
|
搜索推荐 数据安全/隐私保护 UED
产品经理-B 端与C端
B端与C端是IT互联网产品经理的类型划分,分别面向企业和个人消费者。C端产品如微信、淘宝,注重用户体验和快速迭代;B端产品如CRM系统、ERP软件,强调功能复杂性和定制化服务。此外,还有G端产品,主要服务于政府机构,注重数据安全和合规性。产品经理起源于20世纪20年代末的美国宝洁公司,随着互联网的发展,该角色在IT领域变得愈加重要。
1378 12
|
12月前
|
人工智能 自然语言处理
大模型在装傻!谷歌苹果最新发现:LLM知道但不告诉你,掌握知识比表现出来的多
在AI领域,大模型(LLM)展现出了惊人的进步,但在谷歌和苹果的最新研究中,发现这些模型有时会故意“装傻”,即使已知正确答案也不告知用户。这种“隐藏智慧”现象揭示了大模型可能具备超出表面表现的深层能力,对AI评估与应用提出了新挑战,同时也带来了设计更高效模型的新机遇。论文链接:https://arxiv.org/pdf/2410.02707
198 11
|
JavaScript
uniapp-----封装接口
uniapp-----封装接口
241 0
uniapp-----封装接口
|
人工智能 编解码 Cloud Native
微软发布 .NET 8 开源开发平台:引入 PGO、AVX-512 支持,性能提升 20%
对企业来说特别重要的是,.NET 8 是一个长期支持 (LTS) 版本,这意味着它将获得三年的支持和补丁,而标准期限支持 (STS) 版本则是 18 个月。对于开发人员来说,特别重要的是 .NET 团队正在向期待已久的原生提前编译(NativeAOT)迈进 。
461 2
|
存储 Kubernetes Cloud Native
云原生|kubernetes|centos下安装部署kubeapps以及简单的使用示例
云原生|kubernetes|centos下安装部署kubeapps以及简单的使用示例
358 0
|
存储 算法 Java
带你了解JDK
JDK(Java Development Kit)是Java开发工具包,它提供了开发和运行Java应用程序所需的工具、库和资源。下面是JDK的一些重点介绍: 1. Java编译器(javac):JDK包含了Java编译器,可以将Java源代码编译为Java字节码。通过编译器,开发人员可以将Java源代码转换为可在JVM上运行的字节码文件。 2. 核心类库(Core Libraries):JDK提供了丰富的核心类库,其中包含了常用的类和接口,用于处理字符串、集合、IO、网络通信等各种操作。开发人员可以利用这些类库来构建功能丰富的Java应用程序。 3. 调试工具(Debugging Tools)
362 0
|
芯片
LED 是如何发光的?工作原理及种类介绍
发光二极管( LED) 是一种半导体器件,当电流通过时会发出可见光。LED 用于各种应用,包括电子设备上的指示灯、交通信号和照明标志。在本文中,我们将讨论 LED 的工作原理、特性和应用。
1401 0
|
存储 easyexcel Java
springboot使用EasyExcel实现excel导出千万大数据量
# 介绍 EasyExcel是一个基于Java的、快速、简洁、解决大文件内存溢出的Excel处理工具。 他能让你在不用考虑性能、内存的等因素的情况下,快速完成Excel的读、写等功能。 Java解析、生成Excel比较有名的框架有Apache poi、jxl。但他们都存在一个严重的问题就是非常的耗内存,poi有一套SAX模式的API可以一定程度的解决一些内存溢出的问题,但POI还是有一些缺陷,比如07版Excel解压缩以及解压后存储都是在内存中完成的,内存消耗依然很大。 easyexcel重写了poi对07版Excel的解析,一个3M的excel用POI sax解析依然需要100M左右内存,
6394 0
下一篇
开通oss服务