>  기사  >  웹 프론트엔드  >  JavaScript 시리즈 심층 이해(17): 객체지향 프로그래밍에 대한 자세한 소개_기본 지식

JavaScript 시리즈 심층 이해(17): 객체지향 프로그래밍에 대한 자세한 소개_기본 지식

WBOY
WBOY원래의
2016-05-16 16:11:181081검색

소개

이 기사에서는 ECMAScript의 객체 지향 프로그래밍의 다양한 측면을 고려합니다(이 주제는 이전에 많은 기사에서 논의되었지만). 우리는 이러한 문제를 이론적 관점에서 좀 더 살펴보겠습니다. 특히 객체 생성 알고리즘, 객체가 어떻게 관련되어 있는지(기본 관계 - 상속 포함)를 고려할 것이며 이는 토론에도 사용될 수 있습니다(JavaScript의 OOP에 대한 이전 개념적 모호성을 해소할 수 있기를 바랍니다).

영어 원문:http://dmitrysoshnikov.com/ecmascript/chapter-7-1-oop-general-theory/

소개, 패러다임 및 아이디어

ECMAScript에서 OOP의 기술적 분석을 수행하기 전에 OOP의 몇 가지 기본 특성을 숙지하고 소개에서 주요 개념을 명확히 하는 것이 필요합니다.

ECMAScript는 구조적, 객체 지향, 함수형, 명령형 등 다양한 프로그래밍 방법을 지원합니다. 경우에 따라 측면 지향 프로그래밍도 지원하지만 이 문서에서는 객체 지향 프로그래밍에 대해 설명합니다. ECMAScript 정의:

ECMAScript는 프로토타입 구현을 기반으로 하는 객체지향 프로그래밍 언어입니다.
프로토타입 기반 OOP와 정적 클래스 기반 접근 방식에는 많은 차이점이 있습니다. 차이점을 즉시 자세히 살펴보겠습니다.

클래스 속성 기반 및 프로토타입 기반

이전 문장에서 중요한 점을 지적했습니다. 이는 전적으로 정적 클래스를 기반으로 합니다. "정적"이라는 단어는 강력한 형식의(필수는 아니지만) 정적 개체와 정적 클래스를 의미합니다.

이 상황과 관련하여 포럼의 많은 문서에서는 Python과 Ruby의 구현이 다르지만(예: 동적 클래스 기반) JavaScript에서 "클래스와 프로토타입"을 비교하는 것을 반대하는 주된 이유가 이것이라고 강조했습니다. 초점에 너무 반대하지는 않습니다(일부 조건은 작성되었지만 생각에는 특정 차이가 있지만 JavaScript는 그렇게 대안이 되지 않았습니다). 그러나 반대의 초점은 정적 클래스 대 동적 프로토타입입니다), 정확하게 말하면 메커니즘은 정적 클래스(예: C, JAVA)와 그 하위 클래스 및 메서드 정의를 통해 해당 클래스와 프로토타입 기반 구현 간의 정확한 차이점을 확인할 수 있습니다.

그럼 하나씩 나열해 보겠습니다. 이러한 패러다임의 일반 원칙과 주요 개념을 고려해 보겠습니다.

정적 클래스 기반

클래스 기반 모델에는 클래스와 인스턴스라는 개념이 있습니다. 클래스의 인스턴스는 종종 개체 또는 인스턴스로 명명됩니다.

클래스와 객체

클래스는 인스턴스(즉, 객체)의 추상화를 나타냅니다. 이 점에서는 수학과 비슷하지만 우리는 이를 유형 또는 분류라고 부릅니다.

예(여기와 아래의 예는 의사 코드임):

코드 복사 코드는 다음과 같습니다.

C = 클래스 {a, b, c} // 특성 a, b, c를 포함한 클래스 C

인스턴스의 특징은 속성(객체 설명)과 메서드(객체 활동)입니다. 속성 자체도 개체로 간주될 수 있습니다. 즉, 속성이 쓰기 가능, 구성 가능, 설정 가능(getter/setter) 등인지 여부입니다. 따라서 객체는 상태(즉, 클래스에 설명된 모든 속성의 특정 값)를 저장하고 클래스는 해당 인스턴스에 대해 엄격하게 변경할 수 없는 구조(속성)와 엄격하게 변경할 수 없는 동작(메서드)을 정의합니다.
코드 복사 코드는 다음과 같습니다.

C = 클래스 {a, b, c, 메소드1, 메소드2}

c1 = {a: 10, b: 20, c: 30} // 클래스 C는 인스턴스입니다: object с1
c2 = {a: 50, b: 60, c: 70} // 클래스 C는 인스턴스입니다: 자체 상태(즉, 속성 값)를 갖는 객체 с2

계층적 상속

코드 재사용을 개선하기 위해 클래스를 서로 확장하여 추가 정보를 추가할 수 있습니다. 이 메커니즘을 (계층적) 상속이라고 합니다.

