在OOP之前,数据结构成员是否公开?


44

使用OOP语言实现数据结构(例如,队列)时,数据结构的某些成员需要为私有的(例如,队列中的项目数)。

队列也可以使用struct和在上操作的一组函数以过程语言实现struct。但是,在过程语言中,您不能将成员struct设为私有成员。使用过程语言实现的数据结构的成员是否公开了,还是有一些技巧使其私有化?


75
“数据结构的某些成员需要是私有的” “可能应该”和“需要”之间有很大的区别。给我一种面向对象的语言,我保证即使您不公开所有自由的情况下,即使所有成员和方法都是公共的,我也可以使它工作得很好。
8

48
换个说法,@ 8bittree所说的话,只要使用代码的人员受到足够的纪律以遵守您设置的界面,那么将所有公共内容公开就可以了。之所以创建私有成员,是因为人们无法将自己的鼻子挡在不属于自己的地方。
Blrfl

20
您是说“封装流行之前”吗?在OO语言开始流行之前,封装是相当流行的。
Frank Hileman '18

6
@FrankHileman我认为这实际上是问题的核心:OP希望在Simula / Smalltalk / C ++之前
点滴

18
预先抱歉,如果这是居高临下的,我不是故意的。您需要学习其他语言。编程语言不是供机器运行的,而是供程序员考虑的。他们必然塑造方式你的想法。如果您花费了大量时间在JavaScript / Python / Ocaml / Clojure上工作,即使您整天都在工作Java,那么您根本不会遇到这个问题。自从上大学以来,除了我从事的一个C ++开源项目(无论如何,大部分都是C)之外,我还没有真正使用过带有访问修饰符的语言,而且我也没有错过。
贾里德·史密斯

Answers:


139

OOP没有发明封装,并且与封装不相同。许多OOP语言没有C ++ / Java样式访问修饰符。许多非OOP语言具有可用于提供封装的各种技术。

一种经典的封装方法是闭包,如函数编程中所使用的。这比OOP显着老,但是在某种意义上是等效的。例如,在JavaScript中,我们可能会创建一个像这样的对象:

function Adder(x) {
  this.add = function add(y) {
    return x + y;
  }
}

var plus2 = new Adder(2);
plus2.add(7);  //=> 9

上面的plus2对象没有允许直接访问的成员x-它是完全封装的。该add()方法是对x变量的闭包。

所述Ç语言通过其支持一些种封装的报头文件机制,特别是不透明的指针技术。在C中,可以在不定义其成员的情况下声明结构名称。那时不能使用该结构类型的变量,但是我们可以自由使用指向该结构的指针(因为在编译时知道结构指针的大小)。例如,考虑以下头文件:

#ifndef ADDER_H
#define ADDER_H

typedef struct AdderImpl *Adder;

Adder Adder_new(int x);
void Adder_free(Adder self);
int Adder_add(Adder self, int y);

#endif

现在,我们可以编写使用此Adder接口的代码,而无需访问其字段,例如:

Adder plus2 = Adder_new(2);
if (!plus2) abort();
printf("%d\n", Adder_add(plus2, 7));  /* => 9 */
Adder_free(plus2);

这是完全封装的实现细节:

#include "adder.h"

struct AdderImpl { int x; };

Adder Adder_new(int x) {
  Adder self = malloc(sizeof *self);
  if (!self) return NULL;
  self->x = x;
  return self;
}

void Adder_free(Adder self) {
  free(self);
}

int Adder_add(Adder self, int y) {
  return self->x + y;
}

还有一类模块化的编程语言,它专注于模块级接口。ML语言家族,包括 OCaml包含了一种有趣的模块功能,称为函子。OOP在很大程度上掩盖了模块化编程,但OOP的许多优点却更多地在于模块化而不是面向对象。

还观察到,OOP语言(如C ++或Java)中的类通常不用于对象(即通过后期绑定/动态分配来解析操作的实体),而仅用于抽象数据类型(在此我们定义了隐藏的公共接口)内部实施细节)。本文的了解数据抽象,再访(库克,2009年)详细讨论这种差别。

但是,是的,许多语言都没有封装机制。在这些语言中,结构成员不公开。最多会有一个禁止使用的命名约定。例如,我认为Pascal没有有用的封装机制。


11
看到错误Adder self = malloc(sizeof(Adder));?typedef-ing指针sizeof(TYPE)是有原因的,并且通常对此不屑一顾。
重复数据删除器

10
您不能只写sizeof(*Adder),因为*Adder它不是类型,就像*int *不是类型一样。该表达T t = malloc(sizeof *t)既惯用又正确。看到我的编辑。
wchargin

