【问题标题】:Do I understand not using getters and setters correctly我是否理解没有正确使用 getter 和 setter
【发布时间】:2018-04-13 08:05:21
【问题描述】:

在阅读了 Yegor 关于不使用 getter 和 setter 的 this 文章后,我觉得这很有意义。

请注意,这个问题不是关于这样做是否更好/最差,只有在我正确实施的情况下

我想知道在 VBA 中的以下两个示例中,我是否正确理解了这个概念,以及我是否正确应用它。

标准方法是:

Private userName As String

Public Property Get Name() As String
    Name = userName
End Property
Public Property Let Name(rData As String)
    userName = rData
End Property

在我看来他的方式是这样的:

Private userName As String

Public Function returnName() As String
    returnName = userName
End Function

Public Function giveNewName(newName As String) As String
    userName = newName
End Function

从上面两个例子中我了解到,如果我想更改 userName 的格式(假设以全大写形式返回),那么我可以使用第二种方法来做到这一点,而无需更改方法的名称通过它给出名称 - 我可以让 returnName 指向 userNameCaps 属性。我程序中的其余代码仍然可以保持不变并指向方法 userName。

但是如果我想在第一个示例中执行此操作,我可以创建一个新属性,但随后必须在程序中的任何地方更改我的代码以指向新属性...对吗?

换句话说,在第一个示例中,API 从属性中获取信息,在第二个示例中,API 从方法中获取信息。