코드 복사 코드는 다음과 같습니다.

D = 클래스 확장 C = {d, e} // {a, b, c, d, e}
d1 = {a:10, b:20, c:30, d:40, e:50}

클래스의 인스턴스에서 메소드를 호출할 때 일반적으로 네이티브 클래스에서 해당 메소드를 검색합니다. 찾을 수 없는 경우 직접 상위 클래스로 이동하여 검색합니다. 검색할 상위 클래스(예: 엄격한 상속 체인에서) 상속의 최상위가 발견되었지만 아직 발견되지 않은 경우 결과는 다음과 같습니다. 객체에 유사한 동작이 없으며 결과를 얻을 수 있는 방법이 없습니다.

코드 복사 코드는 다음과 같습니다.

d1.method1() // D.method1 (아니요) -> C.method1 (예)
d1.method5() // D.method5 (아니요) -> C.method5 (아니요) -> 결과 없음

메서드가 하위 클래스에 복사되지 않는 상속과 달리 속성은 항상 하위 클래스에 복사됩니다. 하위 클래스 D가 상위 클래스 C로부터 상속받는 것을 볼 수 있습니다. 속성 a, b, c가 복사되고 D의 구조는 {a, b, c, d, e}}입니다. 그러나 {method1, method2} 메소드는 과거에서 복사된 것이 아니라 과거에서 상속되었습니다. 따라서 딥 클래스에 객체에 전혀 필요하지 않은 일부 속성이 있는 경우 하위 클래스에도 이러한 속성이 있습니다.

수업별 핵심 개념

따라서 다음과 같은 핵심 개념이 있습니다.

1. 객체를 생성하기 전에 먼저 클래스를 정의해야 합니다
2. 따라서 객체는 고유한 "아이콘 및 유사성"(구조 및 동작)으로 추상화된 클래스에서 생성됩니다
3. 메소드는 엄격하고 직접적이며 불변적인 상속 체인을 통해 처리됩니다
4. 하위 클래스에는 상속 체인의 모든 속성이 포함됩니다(일부 속성이 하위 클래스에 필요하지 않은 경우에도 마찬가지). 5. 클래스 인스턴스를 생성합니다. 클래스는 (정적 모델로 인해) 해당 인스턴스의 특성(속성 또는 메서드)을 변경할 수 없습니다. 6. 인스턴스(엄격한 정적 모델로 인해)는 해당 인스턴스에 해당하는 클래스에 선언된 것 이외의 추가 동작이나 속성을 가질 수 없습니다.

우리가 프로토타입 OOP를 기반으로 제안하는 OOP 모델을 JavaScript로 대체하는 방법을 살펴보겠습니다.

프로토타입 기준

여기서 기본 개념은 동적 변경 가능 개체입니다. 변환(값뿐만 아니라 속성도 포함하는 완전한 변환)은 동적 언어와 직접적으로 관련됩니다. 다음과 같은 객체는 클래스 없이도 모든 속성(속성, 메서드)을 독립적으로 저장할 수 있습니다.



객체 = {a: 10, b: 20, c: 30, 메소드: fn};
객체.a; // 10
객체.c; // 30
object.method();


또한 동적이므로 특성을 쉽게 변경(추가, 삭제, 수정)할 수 있습니다.


object.method5 = function () {...}; // 새로운 메소드 추가
object.d = 40; // 새 속성 "d" 추가
delete object.c; // "с" 속성 삭제
object.a = 100; // "а" 속성 수정

// 결과는 다음과 같습니다: object: {a: 100, b: 20, d: 40, method: fn, method5: fn};


즉, 할당 시 어떤 속성이 존재하지 않으면 이를 생성하여 할당을 초기화하고, 존재하면 업데이트하면 됩니다.

이 경우 코드 재사용은 클래스 확장으로 이루어지지 않고(여기서는 클래스 개념이 없기 때문에 클래스를 변경할 수 없다고 말하지 않았음을 참고하세요) 프로토타입으로 이루어집니다.

프로토타입은 다른 객체의 기본 복사본으로 사용되는 객체입니다. 또는 일부 객체에 자체 필수 속성이 없는 경우 프로토타입을 이러한 객체의 대리자로 사용하여 보조 객체로 사용할 수 있습니다. .

위임 기반

어떤 객체든 다른 객체의 프로토타입 객체로 사용될 수 있습니다. 객체는 런타임 시 프로토타입을 동적으로 쉽게 변경할 수 있기 때문입니다.

현재는 구체적인 구현보다는 개요를 고려하고 있다는 점에 유의하세요. ECMAScript의 특정 구현을 논의할 때 그 고유한 특성 중 일부를 보게 될 것입니다.

예(의사 코드):


x = {a: 10, b: 20};
y = {a: 40, c: 50};
y.[[Prototype]] = x; // x는 y의 프로토타입입니다

