OOP 이전에는 데이터 구조 멤버가 공개 상태로 유지 되었습니까?


44

데이터 구조 (예 : 큐)가 OOP 언어를 사용하여 구현되는 경우 데이터 구조의 일부 멤버는 개인용 (예 : 큐의 항목 수)이어야합니다.

대기열은에서 struct작동 하는 및 함수 집합을 사용하여 절차 언어로 구현할 수도 있습니다 struct. 그러나 절차 적 언어에서는 struct개인 회원을 만들 수 없습니다 . 절차 적 언어로 구현 된 데이터 구조의 구성원이 공개 되었습니까? 아니면 비공개로 만드는 데 몇 가지 트릭이 있습니까?


75
"데이터 구조의 일부 멤버는 개인 이어야합니다" "아마도 있어야한다"와 "필요하다"는 큰 차이가 있습니다. OO 언어를 제공하십시오. 모든 자유를 침해하지 않는 한 모든 멤버 및 메소드가 공개되어 있어도 완벽하게 작동하는 큐를 만들 수 있습니다.
비트 트리

48
@ 8bittree가 말한 것을 다른 스핀으로 돌리기 위해, 코드를 사용하는 사람들이 설정 한 인터페이스를 고수 할 수 있도록 훈련 된 경우 모든 것을 공개하는 것이 좋습니다. 개인 구성원 구조는 자신이 속한 곳에서 코를 막을 수없는 사람들 때문에 생겨났습니다.
Blrfl

20
"캡슐화가 대중화되기 전에"를 의미 했습니까? OO 언어가 대중화되기 전에 캡슐화가 상당히 유명했습니다.
Frank Hileman

6
나는 실제로 문제의 핵심이라고 생각 @FrankHileman : 영업 이익은 캡슐화 절차 언어에 존재하는 경우, 알고 싶어하기 전에 시뮬 / 스몰 토크 / C ++
dcorking

18
이것이 혼란스러워지면 미리 미안합니다. 그렇다는 의미는 아닙니다. 다른 언어를 배워야합니다. 프로그래밍 언어는 기계를 실행하기위한 것이 아니라 프로그래머가 생각하기위한 것입니다 . 그것들은 반드시 당신이 생각 하는 방식 을 형성합니다 . 하루 종일 하루 종일 Java를 했더라도 JavaScript / Python / Ocaml / Clojure를 다루는 데 상당한 시간을 소비했다면이 질문을받지 못할 것입니다. 내가 일하고있는 하나의 C ++ 오픈 소스 프로젝트 (대부분 C)입니다. 대학 이후 액세스 수정자가있는 언어를 실제로 사용하지 않았으며 놓치지 않았습니다.
Jared Smith

답변:


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의 언어는을 통해 캡슐화의 어떤 종류를 지원하는 헤더 파일 메커니즘, 특히 불투명 한 포인터 기술. 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에는 functors 라는 모듈에 대한 흥미로운 접근 방식이 포함되어 있습니다 . OOP는 그림자가 많고 대체로 모듈화 된 프로그래밍이지만, OOP의 많은 장점은 객체 지향보다 모듈화에 관한 것입니다.

또한 C ++ 또는 Java와 같은 OOP 언어의 클래스는 객체 (늦은 바인딩 / 동적 디스패치를 ​​통해 작업을 해결하는 엔티티의 의미)가 아니라 추상 데이터 유형 (우리가 숨기는 공개 인터페이스를 정의하는 경우 )에 사용되는 경우가 종종 있습니다. 내부 구현 세부 사항). Reisited Data Abstraction, Revisited (Cook, 2009) 논문 에서 이러한 차이점에 대해 자세히 설명합니다.

그러나 많은 언어에는 캡슐화 메커니즘이 없습니다. 이 언어에서 구조 멤버는 공개됩니다. 기껏해야 사용을 권장하지 않는 명명 규칙이있을 것입니다. 예를 들어 Pascal에는 유용한 캡슐화 메커니즘이 없었습니다.


11
Adder self = malloc(sizeof(Adder));? 의 오류를 참조하십시오 . 타입 정의 포인터에 이유 sizeof(TYPE)가 있으며 일반적으로 눈살을 찌푸립니다.
중복 제거기

10
유형이 아니기 sizeof(*Adder)때문에 *Adder유형이 아니기 때문에을 쓸 수는 없습니다 *int *. 표현 T t = malloc(sizeof *t)은 관용적이며 정확합니다. 내 편집을 참조하십시오.
wchargin