4
Pascal具有无法从该单位外部看到的单位变量。实际上,单位变量等效private static于Java中的变量。与C相似,您可以使用不透明指针在Pascal中传递数据而无需声明它是什么。由于记录(数据结构)的公共部分和私有部分可能会一起传递,因此经典MacOS使用了许多不透明的指针。我记得Window Manager会做很多事情,因为Window Record的某些部分是公开的,但也包括一些内部信息。
Michael Shopsin

6
也许比Pascal更好的例子是Python,它支持面向对象但不封装,而是使用诸如_private_member和的命名约定output_property_,或者使用更先进的技术来制造可植入对象。
Mephy

11
OOD文学中有一种令人讨厌的趋势,即将每种设计原则都表示为OO设计原则。(非学术的)OOD文献倾向于描绘“黑暗时代”的图片,其中每个人都在做错误的一切,然后OOP的从业者对此进行了介绍。据我所知,这主要来自无知。例如,据我所知,鲍勃·马丁仅仅几年前就对函数式编程给予了认真的关注。
德里克·埃尔金斯

31

首先,面向程序与面向对象与公共与私有无关。许多面向对象的语言没有访问控制的概念。

其次,在“ C”中-大多数人都将其称为过程性的,而不是面向对象的,可以使用许多技巧来有效地使事物私有化。一种非常常见的方法是使用不透明(例如void *)指针。或者-您可以向前声明一个对象,而不必在头文件中定义它。

foo.h:

struct queue;
struct queue* makeQueue();
void add2Queue(struct queue* q, int value);
...

foo.c:

struct queue {
    int* head;
    int* head;
};
struct queue* makeQueue() { .... }
void add2Queue(struct queue* q, int value) { ... }

看看Windows SDK!它使用HANDLE和UINT_PTR,以及类似的东西作为API中使用的内存的通用句柄-有效地使实现私有化。


1
我的样本展示了一种更好的(C)方法-使用正向声明的结构。要使用void *方法,我将使用typedefs:在.h文件中,输入typedef void * queue,然后在所有我们拥有struct队列的地方都说queue;然后在.c文件中,将struct queue重命名为struct queueImpl,所有参数变为queue(struct queue *的isntead),每个此类函数的第一行代码变为struct queueImpl * qi =(struct queueImpl *)q
Lewis普林格尔

7
嗯 之所以将它设为私有,是因为除了实现(foo.c文件)之外,您无法从任何地方访问(读取或写入)“队列”的任何字段。您私下还说什么?顺便说一句-这就是真正的同时为无效的typedef * apporach和(更好)前进申报结构的方法
刘易斯普林格尔

5
我必须承认,自从我阅读有关smalltalk-80的书以来已经有40年了,但是我不记得任何关于公共或私有数据成员的想法。我认为CLOS也没有这样的概念。帕斯卡对象没有这样的概念。我记得Simula做到了(大概是Stroustrup提出这个想法的),而且自C ++以来,大多数OO语言都拥有它。无论如何-我们同意封装和私有数据是个好主意。在这一点上,甚至最初的提问者也很清楚。他只是在问-老歌如何在C ++之前的语言中进行封装。
刘易斯·普林格

5
@LewisPringle在Smalltalk-80中没有提到公共数据成员,因为除非您使用反射,否则所有“实例变量”(数据成员)都是私有的。AFAIU Smalltalkers为要公开的每个变量编写一个访问器。
dcorking

4
相比之下,@ LewisPringle,所有Smalltalk的“方法”(函数成员)都是公开的(存在将它们标记为私有的笨拙约定)
嘲笑

13

30年前,当我获得计算机科学学位时,“不透明数据类型”是一个众所周知的概念。我们没有介绍OOP,因为当时还不普遍使用OOP,因此认为“函数式编程”更为正确。

Modula-2直接支持它们,请参阅https://www.modula2.org/reference/modules.php

Lewis Pringle已经解释了如何在C中使用向前声明结构的方法。与Module-2不同,必须提供工厂函数来创建对象。(通过将结构的第一个成员作为指向另一个包含指向方法的函数指针的结构的指针,虚拟方法也易于在C中实现。)

通常也使用约定。例如,不应在拥有数据的文件外部访问以“ _”开头的任何字段。通过创建自定义检查工具可以轻松地实施此操作。