ya.a; // 40, 자신의 특성
y.c; // 50, 그 자체의 특성
y.b; // 20 – 프로토타입에서 획득: y.b (no) -> y.[[Prototype]].b (yes): 20

delete ya.a; // 자체 "а" 삭제
ya.a; // 10 – 프로토타입에서
가져오기
z = {a: 100, e: 50}
y.[[Prototype]] = z; // y의 프로토타입을 z로 수정
ya.a; // 100 – 프로토타입 z에서
가져오기 y.e // 50, 역시 프로토타입 z에서 획득

z.q = 200 // 프로토타입에 새 속성 추가
y.q // 수정 사항은 y에도 적용됩니다

이 예는 자체 속성을 요청하는 것처럼 보조 객체 속성으로서 프로토타입의 중요한 기능과 메커니즘을 보여줍니다. 이러한 속성은 자체 속성과 비교할 때 위임 속성입니다. 이 메커니즘을 대리자(Delegate)라고 하며, 이를 기반으로 한 프로토타입 모델이 대리자 프로토타입(또는 대리자 기반 프로토타입)입니다. 여기서 참조 메커니즘은 객체에 메시지를 보내는 것입니다. 객체가 응답을 받지 못하면 이를 찾기 위해 프로토타입에 위임됩니다(메시지에 응답하도록 요구함).

이 경우 코드 재사용을 위임 기반 상속 또는 프로토타입 기반 상속이라고 합니다. 어떤 객체든 프로토타입으로 사용할 수 있으므로 프로토타입도 자체 프로토타입을 가질 수 있습니다. 이러한 프로토타입은 서로 연결되어 소위 프로토타입 체인을 형성합니다. 체인도 정적 클래스처럼 계층적이지만 쉽게 재배열하여 계층과 구조를 변경할 수 있습니다.

코드 복사 코드는 다음과 같습니다.

x = {a: 10}

y = {b: 20}
y.[[프로토타입]] = x

z = {c: 30}
z.[[프로토타입]] = y

z.a // 10

// z.a는 프로토타입 체인에서 발견됩니다:
// z.a (아니요) ->
// z.[[프로토타입]].a (아니요) ->
// z.[[프로토타입]].[[프로토타입]].a (예): 10

객체와 해당 프로토타입 체인이 메시지 전송에 응답할 수 없는 경우 객체는 해당 시스템 신호를 활성화할 수 있으며 프로토타입 체인의 다른 대리인이 처리할 수 있습니다.

