您现在的位置是:首页 > 文章详情

领域驱动设计(DDD):对象属性(property)和 getters , setters 方法

日期:2020-12-08点击:471

对象属性(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 中我们可以使用 getset 关键字来封装属性:

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 中我们可以使用 getset 关键字来封装属性:

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)提供 getset 关键字,而是将其设计成方法。 使用 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; } } 

本来在拥有 getset 关键字的编程语言里,大家只是对 property 与 field 有些混淆,这样的混淆还是可以很简单的解释清楚。但是在 Java 中由于直接使用方法(getXxx , setXxx )来封装属性(property)使得大家对 field , property 和 method 三者混淆起来。在有些时候大家不知道 getXxxsetXxx 方法是在做对象的属性(property),所以很多人误认为字段(field)便是属性(property)。尤其是在应用系统开发中许多模型的属性不需要做多余封装,只是直白的存在。

当把字段(field)误认为是属性(property)以后,在遇到需要为某一个对象的属性进行封装时,往往会使用其它方法来解决。比如:changeXxx 方法。

在拥有 getset 关键字的编程语言里,在使用 get 或者 set 关键字时,在编译器在编译代码时,依然会将 get 或者 set 关键字所做的对属性(property)的封装转换成读(read , get)方法或者写(write , set)方法,所以getset 关键字只是对 getXxxsetXxx 方法的一种语法糖。

在 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 修饰的字段将带来的是 getset 都具有可读写的权限,这就使得使用者可以设置(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)最根本的区别。在区分属性和字段的区别时,是依据他们的所具有的功能来判断的。

尤其是在具有 getset 关键字以及对属性(property)和字段(field)没有区分(如:命名规范、使用方式)的编程语言里,区分属性和字段只需要简单的通过是否具有封装性读写控制来区分。

属性(property) 字段(field)
封装性 具有封装功能 不具有封装功能
读写控制 可以细分控制 不能细分控制

实体对象属性校验方式

嘻嘻,过两天再说。~~~~

开源电商

Mallfoundry 是一个完全开源的使用 Spring Boot 开发的多商户电商平台。它可以嵌入到已有的 Java 程序中,或者作为服务器、集群、云中的服务运行。

  • 领域模型采用领域驱动设计(DDD)、接口化以及面向对象设计。

项目地址:https://gitee.com/mallfoundry/mall

总结

属性(property)与字段(field)有区别,总的来说是两个方面:封装性和访问控制。

一个对象是由属性和方法组成的,所以认识属性时需要知道属性是具有封装性的。而不能只认识到只有方法需要封装,属性一样需要封装。、当属性和方法都具有封装性时,在使用具有属性和方法的对象时才不会极化。

原文链接:https://my.oschina.net/u/4704424/blog/4781204
关注公众号

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。

持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。

转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。

文章评论

共有0条评论来说两句吧...

文章二维码

扫描即可查看该文章

点击排行

推荐阅读

最新文章