我从事的每个大型项目(在我使用C ++之前,然后是C#)都有一个系统来防止错误的代码访问“私人”数据。它只是比现在少了一点标准化。


9

请注意,有许多OO语言没有将成员标记为私有的内置功能。这可以按照惯例完成,而无需编译器强制执行隐私。例如,人们经常在私有变量前加上下划线。

有一些使访问“私有”变量更加困难的技术,最常见的是PIMPL习惯用法。这会将您的私有变量放在单独的结构中,而在公共头文件中仅分配了一个指针。这意味着要进行额外的取消引用和强制转换以获取任何私有变量,例如((private_impl)(obj->private))->actual_value,这会很烦人,因此在实践中很少使用。


4

数据结构没有“成员”,只有数据字段(假设它是记录类型)。通常为整个类型设置可见性。但是,这可能没有您想象的那么有限,因为这些功能不是记录的一部分。

让我们回去看看这里的一些历史...

OOP之前的主导编程范式被称为结构化编程。最初的主要目标是避免使用非结构化的跳转语句(“ goto”)。这是一个面向控制流的范式(而OOP则更面向数据),但是它仍然是它的自然扩展,试图保持数据的逻辑结构像代码一样。

结构化编程的另一个优势是信息隐藏,即认为代码结构的实现(可能会经常更改)应该与接口分开(理想情况下不会发生太大变化)。现在这是教条,但是在过去,许多人实际上认为,对于每个开发人员来说,了解整个系统的详细信息更好,因此,这实际上是一个有争议的想法。布鲁克的《神话人月》的原始版本实际上反对信息隐藏。

后来被明确设计为良好的结构化编程语言的编程语言(例如Modula-2和Ada)通常将信息隐藏作为基本概念,它是围绕某种功能性内聚性功能(以及任何类型,常数和他们可能需要的对象)。在Modula-2中,这些称为“模块”,在Ada中称为“包装”。许多现代的OOP语言将相同的概念称为“命名空间”。这些名称空间是使用这些语言进行开发的组织基础,并且在大多数情况下,它们可以类似于OOP类使用(当然,不真正支持继承)。

因此,在Modula-2和Ada(83)中,您可以在私有或公共名称空间中声明任何例程,类型,常量或对象,但是如果您有记录类型,则没有(简便)方法可以将某些记录字段声明为公共和其他私人。您的整个记录​​是公开的,还是不是公开的。


我花了很多时间在Ada工作。选择性隐藏(属于数据类型的一部分)是我们一直在做的事情。在包含包中,您可以将类型本身定义为私有或受限私有;包接口将公开公共功能/过程以获取和/或设置内部字段。这些例程当然需要采用私有类型的参数。那时我没有,现在也不认为这很困难。
David

同样,AFAIK大多数OO语言在幕后都以相同的方式工作,即myWidget.getFoo()实际上实现为getFoo(myWidget)。该object.method()调用只是语法糖。重要的恕我直言-请参阅迈尔的统一访问/参考原则-但仍然只是语法糖。
David

@David-那是Ada 95时代多年来Ada社区的观点。我相信他们最终屈服并证明了自己的论点object.method()method(object, ...) 为那些无法进行概念性跨越的人们提供了一种替代形式。
TED

0

在C语言中,您已经可以像其他人所说的那样传递指向已声明但未定义类型的指针,实际上限制了对所有字段的访问。

您还可以在模块到模块的基础上拥有私有和公共功能。即使您试图猜测它们的名称,在源文件中声明为静态的函数对外部也不可见。同样,您可以具有静态的文件级全局变量,这通常是不好的做法,但允许在模块基础上进行隔离。

需要强调的是,将访问限制作为一种标准化的约定而不是由语言强制的构造很好地起作用(请参阅Python)。最重要的是,只有在创建后需要更改对象内部数据的值时,限制对对象字段的访问才能保护程序员。这已经是代码的味道了。可以说,const对于Java和Java而言,C的方法和函数参数的关键字对程序员的帮助更大final


C唯一专门用于信息隐藏的功能是static全局数据和操作(这意味着它们不会提供给链接器供其他编译使用)。您可以合理地辩称,C对良好软件设计实践的任何支持,除此以外,几乎都是黑客,而不是1972
TED

0

如果您对Public的定义是可以随时通过自己的代码访问实现和数据/属性,那么答案很简单:。但是,它是通过多种方式抽象的-取决于语言。

希望这能简要回答您的问题。


-1

这是一个非常简单的反例:在Java中,interfaces定义对象,而classes则不。A class定义了抽象数据类型,而不是对象。

人机工程学,你使用任何时间privateclassJava中,你有没有被面向对象的私有成员的数据结构的一个例子。


7
这个答案在技术上当然是正确的,但是对于任何尚不知道ADT是什么以及它们与对象有何不同的人来说,这是完全无法理解的。
阿蒙(Amon)

1
我从这个答案中学到了一些东西。
littleO

3
接口 “定义”对象;它们指定对象可以执行或执行的操作/行为的合同。就像继承一般通过描述通过一个关系和组合物具有关系,接口通常通过描述可以做关系。
code_dredd
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.