이 시스템 신호는 대괄호로 묶인 동적 클래스(Smalltalk의 #doesNotUnderstand, Ruby의 method_missing, Python의 __getattr__, PHP의 __call 및 ECMAScript의 __noSuchMethod__ 구현)를 포함한 다양한 구현에서 사용할 수 있습니다.

예(SpiderMonkey의 ECMAScript 구현):

코드 복사 코드는 다음과 같습니다.

var 객체 = {

//메시지에 응답할 수 없는 시스템 신호 잡기
__noSuchMethod__: 함수(이름, 인수) {
경고([이름, 인수]);
If (이름 == '테스트') {
         return '.test() 메소드가 처리됨';
}
반환 대리자[이름].apply(this, args);
}

};

var 대리자 = {
사각형: 함수 (a) {
*a를 반환합니다;
}
};

경고(object.square(10)); // 100
Alert(object.test()); // .test() 메소드가 처리됩니다

즉, 정적 클래스 기반 구현이 메시지에 응답할 수 없는 경우 현재 객체에 필요한 특성이 없다는 결론이 나지만 프로토타입 체인에서 이를 얻으려고 하면 여전히 또는 일련의 변경 후에 개체가 이 특성을 갖습니다.

ECMAScript와 관련하여 구체적인 구현은 대리자 기반 프로토타입을 사용하는 것입니다. 그러나 사양 및 구현에서 볼 수 있듯이 고유한 특성도 있습니다.

연결 모델

솔직히 다른 상황(ECMASCript에서 사용되지 않는 즉시)에 대해 말할 필요가 있습니다. 즉, 프로토타입이 다른 개체의 기본 개체를 대체하는 상황입니다. 이 경우 코드 재사용은 위임이 아니라 개체 생성 단계에서 개체의 실제 복사본(복제)입니다. 이런 종류의 프로토타입을 연결 프로토타입이라고 합니다. 개체의 모든 프로토타입 속성을 복사하면 해당 속성과 메서드가 완전히 변경될 수 있으며 프로토타입 자체도 변경할 수 있습니다(대리자 기반 모델에서 이 변경은 기존 개체 동작을 변경하지 않지만 프로토타입 속성을 변경합니다). 이 방법의 장점은 스케줄링 및 위임 시간을 줄일 수 있다는 점이지만, 메모리 사용량이 높다는 단점이 있습니다.

오리형

약한 유형을 동적으로 변경하는 객체를 반환하는 것은 정적 클래스 기반 모델과 비교할 때 이러한 작업을 수행할 수 있는지 여부를 테스트하는 것은 객체의 유형(클래스)과 관련이 없으며 메시지에 응답할 수 있는지(즉, 할 수 있는 능력이 필수인지 확인하신 후 입니다.

예:

코드 복사 코드는 다음과 같습니다.

// 정적 기반 모델에서
if (SomeClass의 객체 인스턴스) {
// 일부 작업이 실행됩니다
}

// 동적 구현
// 이 시점에서는 객체의 유형이 무엇인지는 중요하지 않습니다
// 변이, 유형, 속성은 자유롭고 반복적으로 변형될 수 있기 때문입니다.
// 중요한 객체가 테스트 메시지에 응답할 수 있는지 여부
if (isFunction(object.test)) // ECMAScript

if object.respond_to?(:test) // 루비

if hasattr(object, 'test'): // Python

일명 Dock형입니다. 즉, 계층 구조에서 개체의 위치나 특정 유형에 속하는 개체가 아닌 검사 시 개체 자체의 특성으로 개체를 식별할 수 있습니다.

프로토타입 기반 핵심 컨셉

이 접근 방식의 주요 특징을 살펴보겠습니다.

1. 기본 개념은 객체
2. 객체는 완전히 동적이며 가변적입니다(이론적으로 한 유형에서 다른 유형으로 변환 가능)
3. 객체에는 자체 구조와 동작을 설명하는 엄격한 클래스가 없습니다.
4. 객체에는 클래스가 없지만 프로토타입은 있을 수 있습니다. 메시지에 응답할 수 없는 경우 프로토타입에 위임할 수 있습니다.
5. 객체의 프로토타입은 런타임 중에 언제든지 변경될 수 있습니다.
6. 위임 기반 모델에서 프로토타입의 특성을 변경하면 프로토타입과 관련된 모든 객체에 영향을 미칩니다.
7. 연결 프로토타입 모델에서 프로토타입은 다른 개체에서 복제된 원본 복사본이며 더 나아가 완전히 독립된 복사본 원본이 됩니다. 프로토타입 특성의 변형은 복제된 개체에 영향을 미치지 않습니다.
8. 메시지에 응답할 수 없는 경우 발신자는 추가 조치(예: 일정 변경)를 취할 수 있습니다.
9. 개체의 고장은 개체의 레벨과 클래스에 따라 결정될 수 없으며 현재 특성에 따라 결정됩니다

그러나 고려해야 할 또 다른 모델이 있습니다.

동적 클래스 기반

위 예에 표시된 "클래스 VS 프로토타입" 구별은 동적 클래스를 기반으로 하는 이 모델에서는 그다지 중요하지 않다고 생각합니다. (특히 프로토타입 체인이 불변인 경우 보다 정확한 구별을 위해서는 여전히 다음이 필요합니다. 정적 클래스를 고려하십시오). 예를 들어 Python이나 Ruby(또는 기타 유사한 언어)를 사용할 수도 있습니다. 이들 언어는 모두 동적 클래스 기반 패러다임을 사용합니다. 그러나 어떤 측면에서는 프로토타입을 기반으로 구현된 일부 기능을 볼 수 있습니다.

다음 예에서는 위임만을 기반으로 클래스(프로토타입)를 확장하여 이 클래스와 관련된 모든 개체에 영향을 미칠 수 있음을 알 수 있습니다. 또한 런타임에 이 개체를 동적으로 변경할 수도 있습니다. 대리인에 대한 객체) 등이 있습니다.

코드 복사 코드는 다음과 같습니다.

#파이썬

클래스 A(객체):

Def __init__(self, a):
         self.a = a

데프 스퀘어(자신):
          self.a * self.a 반환

a = A(10) # 인스턴스 생성
인쇄(a.a) # 10

A.b = 20 # 클래스에 새 속성을 제공합니다
print(a.b) # 20 –
는 "a" 인스턴스에서 액세스할 수 있습니다.
a.b = 30 # a 자신만의 속성을 생성합니다
인쇄(a.b) # 30

del a.b # 자체 속성 삭제
print(a.b) # 20 - 클래스에서 다시 (프로토타입) 가져오기

# 마치 프로토타입 기반 모델처럼
# 런타임에 객체의 프로토타입을 변경할 수 있습니다

클래스 B(객체): # 빈 클래스 B
합격

b = B() # B의 인스턴스

b.__class__ = A # 클래스(프로토타입)를 동적으로 변경

b.a = 10 # 새 속성 생성
print(b.square()) # 100 - 현재 클래스 A 메소드를 사용할 수 있습니다

# 삭제된 수업에 대한 참조를 표시할 수 있습니다
델 A
델 B

# 하지만 객체에는 여전히 암시적 참조가 있으며 이러한 메서드를 계속 사용할 수 있습니다
print(b.square()) # 100

# 단, 현재는 수업을 변경할 수 없습니다
# 구현된 기능입니다
b.__class__ = dict # 오류

Ruby의 구현은 비슷합니다. 완전 동적 클래스도 사용됩니다(단, Python의 현재 버전에서는 Ruby 및 ECMAScript와 달리 클래스(프로토타입) 확대가 작동하지 않습니다). 객체를 완전히 변경할 수 있습니다. (또는 클래스) 특성(클래스에 메서드/속성 추가, 이러한 변경 사항은 기존 객체에 영향을 미침) 그러나 객체의 클래스를 동적으로 변경할 수는 없습니다.

그러나 이 기사는 특별히 Python과 Ruby에 관한 것이 아니므로 더 이상 언급하지 않고 ECMAScript 자체에 대해 계속 논의하겠습니다.

하지만 그 전에 일부 OOP에서 발견되는 "구문적 설탕"을 다시 살펴보아야 합니다. 왜냐하면 JavaScript에 관한 이전의 많은 기사에서 이러한 문제를 다루는 경우가 많았기 때문입니다.

이 섹션에서 주의해야 할 유일한 잘못된 문장은 "JavaScript는 클래스가 아니며 클래스를 대체할 수 있는 프로토타입이 있습니다."입니다. 모든 클래스 기반 구현이 완전히 다른 것은 아니라는 점을 아는 것이 중요합니다. "JavaScript는 다르다"고 말할 수 있지만 ("클래스" 개념 외에도) 다른 관련 특성이 있다는 점도 고려해야 합니다. .

다양한 OOP 구현의 기타 기능

이 섹션에서는 ECMAScript의 OOP 구현을 포함하여 다양한 OOP 구현에서 코드 재사용의 다른 기능과 방법을 간략하게 소개합니다. 그 이유는 JavaScript에서 OOP를 구현하는 데에는 몇 가지 습관적인 사고 제한이 있기 때문입니다. 유일한 주요 요구 사항은 기술적으로나 이념적으로 입증되어야 한다는 것입니다. 우리는 다른 OOP 구현에서 구문적 설탕 기능을 발견하지 못했다고 말할 수 없으며 JavaScript가 순수한 OOP 언어가 아니라고 성급하게 가정했습니다. 이것은 잘못된 것입니다.

다형성

ECMAScript에서 객체는 다형성이라는 여러 가지 의미를 갖습니다.

예를 들어 함수는 네이티브 객체의 속성과 마찬가지로 다른 객체에 적용될 수 있습니다(실행 컨텍스트에 들어갈 때 값이 결정되기 때문입니다).

코드 복사 코드는 다음과 같습니다.

함수 테스트() {
Alert([this.a, this.b]);
}

test.call({a: 10, b: 20}) // 10, 20
test.call({a: 100, b: 200}) // 100, 200

var a = 1;
var b = 2;

테스트(); // 1, 2

그러나 예외가 있습니다. 표준에 따라 항상 날짜 객체를 가져야 하는 Date.prototype.getTime() 메서드입니다. 그렇지 않으면 예외가 발생합니다.
코드 복사 코드는 다음과 같습니다.

Alert(Date.prototype.getTime.call(new Date())) // 시간
Alert(Date.prototype.getTime.call(new String(''))) // TypeError

함수를 정의할 때 소위 매개변수 다형성은 다형성 매개변수(예: 배열의 .sort 정렬 방법 및 해당 매개변수 - 다형성 정렬 함수)를 허용한다는 점을 제외하고 모든 데이터 유형과 동일합니다. 그런데 위의 예도 일종의 파라메트릭 다형성으로 볼 수 있다.

프로토타입의 메서드는 비어 있는 것으로 정의할 수 있으며, 생성된 모든 객체는 이 메서드를 재정의(구현)해야 합니다(예: "하나의 인터페이스(서명), 다중 구현").

다형성은 위에서 언급한 Duck 유형과 관련이 있습니다. 즉, 계층 구조에서 개체의 유형과 위치는 그다지 중요하지 않지만, 필요한 특성을 모두 갖춘 경우 쉽게 수용할 수 있습니다(예: 공통 인터페이스가 중요함) , 구현은 다양할 수 있습니다).

