데이터 구조 (예 : 큐)가 OOP 언어를 사용하여 구현되는 경우 데이터 구조의 일부 멤버는 개인용 (예 : 큐의 항목 수)이어야합니다.
대기열은에서 struct작동 하는 및 함수 집합을 사용하여 절차 언어로 구현할 수도 있습니다 struct. 그러나 절차 적 언어에서는 struct개인 회원을 만들 수 없습니다 . 절차 적 언어로 구현 된 데이터 구조의 구성원이 공개 되었습니까? 아니면 비공개로 만드는 데 몇 가지 트릭이 있습니까?
데이터 구조 (예 : 큐)가 OOP 언어를 사용하여 구현되는 경우 데이터 구조의 일부 멤버는 개인용 (예 : 큐의 항목 수)이어야합니다.
대기열은에서 struct작동 하는 및 함수 집합을 사용하여 절차 언어로 구현할 수도 있습니다 struct. 그러나 절차 적 언어에서는 struct개인 회원을 만들 수 없습니다 . 절차 적 언어로 구현 된 데이터 구조의 구성원이 공개 되었습니까? 아니면 비공개로 만드는 데 몇 가지 트릭이 있습니까?
답변:
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에는 유용한 캡슐화 메커니즘이 없었습니다.
Adder self = malloc(sizeof(Adder));? 의 오류를 참조하십시오 . 타입 정의 포인터에 이유 sizeof(TYPE)가 있으며 일반적으로 눈살을 찌푸립니다.
sizeof(*Adder)때문에 *Adder유형이 아니기 때문에을 쓸 수는 없습니다 *int *. 표현 T t = malloc(sizeof *t)은 관용적이며 정확합니다. 내 편집을 참조하십시오.
private staticJava의 변수 와 동일했습니다 . C와 마찬가지로 불투명 포인터를 사용하여 파스칼에서 데이터를 선언하지 않고 데이터를 전달할 수 있습니다. 클래식 MacOS는 레코드의 공개 및 개인 부분 (데이터 구조)이 함께 전달 될 수 있으므로 많은 불투명 포인터를 사용했습니다. Window Record의 일부가 공개되었지만 일부 내부 정보도 포함되어 있기 때문에 Window Manager에서 많은 작업을 수행 한 것을 기억합니다.
_private_memberand와 같은 명명 규칙 output_property_이나 불변의 객체를 만들기위한 고급 기술에 의존합니다.
첫째, 절차 적 대 객체 지향적이라는 것은 대중 대 사적인 것과 관련이 없습니다. 많은 객체 지향 언어에는 액세스 제어 개념이 없습니다.
두 번째로, 대부분의 사람들이 절차 지향적이며 객체 지향적이 아닌 "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에서 사용되는 메모리에 대한 일반적인 핸들이되기 때문에 효과적으로 구현을 비공개로 만듭니다.
"불투명 한 데이터 유형"은 30 년 전에 컴퓨터 과학 학위를 받았을 때 잘 알려진 개념이었습니다. 우리는 당시 일반적인 사용법이 아니기 때문에 OOP를 다루지 않았으며 "기능적 프로그래밍"이 더 정확한 것으로 간주되었습니다.
Modula-2는 직접 지원합니다 ( https://www.modula2.org/reference/modules.php 참조) .
C에서 구조체를 선언 할 수있는 방법을 Lewis Pringle에 의해 이미 설명했습니다. Module-2와 달리, 객체를 생성하기 위해 팩토리 함수가 제공되어야했습니다. ( 가상 메소드는 구조체의 첫 번째 멤버가 메소드에 대한 함수 포인터를 포함하는 다른 구조체에 대한 포인터가 됨으로써 C에서 쉽게 구현할 수있었습니다 .)
종종 관습도 사용되었습니다. 예를 들어,“_”로 시작하는 필드는 데이터를 소유 한 파일 외부에서 액세스해서는 안됩니다. 이것은 사용자 정의 검사 도구를 작성함으로써 쉽게 시행되었습니다.
내가 작업 한 모든 대규모 프로젝트 (C ++로 이동 한 다음 C #로 이동하기 전에)는 잘못된 코드로 "개인"데이터에 액세스하지 못하도록하는 시스템을 갖추고있었습니다. 그것은 지금보다 조금 덜 표준화되었습니다.
멤버를 비공개로 표시하는 기능이 내장되어 있지 않은 많은 OO 언어가 있습니다. 이는 컴파일러가 프라이버시를 강제 할 필요없이 관례 적으로 수행 할 수 있습니다. 예를 들어 사람들은 종종 개인 변수 앞에 밑줄을 붙입니다.
PIMPL 관용구 가 가장 일반적인 "개인"변수에 액세스하기 어렵게 만드는 기술이 있습니다 . 이것은 공개 변수 파일에 포인터 만 할당되어 개인 변수를 별도의 구조체에 넣습니다. 이것은 추가적인 역 참조와 같은 사설 변수를 얻기위한 캐스트를 의미하며 ((private_impl)(obj->private))->actual_value, 성가 시므로 실제로는 거의 사용되지 않습니다.
데이터 구조에는 "멤버"가없고 데이터 필드 만 있습니다 (레코드 유형이라고 가정). 가시성은 일반적으로 전체 유형에 대해 설정되었습니다. 그러나 함수가 레코드의 일부가 아니기 때문에 생각만큼 제한적이지 않을 수 있습니다.
물러서서 조금 역사를 보자 ...
OOP 이전의 지배적 인 프로그래밍 패러다임을 구조적 프로그래밍 이라고 합니다 . 이것의 초기 주요 목표는 구조화되지 않은 점프 명령문 ( "goto")의 사용을 피하는 것이 었습니다. 이것은 제어 흐름 지향 패러다임 이지만 (OPP가 더 데이터 지향적이지만) 코드와 마찬가지로 데이터를 논리적으로 구조적으로 유지하려는 시도는 여전히 자연스럽게 확장되었습니다.
구조적 프로그래밍의 또 다른 결과는 정보를 숨기는 것인데 , 코드 구조의 구현 (정상적으로 변경 될 가능성이 높음)은 인터페이스와 분리되어 있어야합니다 (거의 거의 변하지 않을 것임). 지금은 교리이지만, 많은 사람들이 실제로 모든 개발자가 전체 시스템의 세부 사항을 이해하는 것이 더 낫다고 생각했기 때문에 실제로는 논란의 여지가있는 아이디어였습니다. Brook의 The Mythical Man Month 의 원판은 실제로 정보 숨기기에 반대했습니다.
나중에 구조화 된 프로그래밍 언어 (예 : Modula-2 및 Ada)가되기 위해 명시 적으로 설계된 프로그래밍 언어에는 일반적으로 기본 개념으로 숨겨져있는 정보가 포함되어 있으며, 응집력있는 기능 설비 (어떤 유형, 상수 및 필요한 개체). Modula-2에서는 Ada "Packages"에서 "Modules"라고 불렀습니다. 많은 현대 OOP 언어는 동일한 개념을 "네임 스페이스"라고 부릅니다. 이러한 네임 스페이스는 이러한 언어로 된 조직 개발 기반이며 대부분의 경우 OOP 클래스와 유사하게 사용할 수 있습니다 (물론 상속을 실제로 지원하지 않음).
따라서 Modula-2 및 Ada (83)에서는 개인 또는 공용 네임 스페이스에서 루틴, 유형, 상수 또는 객체를 선언 할 수 있지만 레코드 유형이있는 경우 일부 레코드 필드를 public 으로 선언하는 쉬운 방법이 없었습니다. 그리고 다른 사람들은 사적입니다. 전체 기록이 공개되었거나 공개되지 않았습니다.
object.method()호출은 단지 문법 설탕입니다. 중요한 IMHO-Meyer의 균일 한 접근 / 참조 원칙을 참조하십시오.
object.method()을 method(object, ...) 위한 대안적인 형태로 허용함으로써 마침내 그들 자신의 주장을 포기하고 입증했다고 믿는다 .
C에서는 이미 다른 필드에서 말했듯이 선언되었지만 정의되지 않은 유형에 대한 포인터를 전달하여 사실상 모든 필드에 대한 액세스를 제한 할 수 있습니다.
모듈별로 개인 및 공용 기능을 사용할 수도 있습니다. 소스 파일에서 static으로 선언 된 함수는 이름을 추측하려고해도 외부에 표시되지 않습니다. 마찬가지로 정적 파일 레벨 전역 변수를 가질 수 있습니다. 일반적으로 나쁜 습관이지만 모듈 단위로 격리 할 수 있습니다.
언어 강제 구문이 아닌 잘 표준화 된 규칙으로 액세스 제한이 제대로 작동한다는 점을 강조하는 것이 중요합니다 (Python 참조). 또한 객체 필드에 대한 액세스를 제한하면 생성 후 객체 내부의 데이터 값을 변경해야 할 때 프로그래머를 보호 할 수 있습니다. 이미 코드 냄새입니다. 아마도 C와 특히 C ++의 const메소드와 함수 인수에 대한 키워드는 Java의 가난한 사람보다 프로그래머에게 훨씬 큰 도움이됩니다 final.
static전역 데이터 및 작업 이었습니다 (다른 컴파일에서 사용하기 위해 링커에 제공되지 않음). 당신은 C가 훌륭한 소프트웨어 디자인 관행을 제외하고 1972 년에 원래 언어의 원래 디자인의 일부가 아니라는 점을 제외하고는 그럴듯하게 주장 할 수있다.
다음은 매우 간단한 반례입니다. Java에서는 interface객체를 정의하지만 class그렇지 않습니다. A class는 객체가 아닌 추상 데이터 유형을 정의합니다.
Ergo 는 Java private에서 사용할 때마다 class객체 지향이 아닌 개인 멤버가있는 데이터 구조의 예를 가지고 있습니다.