4
파스칼에는 그 단위 밖에서 볼 수없는 단위 변수가있었습니다. 실제로 단위 변수는 private staticJava의 변수 와 동일했습니다 . C와 마찬가지로 불투명 포인터를 사용하여 파스칼에서 데이터를 선언하지 않고 데이터를 전달할 수 있습니다. 클래식 MacOS는 레코드의 공개 및 개인 부분 (데이터 구조)이 함께 전달 될 수 있으므로 많은 불투명 포인터를 사용했습니다. Window Record의 일부가 공개되었지만 일부 내부 정보도 포함되어 있기 때문에 Window Manager에서 많은 작업을 수행 한 것을 기억합니다.
Michael Shopsin

6
아마도 Pascal보다 더 좋은 예는 Python인데, 이것은 객체 지향을 지원하지만 캡슐화는 지원하지 않으며 _private_memberand와 같은 명명 규칙 output_property_이나 불변의 객체를 만들기위한 고급 기술에 의존합니다.
Mephy

11
OOD 문헌에는 모든 디자인 원칙을 OO 디자인 원칙 으로 제시하는 성가신 경향이 있습니다 . (비 학술적) OOD 문헌은 모든 사람들이 모든 잘못을 저지른 "어두운 시대"의 그림을 그리는 경향이 있으며, OOP 전문가들은 빛을 가져옵니다. 내가 알 수있는 한, 이것은 주로 무지에서 비롯됩니다. 예를 들어, 내가 알 수있는 한 Bob Martin은 몇 년 전만해도 함수형 프로그래밍에 진지한 모습을 보여주었습니다.
Derek Elkins

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 * 접근 방식을 사용하려면 typedef를 사용합니다. .h 파일에서 typedef void * queue라고 말한 다음 struct queue가있는 모든 곳에서 queue라고 말합니다. 그런 다음 .c 파일에서 struct queue의 이름을 struct queueImpl로 바꾸고 인수는 모두 queue (struct 큐의 isntead *)가되고 각 함수의 첫 번째 코드 행은 struct queueImpl * qi = (struct queueImpl *) q
Lewis가됩니다. 프링 글

7
흠. 구현 (foo.c 파일) 이외의 곳에서 'queue'의 모든 필드에 액세스 (읽기 또는 쓰기) 할 수 없기 때문에 비공개로 만듭니다. 사적인 것이 또 무슨 뜻인가요? BTW-typedef void * apporach와 (더 나은) 순방향 선언 구조체 접근 모두에 해당됩니다.
Lewis Pringle

5
smalltalk-80에 관한 책을 읽은 지 거의 40 년이 지났다고 고백해야하지만, 공개 또는 개인 데이터 회원의 개념은 기억 나지 않습니다. CLOS도 그런 개념이 없었던 것 같습니다. 오브젝트 파스칼에는 그런 개념이 없었습니다. 나는 Simula가 (아마도 Stroustrup이 아이디어를 얻었던 곳), C ++ 이후 대부분의 OO 언어가 그것을 생각합니다. 어쨌든 캡슐화와 개인 정보는 좋은 아이디어입니다. 원래 질문자조차도 그 시점에서 분명했습니다. 그는 방금 질문했다-oldies는 C ++ 이전 언어로 캡슐화를 어떻게 했습니까?
루이스 프링 글

5
@LewisPringle 리플렉션을 사용하지 않는 한 모든 "인스턴스 변수"(데이터 멤버)는 비공개이기 때문에 Smalltalk-80에는 퍼블릭 데이터 멤버에 대한 언급이 없습니다. AFAIU 스몰 토커는 공개하고자하는 모든 변수에 대한 접근자를 작성합니다.
dcorking

4
반면 @LewisPringle 모든 스몰 토크 "방법"(함수 회원) 공개되어 (마킹 서투른 규칙이있다 그들 개인)
dcorking

13

"불투명 한 데이터 유형"은 30 년 전에 컴퓨터 과학 학위를 받았을 때 잘 알려진 개념이었습니다. 우리는 당시 일반적인 사용법이 아니기 때문에 OOP를 다루지 않았으며 "기능적 프로그래밍"이 더 정확한 것으로 간주되었습니다.