캡슐화

캡슐화에 대해 오해가 있는 경우가 많습니다. 이 섹션에서는 OOP 구현의 몇 가지 구문적 설탕(수정자라고도 함)에 대해 논의합니다. 이 경우 OOP 구현의 몇 가지 편리한 "설탕"에 대해 논의합니다. 잘 알려진 수정자: 개인, 보호 및 공개(또는 객체 액세스라고도 함) 수준 또는 액세스 한정자).

여기서 저는 캡슐화의 주요 목적을 상기시키고 싶습니다. 캡슐화는 클래스에 직접 뭔가를 쓰는 숨겨진 "악의적 해커"가 아니라 추상적 추가입니다.

이것은 큰 실수입니다. 숨기기 위해 hide를 사용하세요.

프로그래밍을 용이하게 하고(정말로 매우 편리한 구문 설탕) 시스템을 보다 추상적으로 설명하고 구축하기 위해 액세스 수준(개인, 보호 및 공개)이 많은 객체 지향 프로그램에서 구현되었습니다.

이는 일부 구현에서 볼 수 있습니다(이미 언급한 Python 및 Ruby 등). 한편으로 (Python에서) 이러한 __private_protected 속성(밑줄 규칙을 통해 이름 지정)은 외부에서 액세스할 수 없습니다. 반면 Python은 특수 규칙(_ClassName__field_name)을 사용하여 외부에서 액세스할 수 있습니다.

