领域驱动设计(DDD):对象属性(property)和 getters , setters 方法
对象属性(property)和 getters , setters 方法
“需要为一个对象的属性添加 Getters / Setters 方法”而提出为什么?由此而进行深入思考。
它是字段(field)
在 Java 中我们都知道如何在类(Class)中声明一个成员属性(field)。
public class HikariConfig { public long connectionTimeout; public long validationTimeout; }
当我们需要设置对象的属性值时,我们可以直接使用 =
赋值。
public class HikariConfigTests { public static void main(String[] args) { var config = new HikariConfig(); config.connectionTimeout = 250; config.validationTimeout = 250; } }
如果我们需要在设置 connectionTimeout
属性时,做一些赋值校验。比如:connectionTimeout 不能小于 250ms 。
public class HikariConfigTests { public static void main(String[] args) { var config = new HikariConfig(); var connectionTimeoutMs = 250; if (connectionTimeoutMs < 250) { throw new IllegalArgumentException("connectionTimeout cannot be less than 250ms"); } config.connectionTimeout = connectionTimeoutMs; } }
属性(property)具有封装性
面向对象有三大特性:继承、封装、多态。
我们应该已经发现校验 connectionTimeout 的逻辑(代码)被放置在 HikariConfig 对象自身之外,但从面向对象的角度来说如校验属性的代码应该放在 connectionTimeout 上,但是字段(field)不具备封装性。
如果你发现了这个问题,那么面向对象的设计者们也一样会发现这个问题。
当听到属性这个词时,你想到的是什么呢?
- 你可能想到的是字段(field),因为 field 常常会被翻译为成员属性(field)。
- field 真正要表达的意思是:一块存放数据区域。
一个对象是由属性和操作组成的。操作可以被封装成一个方法:
public interface Runnable { void run(); }
如果操作可以被封装成方法,那么如何封装属性呢?
现代的编程语言为使用者提供了一些语法糖来封装属性,比如:C# , Kotlin , Typescript , Scala 等等。
在 Kotlin 中我们可以使用 get
和 set
关键字来封装属性:
class HikariConfig { var connectionTimeout: Long = 0 set(value) { if (value < 250) { throw IllegalArgumentException("connectionTimeout cannot be less than 250ms") } field = value } }
在 Kotlin 中使用属性:
fun main() { val config = HikariConfig() config.connectionTimeout = 250 }
在 Typescript 中我们可以使用 get
和 set
关键字来封装属性:
class HikariConfig { #connectionTimeout: number public get connectionTimeout() { return this.#connectionTimeout } public set connectionTimeout(connectionTimeout) { if (connectionTimeout < 250) { throw new Error("connectionTimeout cannot be less than 250ms"); } this.#connectionTimeout = connectionTimeout } }
在 Typescript 中使用属性:
const config = new HikariConfig() config.connectionTimeout = 250
在 Java 中并没有为属性(property)提供 get
和 set
关键字,而是将其设计成方法。 使用 getXxx
方法来模拟 get
关键字和使用 setXxx
方法来模拟 set
关键字。
class HikariConfig { private long connectionTimeout; public long getConnectionTimeout() { return this.connectionTimeout; } public void setConnectionTimeout(long value) { if (value < 250) { throw new IllegalArgumentException("connectionTimeout cannot be less than 250ms"); } this.connectionTimeout = value; } }
本来在拥有 get
和 set
关键字的编程语言里,大家只是对 property 与 field 有些混淆,这样的混淆还是可以很简单的解释清楚。但是在 Java 中由于直接使用方法(getXxx
, setXxx
)来封装属性(property)使得大家对 field , property 和 method 三者混淆起来。在有些时候大家不知道 getXxx
和 setXxx
方法是在做对象的属性(property),所以很多人误认为字段(field)便是属性(property)。尤其是在应用系统开发中许多模型的属性不需要做多余封装,只是直白的存在。
当把字段(field)误认为是属性(property)以后,在遇到需要为某一个对象的属性进行封装时,往往会使用其它方法来解决。比如:changeXxx
方法。
在拥有 get
和 set
关键字的编程语言里,在使用 get
或者 set
关键字时,在编译器在编译代码时,依然会将 get
或者 set
关键字所做的对属性(property)的封装转换成读(read
, get
)方法或者写(write
, set
)方法,所以get
和 set
关键字只是对 getXxx
和 setXxx
方法的一种语法糖。
在 Typescript 中,编译器最终会将 get
或者 set
关键字最终编译成这样:
Object.defineProperty(config, "connectionTimeout", { configurable: false, enumerable: false, set: function (connectionTimeout) { if (connectionTimeout < 250) { throw new Error("connectionTimeout cannot be less than 250ms"); } this.#connectionTimeout = connectionTimeout; }, get: function () { return this.connectionTimeout; } })
在 Kotlin 中,编译器最终会将 get
或者 set
关键字编译成这样:
class HikariConfig { private long connectionTimeout; public long getConnectionTimeout() { return this.connectionTimeout; } public void setConnectionTimeout(long value) { if (value < 250) { throw IllegalArgumentException("connectionTimeout cannot be less than 250ms"); } this.connectionTimeout = value; } }
在其它拥有 get
或者 set
关键字的编程语言(C# , Scala)里一样会将其编译成某种格式的方法来完成对属性的封装性。
属性具有读(read)和写(write)权限
在 Java 中提供了四个访问控制修饰符( public , protected , default , private ),他们可以修饰类(class)、方法(method)以及字段(field)。需要更深入的了解到它们只是在控制一定的范围,比如在创建(new)一个对象时,是在控制可以在哪个包(package)内去创建这个对象。比如在使用方法时,也是在控制可以在哪个包内去使用这个方法。同样在使用字段(field)时也是在控制可以在哪个范围内使用。
这些访问(access)控制修饰符应该作用在被修饰的动作(动词)上,而不是名称(名词)。比如:对象的创建,方法的调用,字段的获得与设置。创建(new)、调用(invoke)、获得(get)、设置(set)这样的动作都需要通过访问修饰符做到精确控制。对于一个类(class)在使用时只有一个创建(new)的动作,同样的使用方法时也只有一个调用(invoke)的动作,不需要再次精确细分。而对于字段(field)在使用时有两个动作:获得(get)和设置(set),而字段(field)本身在处理这两个动作时并没有办法做到细分。
现在的问题是同一个访问控制修饰符同时控制对某一个字段(field)的两个动作( get , set ),而这个问题需要发现者仔细思考:
class HikariConfig { connectionTimeout: number } const config = new HikariConfig() config.connectionTimeout = 100 // Set const timeout = config.connectionTimeout // Get
如果此时把 public
修改为 protected
,那么操作 connectionTimeout
的两个动作( get , set )的访问控制将全部变成 protected 。
class HikariConfig { protected connectionTimeout: number }
我们可以发现一个字段的两个动作( get , set )的访问权限被混合到了一起,这带来了什么问题呢?
- 一个对象的属性只是想对外提供公共读(public read),对外不提供公共写(private write)。
- 一个对象的属性只是想对外提供公共写(public write),对外不提供公共读(private read)。
- ......
简单来说就是:可读、可写、只读、只写、不可读写。
如果一个对象的属性只是想对外提供只读属性(注意是对外,对象的内外有区别),而被 public
修饰的字段将带来的是 get
和 set
都具有可读写的权限,这就使得使用者可以设置(set)这个字段。这将给对象带来意向不当的后果,有可能是破坏性的后果。
因此为一个对象的属性( get
, set
) 提供不同访问控制是有必要的。
class HikariConfig { #connectionTimeout: number // private public get connectionTimeout() { // public , protected , default , private return this.#connectionTimeout } public set connectionTimeout(connectionTimeout) { // public , protected , default , private this.#connectionTimeout = connectionTimeout } }
属性可以无读(写)操作
一个属性(property)有两个操作:读(read)和写(write)。可以将一个对象的属性的读写权限修改为 private 。私有的访问控制权限并不意味着不存在,只是表明这个属性只可以在这个对象内部使用,对外不可使用。同样的属性可以具有无读(写)操作。
无写(write)操作:
class HikariConfig { #connectionTimeout: number public get connectionTimeout() { return this.#connectionTimeout } // 没有 set 方法,无 set 与私有 set 的区别。 }
无读(read)操作:
class HikariConfig { #connectionTimeout: number // 没有 get 方法,无 get 与私有 get 的区别。 public set connectionTimeout(connectionTimeout) { this.#connectionTimeout = connectionTimeout } }
一个属性不能同时没有读和写方法(操作),如果同时没有也就表明这个属性不存在。
区分属性(property)和字段(field)
封装性和读写访问控制是属性(property)和字段(field)最根本的区别。在区分属性和字段的区别时,是依据他们的所具有的功能来判断的。
尤其是在具有 get
和 set
关键字以及对属性(property)和字段(field)没有区分(如:命名规范、使用方式)的编程语言里,区分属性和字段只需要简单的通过是否具有封装性和读写控制来区分。
属性(property) | 字段(field) | |
---|---|---|
封装性 | 具有封装功能 | 不具有封装功能 |
读写控制 | 可以细分控制 | 不能细分控制 |
实体对象属性校验方式
嘻嘻,过两天再说。~~~~
开源电商
Mallfoundry 是一个完全开源的使用 Spring Boot 开发的多商户电商平台。它可以嵌入到已有的 Java 程序中,或者作为服务器、集群、云中的服务运行。
- 领域模型采用领域驱动设计(DDD)、接口化以及面向对象设计。
项目地址:https://gitee.com/mallfoundry/mall
总结
属性(property)与字段(field)有区别,总的来说是两个方面:封装性和访问控制。
一个对象是由属性和方法组成的,所以认识属性时需要知道属性是具有封装性的。而不能只认识到只有方法需要封装,属性一样需要封装。、当属性和方法都具有封装性时,在使用具有属性和方法的对象时才不会极化。

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
#2020征文-开发板#鸿蒙liteos-a如何启动第一个用户进程init_lite
目录: 1. 鸿蒙OS编译知识 2. 从编译过程看鸿蒙OS代码结构 3. 第一个用户态进程init_lite 4. Init_lite是如何被kernel调用的? 1、鸿蒙OS编译知识 OpenHarmony源码编译系统使用了google开发的gn工具以及ninjia。这二者结合起来比传统的makefile编译系要高效,尤其适合大系统的并行编译。对开发者而言,如果要参与OpenHarmony的开发,需要对gn的语法有些了解。本文仅做一些最基本的介绍: 1.使用gn工具的话,开发者将编译规则写在名为BUILD.gn文件中。和Makefile一样,gn文件有自己的语法规则,属于领域语言(Domain Specific Language,DSL)。gn语法不难,但编译规则本身有很多内容,所以一下子要掌握全部内容也不容易。 2.gn支持自定义模板函数,可放在名为.gni的文件中。OpenHarmony中最常见到的gn模板文件为./build/lite/config/component/lite_component.gni。.gn文件中通过import可导入gni模板文件。OpenHarmony...
- 下一篇
领域驱动设计(DDD)前夜:面向过程与面向对象思维
面向过程与面向对象思维 在大多数的情况下,我们都是从面向过程的语言(C语言)开始学起编程,然后是进入到面向对象的语言中,比如 Java、C#、Python 等。但在使用面向对象编程时,有可能依然保留着部分面向过程的思维,或者存在一些错误地面向对象思维。 下面我将通过两个示例来对比面向过程与面向对象思维的不同,并在每个示例实现后,再举一个实际示例和错误示例来说明两个问题: 在面向对象编程中会存在一些过程化的脚本编码。 对象建模中会存在一些对象建模错误问题的。 在描述面向过程与面向对象的区别时,有一个经典的例子叫做《把大象装进冰箱》: 人把冰箱门打开 人把大象装进去 人把冰箱门关上 然而又演变出另外一个版本《把大象走进冰箱》: 人把冰箱门打开 大象走进冰箱 人把冰箱门关上 这两个版本一个是把大象装进冰箱,另一个是大象自己走进冰箱。在使用面向过程和面向对象实现这两个用例时,你应该全部实现出来,也就是说应该使用面向过程分别实现这两个用例:装进冰箱和走进冰箱,使用面向对象也分别实现这两个用例:装进冰箱和走进冰箱。然后再去横向对比,使用面向过程实现的“把大象装进冰箱”与使用面向对象实现的“大象装进...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- SpringBoot2更换Tomcat为Jetty,小型站点的福音
- CentOS8编译安装MySQL8.0.19
- SpringBoot2整合Thymeleaf,官方推荐html解决方案
- Hadoop3单机部署,实现最简伪集群
- Springboot2将连接池hikari替换为druid,体验最强大的数据库连接池
- CentOS7设置SWAP分区,小内存服务器的救世主
- SpringBoot2全家桶,快速入门学习开发网站教程
- SpringBoot2编写第一个Controller,响应你的http请求并返回结果
- Docker快速安装Oracle11G,搭建oracle11g学习环境
- Windows10,CentOS7,CentOS8安装Nodejs环境