【问题标题】:How can I make a private member object-encapsulated?如何使私有成员对象封装?
【发布时间】:2016-10-20 00:45:57
【问题描述】:

在 Scala 中,通过键入 private[this] 我们可以使私有成员只能由该类的特定对象访问,而同一类的另一个对象不能访问它 .如果可能,我们如何在其他语言中实现这一点……尤其是 F#、Erlang 和 Clojure?

【问题讨论】:

  • 这在 F# 中绝对是可能的,我认为在其他方面也是如此
  • 怎么样?你能举个例子吗?我无法通过搜索找到任何内容
  • 不要试图给没有给出答案的借口,但你为什么需要这样做?我想了很多,到目前为止,我最终同意Rich Hickey's position;我还没有找到一种情况,数据封装可以像不变性那样提供真正的好处。
  • @JohnPalmer OP 要求一个说明符,它将对成员的访问限制为 this(而不是声明类型),这在 .NET 中不可用

标签: scala clojure f# erlang


【解决方案1】:

您要问的基本上是面向对象的数据抽象和具有抽象数据类型的数据抽象之间的区别(ADTs,不要与代数数据类型混淆,代数数据类型通常也缩写为“ADT”)。 (见On Understanding Data Abstraction, RevisitedWilliam R. Cook、他的Proposal for Simplified, Modern Definitions of "Object" and "Object Oriented"Object-Oriented Programming Versus Abstract Data Types。)

相同抽象数据类型的两个不同实例可以检查彼此的表示(但不能检查其他抽象数据类型实例的表示),而一个对象可以从不检查另一个对象的表示,即使该对象是 same 类型的实例。 Cook 将此属性识别为面向对象的数据抽象(实际上是一般的面向对象)的基本和定义特征,并将其称为 Autognosis(自我知识)。我更喜欢术语Xenoagnosis(外国不知识),它是由 Glenn Vanderburg、Rick de Natale 或已故的 IIRC 的 Jim Weirich 应用的,因为它强调了两种方法之间的区别,即对象无法知道其他对象。

如您所见,在 Scala 中,可以通过 private[this] 访问修饰符实现面向对象的数据抽象。

class TestPrivate {
  private       val notReallyPrivate = 42
  private[this] val privateIMeanIt   = 23

  def blowUp(other: TestPrivate) = {
    other.notReallyPrivate
    other.privateIMeanIt
  }
}
// error: value privateIMeanIt is not a member of TestPrivate

在 Erlang 中,Object 的等价物是 Process,而 Process 被语言语义完全封装(它们甚至有自己的垃圾回收内存)。毕竟,他们甚至可以生活在不同大陆的不同机器上,因此公开表示是不切实际的。您向进程发送消息,进程完全自主地决定如何处理消息以及如何响应消息。 一切基本上都是private[this]

在 Clojure 中,可以通过闭包实现面向对象的数据抽象。 (这对于几乎任何具有闭包和高阶子例程的语言都是如此,例如,您也可以在 Erlang 中将函数用作对象而不是进程。)想想 ECMAScript:尽管它有一个令人困惑的语言结构,称为“对象”,在ECMAScript 对象实际上是用函数实现的。您可以以同样的方式在 Clojure 中实现它们。数据抽象的面向对象形式也称为过程数据抽象,因为它不依赖于类型,而是依赖于将数据隐藏在过程接口(或功能接口,如果你想要的话,毕竟可变性和副作用与 OO 正交)。库克(半开玩笑地)争辩说,因为 λ-演算只有函数,所以所有抽象都是函数式的,因此 λ-演算是第一个、最古老和最纯粹的 OO 语言——显然这意味着 OO 数据抽象必须在密切相关的语言中成为可能基于 λ 演算,例如 Lisp 家族。 (历史轶事:Scheme最初是为了研究OO和Actors而设计的,他们在实现过程中才注意到消息发送和函数调用的解释器的代码路径是相同的。)

在 Java 或 C♯ 等语言中,面向对象是使用类型系统和程序员纪律实现的,仅使用 interfaces 作为类型。 interfaces 无法描述表示,因此,只要您确保(通过程序员纪律、编码风格、代码审查,也许是静态分析工具)只有 interfaces 被用作类型(每个局部变量、字段、方法参数、方法返回值、强制转换运算符、instanceof 检查和通用类型参数必须始终interface) 和classes用作工厂(唯一允许出现类名的地方是直接在new关键字旁边),那么你就实现了面向对象的数据抽象。 (还有一些其他注意事项,最重要的是,您不能使用引用相等。)

interface ITestPrivate {
  default int thisIsPublic() { return 0; }

  default void blowUp1(ITestPrivate other) {
    other.thisIsPublic();
    other.privateIMeanIt();
  }
}

class TestPrivate implements ITestPrivate {
  private int privateIMeanIt() { return 23; }

  void blowUp2(TestPrivate other1, ITestPrivate other2) {
    other1.notReallyPrivate(); // works
    other2.notReallyPrivate(); // doesn't
  }
}

这样做应该在 F♯ 中也是可能的,因为 F♯ supports interfaces as well

【讨论】:

  • 顺便说一句:如果更熟悉 Erlang、Clojure、F♯ 或 Java 的人想要提供/改进示例,请继续!
  • 我会在这个出色的答案和@SamEstep 对这个问题的评论中添加一条评论:通常当人们想要限制对对象或闭包内的“成员”变量的访问时,目的是限制改变变量值的能力。因为 Clojure 是为函数式编程而设计的,一般来说,变量的值一旦定义就永远不能改变。这减少了隐藏对变量的访问的动机。 (可以给变量一个特殊的值,例如使用atom,间接允许更改值,但这需要额外的步骤。)