코드 복사 코드는 다음과 같습니다.

클래스 A(객체):

Def __init__(self):
        self.public = 10
        self.__private = 20

Def get_private(self):
          self.__private
반환
# 외부:

a = A() # A의 인스턴스

print(a.public) # OK, 30
print(a.get_private()) # 좋아요, 20
print(a.__private) # A에서만 사용할 수 있으므로 실패했습니다

# 하지만 Python에서는 특별한 규칙을 통해 접근할 수 있습니다

print(a._A__private) # 좋아요, 20

Ruby에는 개인 및 보호 특성을 정의하는 기능이 있는 반면, 캡슐화된 데이터를 얻기 위한 특수 메서드(예: 인스턴스_변수_get, 인스턴스_변수_세트, 전송 등)도 있습니다.

코드 복사 코드는 다음과 같습니다.

A반

초기화
@a = 10


def public_method
private_method(20)


비공개

def private_method(b)
@ab반환




a = A.new # 새 인스턴스

a.public_method # OK, 30

a.a # 실패, @a - 전용 인스턴스 변수입니다

# "private_method"는 비공개이며 클래스 A에서만 접근할 수 있습니다

a.private_method # 오류

# 하지만 데이터를 얻을 수 있는 특별한 메타데이터 메소드 이름이 있습니다

a.send(:private_method, 20) # OK, 30
a.instance_variable_get(:@a) # OK, 10

주된 이유는 프로그래머 자신이 캡슐화된(특히 "숨겨진" 데이터를 사용하지 않는다는 점에 유의하세요) 데이터를 얻고 싶어하기 때문입니다. 이 데이터가 어떤 방식으로든 잘못 변경되거나 오류가 있는 경우 모든 책임은 프로그래머에게 있지만 단순히 "오류 입력"이나 "일부 필드 변경"만은 아닙니다. 그러나 이런 일이 자주 발생한다면 이는 매우 나쁜 프로그래밍 습관과 스타일입니다. 일반적으로 공용 API를 사용하여 객체와 "대화"하는 것이 가치가 있기 때문입니다.

다시 말하지만, 캡슐화의 기본 목적은 해커가 데이터를 숨기는 것을 방지하는 것이 아니라 보조 데이터 사용자를 추상화하는 것입니다. 더 심각한 것은 캡슐화는 소프트웨어 보안을 달성하기 위해 개인 정보를 사용하여 데이터를 수정하지 않는다는 것입니다.

보조 객체 캡슐화(부분). 공개 인터페이스의 동작 변경에 대한 타당성을 제공하기 위해 최소한의 비용, 현지화 및 예측 변경을 사용합니다.

또한 setter 메소드의 중요한 목적은 복잡한 계산을 추상화하는 것입니다. 예를 들어, element.innerHTML setter - 추상문 - "이 요소 내부의 HTML은 이제 다음 내용입니다", innerHTML 속성의 setter 함수는 계산하고 확인하기 어려울 것입니다. 이 경우 문제는 주로 추상화와 관련이 있지만 캡슐화도 발생합니다.

캡슐화의 개념은 OOP에만 관련된 것이 아닙니다. 예를 들어, 다양한 계산만 캡슐화하여 추상화하는 간단한 함수일 수 있습니다(예를 들어 Math.round(...) 함수가 어떻게 구현되는지 사용자가 알 필요가 없으며 사용자는 간단히 호출합니다. 그것). 이는 일종의 캡슐화입니다. "비공개, 보호 및 공개"라고 말하지 않았습니다.

ECMAScript 사양의 현재 버전은 private, protected 및 public 한정자를 정의하지 않습니다.

그러나 실제로는 "Mock JS Encapsulation"이라는 것을 볼 수 있습니다. 일반적으로 이 컨텍스트는 사용하기 위한 것입니다(원칙적으로 생성자 자체). 불행하게도 이 "모방"은 종종 구현되며 프로그래머는 의사-완전히 비추상적인 엔터티 설정 "getter/setter 메서드"를 생성할 수 있습니다(반복합니다. 틀렸습니다):

코드 복사 코드는 다음과 같습니다.

함수 A() {

var _a; // "비공개"a

this.getA = 함수 _getA() {
_a를 반환;
};

this.setA = 함수 _setA(a) {
_a =a;
};

}