Modula-2는 직접 지원합니다 ( https://www.modula2.org/reference/modules.php 참조) .

C에서 구조체를 선언 할 수있는 방법을 Lewis Pringle에 의해 이미 설명했습니다. Module-2와 달리, 객체를 생성하기 위해 팩토리 함수가 제공되어야했습니다. ( 가상 메소드는 구조체의 첫 번째 멤버가 메소드에 대한 함수 포인터를 포함하는 다른 구조체에 대한 포인터가 됨으로써 C에서 쉽게 구현할 수있었습니다 .)

종종 관습도 사용되었습니다. 예를 들어,“_”로 시작하는 필드는 데이터를 소유 한 파일 외부에서 액세스해서는 안됩니다. 이것은 사용자 정의 검사 도구를 작성함으로써 쉽게 시행되었습니다.

내가 작업 한 모든 대규모 프로젝트 (C ++로 이동 한 다음 C #로 이동하기 전에)는 잘못된 코드로 "개인"데이터에 액세스하지 못하도록하는 시스템을 갖추고있었습니다. 그것은 지금보다 조금 덜 표준화되었습니다.


9

멤버를 비공개로 표시하는 기능이 내장되어 있지 않은 많은 OO 언어가 있습니다. 이는 컴파일러가 프라이버시를 강제 할 필요없이 관례 적으로 수행 할 수 있습니다. 예를 들어 사람들은 종종 개인 변수 앞에 밑줄을 붙입니다.

PIMPL 관용구 가 가장 일반적인 "개인"변수에 액세스하기 어렵게 만드는 기술이 있습니다 . 이것은 공개 변수 파일에 포인터 만 할당되어 개인 변수를 별도의 구조체에 넣습니다. 이것은 추가적인 역 참조와 같은 사설 변수를 얻기위한 캐스트를 의미하며 ((private_impl)(obj->private))->actual_value, 성가 시므로 실제로는 거의 사용되지 않습니다.


4

데이터 구조에는 "멤버"가없고 데이터 필드 만 있습니다 (레코드 유형이라고 가정). 가시성은 일반적으로 전체 유형에 대해 설정되었습니다. 그러나 함수가 레코드의 일부가 아니기 때문에 생각만큼 제한적이지 않을 수 있습니다.

물러서서 조금 역사를 보자 ...

OOP 이전의 지배적 인 프로그래밍 패러다임을 구조적 프로그래밍 이라고 합니다 . 이것의 초기 주요 목표는 구조화되지 않은 점프 명령문 ( "goto")의 사용을 피하는 것이 었습니다. 이것은 제어 흐름 지향 패러다임 이지만 (OPP가 더 데이터 지향적이지만) 코드와 마찬가지로 데이터를 논리적으로 구조적으로 유지하려는 시도는 여전히 자연스럽게 확장되었습니다.

구조적 프로그래밍의 또 다른 결과는 정보를 숨기는 것인데 , 코드 구조의 구현 (정상적으로 변경 될 가능성이 높음)은 인터페이스와 분리되어 있어야합니다 (거의 거의 변하지 않을 것임). 지금은 교리이지만, 많은 사람들이 실제로 모든 개발자가 전체 시스템의 세부 사항을 이해하는 것이 더 낫다고 생각했기 때문에 실제로는 논란의 여지가있는 아이디어였습니다. Brook의 The Mythical Man Month 의 원판은 실제로 정보 숨기기에 반대했습니다.

나중에 구조화 된 프로그래밍 언어 (예 : Modula-2 및 Ada)가되기 위해 명시 적으로 설계된 프로그래밍 언어에는 일반적으로 기본 개념으로 숨겨져있는 정보가 포함되어 있으며, 응집력있는 기능 설비 (어떤 유형, 상수 및 필요한 개체). Modula-2에서는 Ada "Packages"에서 "Modules"라고 불렀습니다. 많은 현대 OOP 언어는 동일한 개념을 "네임 스페이스"라고 부릅니다. 이러한 네임 스페이스는 이러한 언어로 된 조직 개발 기반이며 대부분의 경우 OOP 클래스와 유사하게 사용할 수 있습니다 (물론 상속을 실제로 지원하지 않음).

따라서 Modula-2 및 Ada (83)에서는 개인 또는 공용 네임 스페이스에서 루틴, 유형, 상수 또는 객체를 선언 할 수 있지만 레코드 유형이있는 경우 일부 레코드 필드를 public 으로 선언하는 쉬운 방법이 없었습니다. 그리고 다른 사람들은 사적입니다. 전체 기록이 공개되었거나 공개되지 않았습니다.


나는 Ada에서 일하는 데 꽤 많은 시간을 보냈습니다. 선택적 데이터 숨기기 (데이터 유형의 일부)는 항상 우리가 한 일입니다. 포함하는 패키지에서 유형 자체를 개인용 또는 제한된 개인용으로 정의합니다. 패키지 인터페이스는 공개 기능 / 프로 시저를 노출하여 내부 필드를 가져 오거나 설정합니다. 물론 이러한 루틴은 개인 유형의 매개 변수를 가져와야합니다. 그때 나는 이것을 어렵게 생각하지 않았습니다.
David

또한 AFAIK 대부분의 OO 언어는 동일한 방식으로 작동합니다. 즉, myWidget.getFoo ()는 실제로 getFoo (myWidget)로 구현됩니다. object.method()호출은 단지 문법 설탕입니다. 중요한 IMHO-Meyer의 균일 한 접근 / 참조 원칙을 참조하십시오.
David

@David-Ada 95 시대에 수년간 Ada 커뮤니티의 주장이었습니다. 나는 그들이 개념적 도약을 할 수 없었던 사람들 object.method()method(object, ...) 위한 대안적인 형태로 허용함으로써 마침내 그들 자신의 주장을 포기하고 입증했다고 믿는다 .
TED

0

C에서는 이미 다른 필드에서 말했듯이 선언되었지만 정의되지 않은 유형에 대한 포인터를 전달하여 사실상 모든 필드에 대한 액세스를 제한 할 수 있습니다.

모듈별로 개인 및 공용 기능을 사용할 수도 있습니다. 소스 파일에서 static으로 선언 된 함수는 이름을 추측하려고해도 외부에 표시되지 않습니다. 마찬가지로 정적 파일 레벨 전역 변수를 가질 수 있습니다. 일반적으로 나쁜 습관이지만 모듈 단위로 격리 할 수 ​​있습니다.

언어 강제 구문이 아닌 잘 표준화 된 규칙으로 액세스 제한이 제대로 작동한다는 점을 강조하는 것이 중요합니다 (Python 참조). 또한 객체 필드에 대한 액세스를 제한하면 생성 후 객체 내부의 데이터 값을 변경해야 할 때 프로그래머를 보호 할 수 있습니다. 이미 코드 냄새입니다. 아마도 C와 특히 C ++의 const메소드와 함수 인수에 대한 키워드는 Java의 가난한 사람보다 프로그래머에게 훨씬 큰 도움이됩니다 final.


C가 정보 숨기기를 위해 특별히 가지고있는 유일한 기능은 static전역 데이터 및 작업 이었습니다 (다른 컴파일에서 사용하기 위해 링커에 제공되지 않음). 당신은 C가 훌륭한 소프트웨어 디자인 관행을 제외하고 1972 년에 원래 언어의 원래 디자인의 일부가 아니라는 점을 제외하고는 그럴듯하게 주장 할 수있다.
TED

0

Public 정의가 언제라도 자신의 코드를 통해 구현 및 데이터 / 속성에 액세스 할 수있는 능력이라면, 그 대답은 간단합니다 : . 그러나 언어에 따라 다양한 방법으로 추상화되었습니다.

나는 이것이 간결하게 당신의 질문에 대답하기를 바랍니다.


-1

다음은 매우 간단한 반례입니다. Java에서는 interface객체를 정의하지만 class그렇지 않습니다. A class는 객체가 아닌 추상 데이터 유형을 정의합니다.

Ergo 는 Java private에서 사용할 때마다 class객체 지향이 아닌 개인 멤버가있는 데이터 구조의 예를 가지고 있습니다.


7
이 대답은 물론 기술적으로는 정확하지만 ADT가 무엇인지, 개체와 어떻게 다른지 아직 모르는 사람에게는 완전히 이해할 수 없습니다.
amon

1
이 답변에서 무언가를 배웠습니다.
littleO

3
인터페이스는 객체를 "정의" 하지 않습니다 . 그들은 계약 지정 개체가 작업 / 행동에 대해 할 수있는 또는 수행합니다. 일반적으로 설명되고 상속 마찬가지로 A는 A가별로 관계 조성물 보유 관계는, 인터페이스는 일반적으로 설명된다 할 수있는 관계.
code_dredd
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.