【解决方案2】:

F# 有 publicinternalprivate access control specifiers。 Scalas 限制性更强的private[this] 限制了对类型实例而非类型本身的访问 不能用作说明符,因此即使private(F# 中限制最大)成员也是@987654328 @instance 可以访问。 class let bindings 虽然是实例私有的:

type Foo(x) =
    let totallyPrivate = x
    member private(*[this]*) __.Bar = x
    member this.Baz (other : Foo) =
        // other.Bar not accessible in Scala
        this.Bar + other.Bar + totallyPrivate
        // the field, constructor or member 'totallyPrivate' is not defined
        // other.totallyPrivate

【讨论】:

  • 我认为你是对的,我实际上想知道 OP 到底在想什么,并把答案作为更多的测试扔掉了。在 OP 中看到一个更具体的例子(和你的完全一样)显然会更有帮助。
  • 您可以在类中使用 let 绑定,这正是 OP 所期望的(并且绑定的形式表明了这一点,因为它没有绑定为类型的成员)。
  • @kvb 感谢您指出这一点。我相应地更新了我的答案。
【解决方案3】:

正如我对@JörgWMittag 回答的评论所建议的那样,我倾向于同意@SamEstep 的评论,即您想做的codingXeno 在Clojure 中几乎没有意义。不过,在 Common Lisp 或 Scheme 中这是一个很好的练习,在 Clojure 中只是稍微难一些。这是您所要求的内容的非常简单的说明:

(def getit-and-setit (let [x (atom 42)]
                       [(fn [] @x), (fn [new-x] (reset! x new-x))]))

这里我们定义了一个局部变量x,它的值是一个“原子”,即可以保存一个可以被新值替换的值。 (可以使用@deref 提取原子的内容。可以使用reset! 或其他一些有用的运算符替换它。)然后我们返回一个二元素向量。每个元素都是一个函数。 (通常不使用逗号,但允许使用逗号,并使这段代码更清晰。)

(def get-it (first getit-and-setit))
(def set-it! (second getit-and-setit))

这里提取了第一个函数并定义了一个新变量get-it 以将该函数作为其值。我们定义了set-it! 以将另一个函数作为它的值。

get-it 返回原子的内容,即x 的值:

(get-it) ;==> 42

set-it! 用新值替换原子的内容:

(set-it! 5)
(get-it) ;==> 5

除了通过get-itset-it! 之外,没有其他方法可以访问xx 是“私人的”。

在 Common Lisp 或 Scheme 中这会更简单的原因是在这些语言中,普通的局部变量可以被赋予新的值。 Clojure 通常不允许这样做。 (如果您使用 Java 或 Javascript 互操作方法,有一些方法可以在 Clojure 中不使用原子来更改变量的值。但是,您应该只为互操作或其他一些特殊需要而这样做。)

另一方面,如果你想限制对 Clojure defrecord 结构的组件的访问,我认为你不能这样做,除非使用上面给出的方法。您可以使用相关的 deftype 数据结构来实现,但这通常用于 Java 互操作。

【讨论】:

    【解决方案4】:

    请参考@JohnPalmer 的评论以及Access Control。可以通过Google 轻松找到。

    type  TestPrivate(x)  =
        let y = x - 10
        member private __.X() = 10
        member __.Z() = x + 10
        member __.Y = __.X()
    
    let test = TestPrivate(5)
    test.Z() //val it : int = 15
    test.Y //val it : int = 10
    test.X()
    

    错误 FS0491:成员或对象构造函数“X”不可访问。 私有成员只能从声明类型中访问。 受保护的成员只能从扩展类型访问,并且 无法从内部 lambda 表达式中访问。

    编辑

    type TestPrivate(x:string)  =
        let totallyPrivate = x 
        member private __.PrivateX = x
        member __.FromOther(other:TestPrivate) = 
            __.PrivateX + "-" + other.Y + "-" + other.PrivateX + "-" + totallyPrivate
        member __.Y = __.PrivateX
    
    let thisTest = TestPrivate("this")
    let otherTest = TestPrivate("other")
    
    thisTest.FromOther(otherTest) //val it : string = "this-other-other-this"
    otherTest.FromOther(thisTest) //val it : string = "other-this-this-other"
    thisTest.FromOther(thisTest) //val it : string = "this-this-this-this"
    otherTest.FromOther(otherTest) //val it : string = "other-other-other-other" 
    

    所以this 实例当然可以访问totallyPrivate,但是thisother 都不能访问对方的let 绑定,尽管它们可以访问对方的私有属性。

    【讨论】:

    • 这不是 OP 要求的,对 instances(不是类型)的访问限制在 .NET 中不可用
    • 顺便说一句:您到 Google 的流氓链接不合适,因为阅读问题会指出:...并且同一类的另一个对象无法访问它...
    • @CaringDev - 这是真的;但是,let 绑定实际上确实演示了 OP 的要求:没有其他实例可以使用绑定名称来访问该值。
    • 那么如果我让 test2 = TestPrivate(5) 将无法访问类型内的 let 绑定?
    • @codingXeno test1 可以访问x,它将无法访问 test2 的x。我将根据 CaringDev 的示例更新答案。如果这不是您想要的,请随时在 OP 中对其进行扩展。
    猜你喜欢
    • 2023-03-18
    • 2013-12-30
    • 2011-05-15
    • 2011-04-25
    • 2015-05-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-09-03
    相关资源
    最近更新 更多