var a = new A();

a.setA(10);
경보(a._a); // 정의되지 않음, "비공개"
경고(a.getA()); // 10

그래서 모든 객체가 생성될 때마다 getA/setA 메서드도 생성된다는 점을 모두가 이해하고 있으며, 이는 프로토타입 정의와 비교하여 메모리가 증가하는 이유이기도 합니다. 하지만 이론적으로는 첫 번째 경우에 개체를 최적화할 수 있습니다.

또한 일부 JavaScript 기사에서는 "비공개 메서드" 개념을 자주 언급합니다. 참고: ECMA-262-3 표준은 "비공개 메서드" 개념을 정의하지 않습니다.

그러나 어떤 경우에는 JS가 이데올로기적 언어이기 때문에 생성자에서 생성될 수 있습니다. 객체는 완전히 변경 가능하고 고유한 특성을 갖습니다(생성자의 특정 조건에서 일부 객체는 추가 메소드를 얻을 수 있지만 다른 객체는 그렇지 않습니다). ).

또한 JavaScript에서 캡슐화가 여전히 악의적인 해커가 setter 메서드를 사용하는 대신 특정 값을 자동으로 작성하지 못하도록 막는 것으로 잘못 해석되면 소위 "숨겨진" 및 "개인"이 실제로 "숨겨져 있지" 않은 경우, 일부 구현에서는 eval 함수에 대한 컨텍스트를 호출하여 관련 범위 체인(및 해당하는 모든 변수 개체)에서 값을 얻을 수 있습니다(SpiderMonkey1.7에서 테스트 가능).

코드 복사 코드는 다음과 같습니다.

eval('_a = 100', a.getA); // 또는 a.setA, "_a" 두 메서드가 [[Scope]]
에 있기 때문입니다. a.getA(); // 100

또는 구현을 통해 활성 개체(예: Rhino)에 직접 액세스할 수 있으며, 개체의 해당 속성에 액세스하여 내부 변수의 값을 변경할 수 있습니다.

코드 복사 코드는 다음과 같습니다.

// 코뿔소
var foo = (함수 () {
var x = 10; // "비공개"
반환 함수 () {
인쇄(x);
};
})();
foo(); // 10
foo.__parent__.x = 20;
foo(); // 20

때로는 변수 앞에 밑줄을 붙여 JavaScript에서 "개인" 및 "보호된" 데이터를 얻을 수도 있습니다(그러나 Python과 비교하면 이는 단지 명명 규칙일 뿐입니다).

var _myPrivateData = 'testString';
실행 컨텍스트를 괄호로 묶는 데 자주 사용되지만 실제 보조 데이터의 경우 객체와 직접 관련이 없으며 외부 API에서 추상화하는 데 편리합니다.

코드 복사 코드는 다음과 같습니다.

(함수 () {

// 컨텍스트 초기화

})();

다중 상속

다중 상속은 코드 재사용을 개선하기 위한 매우 편리한 구문 설탕입니다(한 번에 하나의 클래스를 상속할 수 있다면 왜 한 번에 10개 클래스를 상속할 수 없습니까?). 그러나 다중 상속의 몇 가지 단점으로 인해 널리 구현되지는 않았습니다.

ECMAScript는 다중 상속을 지원하지 않습니다(즉, 하나의 객체만 직접 프로토타입으로 사용할 수 있음). 하지만 조상 프로그래밍 언어에는 이러한 기능이 있습니다. 그러나 SpiderMonkey와 같은 일부 구현에서는 __noSuchMethod__를 사용하여 프로토타입 체인 대신 일정 및 위임을 관리하는 데 사용할 수 있습니다.

믹스

믹스인은 코드를 재사용하는 편리한 방법입니다. 다중 상속의 대안으로 믹스인이 제안되었습니다. 이러한 개별 요소 각각은 기능을 확장하기 위해 모든 개체와 혼합될 수 있습니다. 따라서 개체는 여러 믹스인과 혼합될 수도 있습니다. ECMA-262-3 사양에는 "Mixins"라는 개념이 정의되어 있지 않지만, Mixin의 정의에 따르면 ECMAScript에는 동적으로 변경 가능한 객체가 있으므로 Mixin을 사용하여 단순히 기능을 확장하는 데 장애가 없습니다.

전형적인 예:

코드 복사 코드는 다음과 같습니다.

// 증강을 위한 도우미
Object.extend = 함수(대상, 소스) {
for (소스의 속성) if (source.hasOwnProperty(property)) {
목적지[속성] = 소스[속성];
}
귀국지;
};

var X = {a: 10, b: 20};
var Y = {c: 30, d: 40};

Object.extend(X, Y); // Y를 X에 섞습니다
경고([X.a, X.b, X.c, X.d]); 10, 20, 30, 40