【问题讨论】:

    标签: vba get set


    【解决方案1】:

    您的第二个 sn-p 既不惯用也不等同。您链接到的那篇文章是关于 Java 的,这是一种没有任何对象属性概念的语言 - getFoo/setFoo 只是 Java 中的约定

    在 VBA 中:

    Private userName As String
    
    Public Property Get Name() As String
        Name = userName
    End Property
    Public Property Let Name(rData As String)
        userName = rData
    End Property
    

    最终等价于这个:

    Public UserName As String
    

    不相信?将这样的公共字段添加到类模块中,例如Class1。然后添加一个新的类模块并添加:

    Implements Class1
    

    编译器会强制你实现一个Property Get和一个Property Let成员,这样Class1接口契约就可以实现了。

    那么,为什么还要为属性而烦恼呢?属性是一种工具,可以帮助封装

    Option Explicit
    Private Type TSomething
        Foo As Long
    End Type
    Private this As TSomething
    
    Public Property Get Foo() As Long
        Foo = this.Foo
    End Property
    
    Public Property Let Foo(ByVal value As Long)
        If value <= 0 Then Err.Raise 5
        this.Foo = value
    End Property
    

    现在,如果您尝试为 Foo 分配负值,您将收到运行时错误:该属性封装只有类知道并且能够改变的内部状态:调用代码看不到或不知道封装的值——它只知道Foo 是一个读/写属性。 “setter”中的验证逻辑确保对象始终处于一致状态。

    如果你想把一个属性分解成方法,那么你需要一个Function作为getter,赋值是Sub而不是Function。事实上,Rubberduck 会告诉您,giveNewName 的返回值存在问题,从未分配过:这比“OMG 你正在使用属性!”更糟糕代码气味

    函数返回值。子/方法做某事 - 在对象/类的情况下,某事可能意味着改变内部状态。

    但是通过避免 Property Let 只是因为一些 Java 人说 getter 和 setter 是邪恶的,你只是让你的 VBA API 比它需要的更混乱 - 因为 VBA 理解属性,而 Java 不理解。然而,C# 和 VB.NET 确实如此,所以如果有的话,这些语言的原则将比 Java 更容易适用于 VBA,至少在属性方面是这样。见Property vs Method

    按照约定,VB 中的 FWIW 公共成员名称为 PascalCasecamelCase 公共成员名称是 Java 的东西。注意到标准库中的所有内容都是如何以大写首字母开头的吗?

    【讨论】:

    • 好的。只是我的想法不是 get/set 是 Java 问题,而是 OOP(封装)问题。它只是碰巧在Java中成为一个更大的问题。最后我不想写好的 VB 代码,我想写好的 OOP 代码,所以这是我从一开始就想做好的事情。看了这个答案(softwareengineering.stackexchange.com/a/120831/302738),cmets 中的一些人说,你的代码中有很多 getter 和 setter 也是代码异味的标志……
    • @AlfaBravo 我猜如果您的对象主要基于这些 setter/getter 进行交互,这可能表明您的代码是紧密耦合的......
    【解决方案2】:

    在我看来,您刚刚为属性访问器赋予了新名称。它们在功能上是相同的。

    我认为不使用 getter/setter 的想法意味着您不要尝试从外部修改对象的状态 - 因为如果您这样做了,那么对象只不过是用户定义的类型,一个简单的数据集合.对象/类应该由它们的行为来定义。它们包含的数据应该仅用于启用/支持该行为。
    这意味着您不告诉对象它必须是什么或您希望它保存什么数据。你告诉它你想让它做什么或它正在发生什么。然后对象本身决定如何修改其状态。

    对我来说,您的示例类似乎有点过于简单,无法作为示例工作。目前尚不清楚预期目的是什么:目前您最好只使用变量UserName

    看看this answer to a related question - 我认为它提供了一个很好的例子。


    关于您的编辑:

    从上面两个例子中我了解到,如果我想要 更改用户名的格式(假设以全大写形式返回), 然后我可以用第二种方法做到这一点,而无需更改名称 给出名称的方法 - 我可以让 returnName 指向 userNameCaps 属性。我的程序中的其余代码 仍然可以保持不变并指向方法 iserName。

    但是如果我想用第一个例子来做这个,我可以做一个新的 属性,但随后必须在程序中的任何地方更改我的代码 好吧,指向新属性...对吗?

    实际上,您在此处描述的内容在这两种方法中都是可能的。你可以拥有一个财产

    Public Property Get Name() As String
        ' possibly more code here...
        Name = UCase(UserName)
    End Property
    

    或等效功能

    Public Function Name() As String
        ' possibly more code here...
        Name = UCase(UserName)
    End Function
    

    只要你只改变属性/函数体,不需要修改外部代码。保持属性/函数的签名(第一行,包括Public 语句、它的名称、它的类型以及它的参数的顺序和类型)不变,您不需要更改类之外的任何内容来适应。

    【讨论】:

    • 嗨,我在问题的最后添加了一些信息,我认为这可以更好地显示两个示例之间的差异。
    【解决方案3】:

    Java 文章提出了某种不限于 Java 的哲学设计立场:一般建议是严格限制有关如何实现类的任何细节,以避免使代码更难维护。将此类建议放入 VBA 术语中并非无关紧要。

    Microsoft 普及了 Property 的概念,它实际上是一种(或两种)伪装成字段(即任何普通变量)的方法。这是一种将 getter 和 setter 打包在一起的简洁方式。除此之外,实际上,在幕后它仍然只是一组函数或子例程,它们充当您的类的访问器。

    了解 VBA 不做类,但它做接口。这就是“类模块”的含义:(匿名)类的接口。当您说Dim o As New MyClassModule 时,VBA 会调用一些工厂函数,该函数返回与MyClassModule 一起使用的类的实例。从那时起,o 引用了接口(该接口又连接到实例中)。正如@Mathieu Guindon 所证明的那样,类模块中的Public UserName As String 确实在幕后变成了Property。为什么?因为类模块是一个接口,而一个接口是一组(指向)函数和子例程的。

    至于哲学设计立场,这里真正重要的想法是不要做出太多承诺。如果UserNameString,它必须始终保持为String。此外,它必须始终可用 - 您不能将其从课程的未来版本中删除! UserName 可能不是这里最好的例子(毕竟,为什么 String 不能满足所有需求?为什么 UserName 会变得多余?)。但确实发生了,在上课时看起来不错的想法变成了一个大傻瓜。想象一个Public TwiddlePuff As Integer(或者getTwiddlePuff() As IntegersetTwiddlePuff(value As Integer))只是为了发现(很久以后!)Integer 已经不够用了,也许它应该是Long。或者可能是Double。如果您现在尝试更改TwiddlePuff,任何在Integer 时编译回来的东西都可能会损坏。因此,也许制作新代码的人会很好,也许大多数仍然需要使用旧代码的人现在遇到了问题。

    如果TwiddlePuff 被证明是一个非常大的设计错误,它本来就不应该存在呢?好吧,删除它会带来一系列令人头疼的问题。如果TwiddlePuff 被用在其他地方,那意味着有些人可能手头有很大的重构工作。这可能还不是最糟糕的——如果你的代码特别是编译成本机二进制文件,那会造成非常大的混乱,因为接口是关于一组以非常特定的方式布局和排序的函数指针。

    过于重申,不要做出太多的承诺。仔细考虑您将与他人分享的内容。 Properties-getters-setters-accessors 没问题,但必须谨慎使用。如果您正在编写的代码是您要与他人共享的代码,并且其他人会接受它并将其用作更大的代码系统的一部分,那么上述所有这些都很重要,并且这些其他人可能打算分享他们的更大的代码系统,还有更多的人将在他们更大的代码系统中使用它。

    这就是为什么尽可能地隐藏实现细节被认为是面向对象编程的基础。

    【讨论】:

    • 感谢您的详细回答,它有助于加深我的理解。
    猜你喜欢
    • 2014-10-08
    • 2011-02-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-12-08
    • 1970-01-01
    相关资源
    最近更新 更多