ECMA-262-3에 언급된 따옴표 안에 이러한 정의("mixin", "mix")를 사용한다는 점에 유의하시기 바랍니다. 사양에는 그러한 개념이 없으며 mix가 아니라 객체를 확장하는 데 일반적으로 사용됩니다. 새로운 기능으로. (Ruby에서 믹스인의 개념은 공식적으로 정의되어 있습니다. 믹스인은 단순히 모듈의 모든 속성을 다른 모듈에 복사하는 대신 포함하는 모듈에 대한 참조를 생성합니다. 실제로는 델리게이트에 대한 추가 객체(프로토타입)를 생성하는 것입니다.)

특성

특성은 개념상 믹스인과 유사하지만 많은 기능을 가지고 있습니다(정의상 믹스인은 적용 가능하므로 이름 충돌이 발생할 수 있으므로 상태를 포함할 수 없습니다). ECMAScript에 따르면 Traits와 mixin은 동일한 원칙을 따르므로 사양에서는 "Traits"의 개념을 정의하지 않습니다.

인터페이스

일부 OOP에 구현된 인터페이스는 믹스인 및 특성과 유사합니다. 그러나 믹스인 및 특성과 달리 인터페이스는 구현 클래스가 해당 메서드 시그니처의 동작을 구현하도록 강제합니다.

인터페이스는 완전히 추상 클래스로 간주될 수 있습니다. 그러나 추상 클래스(추상 클래스의 메서드는 메서드의 일부만 구현할 수 있고 다른 부분은 여전히 ​​시그니처로 정의됨)와 비교할 때 상속은 단일 기본 클래스만 상속할 수 있지만 여러 인터페이스를 상속할 수 있습니다. 인터페이스(다중 혼합)는 다중 상속의 대안으로 볼 수 있습니다.

ECMA-262-3 표준은 "인터페이스" 개념이나 "추상 클래스" 개념을 정의하지 않습니다. 그러나 모방으로서 "빈" 메소드(또는 개발자에게 이 메소드를 구현해야 함을 알리기 위해 빈 메소드에 예외가 발생함)를 사용하여 객체를 구현하는 것이 가능합니다.

객체 조합

객체 합성 역시 동적 코드 재사용 기술 중 하나입니다. 개체 구성은 동적으로 변경 가능한 대리자를 구현한다는 점에서 매우 유연한 상속과 다릅니다. 그리고 이것은 또한 의뢰된 프로토타입을 기반으로 합니다. 동적으로 변경 가능한 프로토타입 외에도 개체는 대리자를 위한 개체를 집계하고(결과로 조합 생성, 집계) 대리자에게 위임하는 개체에 메시지를 보낼 수 있습니다. 이는 동적 특성이 런타임에 변경될 수 있음을 의미하므로 2개 이상의 대리자를 사용하여 수행할 수 있습니다.

이미 언급한 __noSuchMethod__ 예에서는 이를 수행하지만 대리자를 명시적으로 사용하는 방법도 보여드리겠습니다.

예:

코드 복사 코드는 다음과 같습니다.

var _delegate = {
foo: 함수 () {
경고('_delegate.foo');
}
};

var 집계 = {

대의원: _delegate,

foo: 함수 () {
this.delegate.foo.call(this)을 반환하세요.
}

};

Aggregate.foo(); // 대리자.foo

Aggregate.delegate = {
foo: 함수 () {
Alert('새 대리인의 foo');
}
};

Aggregate.foo(); // 새로운 대리자의 foo

이러한 대상 관계를 "has-a" 관계라고 하며 통합은 "is-a" 관계입니다.

명시적 구성(상속 대비 유연성)이 부족하므로 중간 코드를 추가하는 것도 괜찮습니다.

AOP 기능

Aspect 지향 함수로 함수 데코레이터를 사용할 수 있습니다. ECMA-262-3 사양은 "함수 데코레이터"의 개념을 명확하게 정의하지 않습니다(이 용어가 공식적으로 정의된 Python과 반대). 그러나 기능적 매개변수가 있는 기능은 특정 측면에서 장식되고 활성화될 수 있습니다(소위 제안을 적용하여).

가장 간단한 데코레이터 예:

코드 복사 코드는 다음과 같습니다.

함수 checkDecorator(originalFunction) {
반환 함수 () {
If (fooBar != '테스트') {
경고('잘못된 매개변수');
거짓을 반환합니다.
}
원래 함수()를 반환합니다.
};
}

함수 테스트() {
Alert('테스트 기능');
}

var testWithCheck = checkDecorator(테스트);
var fooBar = false;

test(); // '테스트 함수'
testWithCheck(); // '잘못된 매개변수'

fooBar = '테스트';
test(); // '테스트 함수'
testWithCheck(); // '테스트 함수'

결론

이 기사에서는 OOP의 소개를 명확히 했으며(이 정보가 도움이 되었기를 바랍니다) 다음 장에서는 객체 지향 프로그래밍을 위한 ECMAScript 구현을 계속하겠습니다.

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.