Что означает наследование дескрипторов

Обновлено: 02.07.2024

Псевдодескриптор (pseudohandle) представляет собой упрощенный вариант дескриптора. По определению, псевдодескриптор - это зависимое от процесса число, которое служит идентификатором процесса и может использоваться в вызовах тех API-функций, которым требуемся дескриптор процесса.

Хотя назначение псевдодескрипторов и обычных дескрипторов почти одно и то же, у них все же есть некоторые существенные различия. Псевдодескрипторы не могут наследоваться порожденными процессами, как настоящие дескрипторы (real handler). К тому же псевдодескрипторы ссылаются только на текущий процесс, а настоящие дескрипторы могут ссылаться и на внешний (foreign).

Windows предоставляет возможность получения настоящего дескриптора по псевдодескриптору при помощи API-функции DuplicateHandle. Она определяется так:

BOOL DuplicateHandle (

HANDLE hSourceProcessHandle , // Дескриптор процесса-источника.
HANDLE hSourceHandle , // Копируемый дескриптор.
HANDLE hTargetProcessHandle , // Дескриптор процесса-приемника.
LPHANDLE IpTargetHandle , // Указатель на копию дескриптора.
DWORD dwDesiredAccess , // Доступ к копии дескриптора.
BOOL bInheritHandle , // Флаг наследования дескриптора.
DWORD dwOptions // Необязательные опции.

С использованием выше приведенных API-функций можно разработать программу

(второе задание в лабораторной работе), которая будет выполнять следующее:

1. Используя функцию GetCurrentProcessId определяет идентификатор текущего процесса.

2. Используя функцию GetCurrentProcess определяет псевдодескриптор текущего процесса.

3. Используя функцию DuplicateHandl и значение псевдодескриптора определяет дескриптора текущего процесса.

4. Используя функцию OpenProcess определяет копию дескриптора текущего процесса.

5. Закрывает дескриптор, полученный функцией DuplicateHandl .

6. Закрывает дескриптор, полученный функцией OpenProcess .

Настоящий дескриптор обязательно должен быть закрыт, а псевдодескриптор закрывать не нужно. Вывод на компьютере может быть таким:

Идентификатор текущего процесса: 183

Псевдодескриптор: 4294967295

Дескриптор, полученный функцией DuplicateHandle : 412

Дескриптор, полученный функцией OpenProcess : 392

Закрываем дескриптор, полученный функцией OpenProcess : 392

Закрываем дескриптор, полученный функцией DuplicateHandle : 412

Перечисление процессов

Рассмотрим проблему перечисления процессов, потоков и модулей в операционной системе Windows (это третье задание в этой работе). Способы ее решения в Windows NT и Windows 9x различны.

Перечисление процессов в Windows 9x (использование ToolHelp32)

ToolHelp32 — это семейство функций и процедур, составляющих подмножество Win32 API, которые позволяют получить сведения о некоторых низкоуровневых аспектах работы ОС, В частности, сюда входят функции, с помощью которых можно получить информацию обо всех процессах, выполняющихся в системе в данный момент, а также потоках, модулях, принадлежащих каждому процессу. Большинство данных, получаемых от функций ToolHelp32, используется главным образом приложениями, которые должны заглядывать "внутрь" ОС.

Семейство процедур и функций ToolHelp32 API доступно только в варианте
реализации Win32 для Windows 95/98. В среде Windows NT вызов их приведет
к нарушению системы защиты и безопасности NT-процессов. Поэтому приложения, которые используют функции ToolHelp32, работоспособны только под
управлением Windows 95/98, но не Windows NT.

Типы и определения функций ТооlНе1р32 размещаются в модуле TlHelp32, поэтому при работе с этими функциями не забудьте включить его имя в список инструкции uses ( в Delphi).

Моментальные снимки

Благодаря многозадачной природе среды Win32 такие объекты, как процессы, потоки, модули и т.п., постоянно создаются, разрушаются и модифицируются. И поскольку состояние компьютера непрерывно изменяется, системная информация, которая, возможно, будет иметь значение в данный момент, через секунду уже никого не заинтересует. Например, предположим, что вы хотите написать программу для регистрации всех модулей, загруженных в систему. Поскольку операционная система в любое время может прервать выполнение потока, отрабатывающего вашу программу, чтобы предоставить какие-то кванты времени другому потоку в системе, модули теоретически могут создаваться и разрушаться даже в момент выборки информации о них.

В этой динамической среде имело бы смысл на мгновение заморозить систему, чтобы получить такую системную информацию. В ТооlНе1р32 не предусмотрено средств замораживания системы, но есть функция, с помощью которой можно сделать "снимок" системы в заданный момент времени. Эта функция называется CreateToolhelp32Snapshot(), и ее объявление (в Delphi) выглядит следующим образом:




function CreateToolhelp32Snapshot(dwFlags, th32ProcesslD: DWORD): THandle; stdcall;

• Параметр dwFlags означает тип информации, подлежащий включению в моментальный снимок. Этот параметр может иметь одно из перечисленных в таблице значений.

Значение Описание
TH32CS_INHERIT Означает, что дескриптор снимка будет наследуемым
TH32CS_SNAPALL Эквивалентно заданию значений TH32CS_snapheaplist, TH32CS_SNAPMODULE, TH32CS_SNAPPROCESS И TH32CS_SNAPTHREAD
TH32CS_SNAPHEAPLIST Включает в снимок список куч заданного процесса Win32
TH32CS_SNAPMODULE Включает в снимок список модулей заданного процесса Win32
TH32CS_SNAPPROCESS Включает в снимок список процессов Win32
TH32CS_ SNAPTHREAD Включает в снимок список потоков Win32

• Функция CreateToolhelp32Snapshot() возвращает дескриптор созданного снимка или -1 в случае ошибки. Возвращаемый дескриптор работает подобно другим дескрипторам Win32 относительно процессов и потоков, для которых он действителен.

Следующий код создает дескриптор снимка, который содержит информацию обо всех процессах, загруженных в настоящий момент (EToolHelpError — это исключительная ситуация, определенная программистом):

Snap := CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);

if Snap = -1 then

raise EToolHelpError.Create('CreateToolHelp32Snapshot failed');

По завершении работы с созданным функцией CreateToolhelp32Snapshot() дескриптором, для освобождения связанных с ним ресурсов используйте функцию CloseHandle().




Как известно, тема наследования в JS тесно связана с таким механизмом, как прототипы, сведения о которых, будучи незамысловаты, в то же время могут вызвать трудности с понимаем общей картины. Нередко на постижение материала уходит какое-то время, а ответы приходится искать на форумах.

После возвращения к конспектам решил выбрать публичный формат изложения, из-за чего были добавлены некоторые связующие пункты. В итоге получилась небольшая, не претендующая на академический характер статья, которая может быть полезна в качестве дополнительного материала для изучающих JavaScript.

ОТР , Удалённо , От 70 000 до 150 000 ₽

Подразумевается, что читатель знаком с основами языка и вообще ООП.

Содержание:

1. Объект

Под объектом понимается тип данных, реализованных в виде набора свойств (полей и методов), имеющих имя и значение, а также экземпляр этого типа. Например, машина — это объект, и конкретный экземпляр, выпущенный на заводе, — тоже объект.

Ключевое слово this в методе объекта указывает на сам же объект и используется для обращения к его свойствам. Заметим, что свойства в js могут назначаться объекту не только при его создании, но и после. Также заметим, что возможно обращение к несуществующим полям, и они будут равны undefined , но обращение к несуществующим методам, конечно, невозможно.

2. Типы и классы

Вывод консоли показывает, что тип объекта object . Когда же мы выводим сам экземпляр класса, то обозначен он будет как Person <> . Также есть способ проверить, является ли объект экземпляром класса через оператор instanceof .

3. Класс Object

Для создания простого объекта, помимо литерального способа ( let o = <> ), использованного ранее, можно использовать функцию Object . Не следует путать тип object и класс Object . Последний выполняет роль фундаментального базового класса в генеалогии классов js. Если вывести Object в консоль, будет видно, что это функция.

4. Свойства функций и статические свойства класса

Говорят, что в js функция — это объект. Действительно, функции можно назначить произвольное свойство и вообще обращаться с ней как с обычным объектом, неважно, с маленькой она или большой буквы, является функцией-конструктором или возвращает результат вычисления.

Скажем, что функция в js имеет дуалистическую природу: с одной стороны, это функция, и её можно вызывать через круглые скобки, с другой, это объект, и у неё могут быть свои свойства, будь то поля или даже методы. Их объявление и обращение к ним выполняется через точку от имени функции. Нетрудно догадаться, что свойства функции-конструктора выступают статическими свойствами реализуемого ею класса. В этом примере мы объявляем у класса Person статическое поле count и статический метод getCount .

5. Флаги свойств объектов и некоторые статические методы функции Object

У свойств объекта есть значение и флаги. Их совокупность называется дескриптором свойства. Получить дескриптор можно через метод Object.getOwnPropertyDescriptor . Дескриптор в свою очередь представляет собой объект с полями с говорящими названиями value , writable , enumerable , configurable .

Флаг writable отвечает за возможность перезаписи свойства, а configurable — его удаления (с помощью оператора delete ).

6. Прототипы

6.1. Связь объекта с прототипом

При программировании на js для манипуляции с прототипом объекта используются предназначенные для этого функции, хотя и прямое обращение к прототипу объекта через свойство __proto__ возможно.

6.2. Указание прототипа и обращение к свойствам прототипа

Рассмотрим существенно искусственный пример прототипной связи между двумя объектами.

Здесь мы создаём объект person , который далее используется в качестве прототипа при создании объекта с тривиальным именем person1 . Используется функция Object.create (это статический метод класса Object ), которая создаёт объект с указанным прототипом (справочно, по другой схеме также создаются объекты person2-4 ).

Несмотря на то, что у объекта person1 нет заданных свойств (мы намеренно их не указывали), он может использовать свойства своего прототипа. Когда мы вызываем метод speak , то сам метод и поле name заимствуются у прототипа. Затем мы назначаем объекту person1 свойство name , и теперь у него своё собственное значение этого свойства.

Когда мы вновь вызываем метод speak , сам метод будет взят у прототипа, а свойство name — уже у объекта person1 . Затем мы назначаем ему и свой собственный метод speak . В конце мы обращаемся к person.speak() , чтобы показать, что значение свойств name и speak у прототипа остались прежними.

6.3. Цепочка прототипов

Каждый объект имеет прототип. У прототипа объекта есть свой прототип, у того — свой и т.д. Цепочка заканчивается, если прототип становится равным null .

Когда происходит обращение к свойству объекта и такого свойства не обнаруживается, то поиск свойства происходит по цепочке прототипов.

В следующем листинге мы создаём цепочку объектов person , user , account . Чтобы назначить прототип уже созданному объекту, можно использовать функцию Object.setPrototypeOf , а чтобы получить прототип объекта — getPrototypeOf . Код выводит объект account с цепочкой прототипов.

6.4. Автоматические прототипы

В предыдущих примерах мы произвольно назначали одни объекты в качестве прототипов другим объектам. А что является прототипом объекта по умолчанию? Вообще есть ли он? Да, при создании объекта ему обязательно будет назначен прототип. До того было сказано, что прототип может быть назначен неявно.

Например, мы описали функцию-конструктор Person (неважно, что внутри тела функции). Тогда, незаметно, произойдёт вот что.

A. Функции будет назначено свойство prototype типа object .

Условно это можно записать так: Person.prototype = <> ;

B. Объекту prototype будет назначено свойство constructor со значением ссылки на саму функцию. Условно это можно записать так:

C. Объекту prototype будет назначено свойство __proto__ со значением ссылки на Object.prototype .

Условно это можно записать так:

Разумеется, это условный код, призванный раскрыть немного смысла внутренней реализации.

Объект Object.prototype также имеет свойства constructor , равный функции Object , и __proto__ , равный null .

Благодаря свойству constructor свойства prototype функции (в данном случае Person.prototype.constructor ) можно создавать объекты через обращение к прототипу другого объекта. У person1 нет свойства constructor , поэтому при вызове этого метода машина начнёт поиск в person1.__proto __. Свойство __proto__ , как мы уже знаем, указывает на Person.prototype , а у последнего как раз есть свойство constructor , которое, в свою очередь, ссылается на саму функцию Person , и, таким образом, person1.constructor есть Person . Витиевато, но правда.

Заканчивает листинг создание объекта с помощью constructor’а строкового литерала (в данном случае пустой строки). Обратите внимание, что во всех случаях используется оператор new .

6.5. Расширение прототипов

Вследствие того, что при создании нового объекта ему назначается прототип равный свойству prototype функции-конструктора, все прототипы новых объектов равны друг другу и ссылаются на один и тот же объект. Отсюда следует, что если добавить в prototype новое свойство, то оно будет доступно всем объектам класса.

Здесь у каждого объекта наличествуют собственные свойства name и speak , а вот свойство talk принадлежит одному лишь прототипу. Объекты могут беспрепятственно обращаться к свойству прототипа. При этом указатель this не теряет свой контекст и ссылается на сам объект. Отсюда следует, что при проектировании классов через функции-конструкторы целесообразно размещать методы класса как свойства prototype .

6.6. Как работает оператор instanceof

Оператор имеет синтаксис obj instanceof , где слева какой-то объект, а справа какая-то функция-конструктор. Это выражение возвращает истину, если объект является экземпляром функции.

Объект считается экземпляром функции, если свойство prototype функции, т.е. .prototype , является звеном цепочки прототипов объекта. Т.е., если:

7. Прототипная связь функций

Если функция является объектом, то, стало быть, у неё может/должен быть прототип. Если мы возьмём любую функцию, то вывод в консоль выражения типа .__proto__ покажет результат. Более того, они все ссылаются на один и тот же объект.

Этот загадочный объект находится по адресу Function.prototype . Здесь Function тоже функция. То есть получается, что при описании/создании какой-нибудь функции она как будто бы создаётся через вызов new Function() . В принципе, мы даже можем создать функцию подобным образом.

Из этого, конечно, не следует, что именно так и создаются функции. Просто их свойство __proto__ указывает на Function.prototype .

Возникает резонный вопрос: если прототип всякой функции ссылается на Function.prototype , то что является прототипом самой функции Function ? Ответ: Function.__proto__ также ссылается на Function.prototype . В принципе это логично, ибо Function и сама является функцией.

А вот Function.prototype.__proto__ ссылается на Object.prototype , что тоже логично, ибо свойство prototype любой функции является объектом, а не функцией. Вспомним как работает оператор instanceof и изобразим прототипные отношения наших функций следующим образом.

8. Полезные свойства Function.prototype

Если вывести в консоль объект Function.prototype , можно заметить, что у него есть какие-то свойства. Нас интересует метод call .

Рассмотрим следующий пример. Пусть у нас есть функция-конструктор Person . Мы создали объект этого класса (person1) стандартным образом через оператор new . Теперь предположим, мы хотим превратить произвольный объект в экземпляр класса Person . Что для этого нужно сделать?

Во-первых, связать объект через прототип. В случае объекта person2 мы назначаем свойству __proto__ значение Person.prototype через метод Object.setPrototypeOf , а в случае person3 создаём его сразу с указанным прототипом.

Во-вторых, нам нужно вызвать функцию Person применительно к объекту так, чтобы объект ( person2 и person3 ) стал this . Это возможно сделать с помощью метода call Function.prototype .

Поскольку Person.__proto__ = Function.prototype , мы можем писать просто Person.call . Первым аргументом мы передаём объект, который внутри Person выступит в качестве this (иначе говоря, мы передаём контекст), остальные аргументы call это аргументы функции Person (в данном случае один аргумент name ). Выводим объекты person1-3 в консоль, и убеждаемся, что они однотипны.

Таким хитрым образом мы смогли имитировать создание экземпляра функции Person без оператора new . Подобный приём нам пригодится при реализации наследования.

9. Наследование с помощью прототипов

Рассмотрим класс TestItem , представляющий собой вопрос проверочного теста. У него есть поля question (текст вопроса), points (ответы) и answer (номер правильного ответа) и метод check , проверяющий ответ. Предположим, параметр answer — это номер выбранного ответа, метод check просто сравнивает его с правильным. В листинге мы создаём вопрос и имитируем его проверку.

Поле hint мы заполняем уже в теле конструктора производного класса.

В обязательном порядке устанавливается прототипная связь между prototype производного и базового класса. То есть свойство __proto__ свойства prototype производного класса должно ссылаться на prototype базового класса. В нашем случае MultipleChoiseTestItem.prototype.__proto__ устанавливается равным TestItem.prototype .

Также устанавливается прототипная связь между самим производным и базовым классом. В нашем случае MultipleChoiseTestItem.__proto__ устанавливается равным TestItem .

В листинге мы используем для связи метод Object.setPrototypeOf .

После этих действий можно приступать к переопределению методов базового класса и добавлению методов производного класса. В нашем примере мы полностью переопределяем один метод и частично — другой. Статический метод оставляем без изменений.

На практике некоторые приёмы обращения со свойством __proto__ считаются нерекомендованными. Например, при наследовании вместо метода Object.setPrototypeOf используется Object.create . В этом случае prototype функции-конструктора создаётся с нуля (якобы быстрее создать prototype с нуля, чем модифицировать существующий).

После такого действия объекту prototype необходимо назначить свойство constructor , равным самой функции-конструктору. В следующем примере для этого используется метод Object.defineProperty с передачей дескриптора свойства. Если мы напишем просто User.prototype.constructor = User (в принципе можно и так), то флаги свойства могут быть отличны от дефолтных значений флагов свойства constructor .

Отдельная история наблюдается с прототипной связью между функцией-конструктором производного и базового классов. Здесь мы не можем пересоздать функцию. Статические свойства базового класса можно скопировать в базовый с помощью метода Object.assign . Однако прототипной связи в данном случае установлено не будет, User.__proto__ будет ссылаться на Function.prototype , а не на Person .

Можно сказать, что статья даёт простор для выбора того или иного способа реализации базовых методов в производном классе с учётом наилучших практик и рекомендаций, которые можно найти в сети, ограничиваясь их упоминанием. Впрочем, js предоставляет и классический способ проектирования классов.

10. Class вместо function

В js поддерживается такая языковая конструкция как класс. Перепишем наш пример с их использованием.

Реализация классов с помощью конструкции class оставляет за бортом возню с прототипами. Не будем заостряться на синтаксисе, ибо он не из ряда вон выходящий, но обратим внимание на то, что прототипная архитектура функций осталась прежней. Сам класс при этом остаётся по сути и за исключением каких-то нюансов той же функцией.

Заметим, что прототип производного класса равен базовому: MultipleChoiseTestItem.__proto__ = TestItem . При использовании конструкции function такая связь устанавливалась через метод Object.setPrototypeOf . Эта связь позволяет производному классу вызывать статические методы базового класса.

11. Наследование статических свойств

Как и обычные свойства, статические также наследуются, что показано в предыдущем пункте на примере метода printTest .

В отличие от обычных методов, указатель this в теле статического метода содержит ссылку на саму функцию, а не экземпляр класса.

Обращение к статическому свойству выполняется либо через имя функции, либо через this в теле статического метода.

Операция присваивания свойств производного класса, которая создаёт одноимённое свойство, независимое от базового (например, переопределение метода), может приобрести разночтение относительно статического поля.

Рассмотрим следующий показательный пример.

При вызове метода resetCount , поскольку собственный метод у класса User не реализован (то есть не переопределён), вызывается метод базового класса. Однако this указывает на класс User , что, собственно, правильно, ибо метод вызван объектом (в данном случае это функция) User . В теле метода this.count = 0 превращается в User.count = 0, что приводит к созданию у класса User своего статического поля count . Поле count базового класса обнулено не будет.

Перепишем пример на классах и размножим поле count на два поля. К одному из них в теле статического метода будем обращаться через this.count1 , а к другому через прямое обращение к базовому классу как Person.count2 . В последнем случае как раз-таки будет обнулено собственное свойство count2 класса Person , класс User получит собственное свойство count1 (равное 0), а User.count2 вернёт значение поля базового класса.

Итого

В этой статье мы постарались раскрыть ключевые моменты наследования в js, особое внимание уделив прототипной архитектуре. Скажем само собой разумеющееся: некоторые (или все) темы, затронутые в статье, требуют отдельного изучения, и многое здесь не упомянуто.

С развитием классов в js нюансы работы с прототипами имеют меньшее распространение, но сохраняют свой шарм, присущий JavaScript в целом.

В предыдущих уроках этой главы вы немного узнали о том, как работает базовое наследование. До сих пор во всех наших примерах мы использовали открытое (публичное) наследование. То есть наш производный класс открыто наследовал базовый класс.

В этом уроке мы более подробно рассмотрим открытое ( public ) наследование, а также два других вида наследования: закрытое (частное, private ) и защищенное ( protected ). Мы также рассмотрим, как различные виды наследования взаимодействуют со спецификаторами доступа, чтобы разрешить или ограничить доступ к членам.

К этому моменту вы уже видели спецификаторы доступа public и private , которые определяют, кто может получить доступ к членам класса. Напоминаем, что к открытым ( public ) членам может получить доступ кто угодно. Доступ к закрытым ( private ) членам могут получить только функции-члены этого же класса или друзей. Это означает, что производные классы не могут напрямую обращаться к закрытым членам базового класса!

Это довольно просто, и вы уже должны были к этому привыкнуть.

Спецификатор доступа protected

При работе с наследованными классами всё становится немного сложнее.

В C++ есть третий спецификатор доступа, о котором мы еще не говорили, потому что он полезен только в контексте наследования. Спецификатор доступа protected позволяет получить доступ к члену классу, к которому принадлежит член, друзьям и производным классам. Однако защищенные члены недоступны извне класса.

В приведенном выше примере вы можете видеть, что защищенный член базового класса m_protected напрямую доступен производному классу, но не внешней функции.

Итак, когда мне следует использовать спецификатор доступа protected ?

К защищенному члену в базовом классе производные классы могут обращаться напрямую. Это означает, что если вы позже измените что-либо в этом защищенном члене (тип, значение и т.д.), вам, вероятно, придется изменить как базовый класс, так и все производные классы.

Следовательно, использование спецификатора доступа protected наиболее полезно, когда вы (или ваша команда) собираетесь наследоваться от ваших собственных классов, и количество производных классов не слишком велико. Таким образом, если вы вносите изменения в реализацию базового класса и в результате необходимы обновления производных классов, вы можете вносить эти обновления самостоятельно (и это не будет длиться вечно, поскольку количество производных классов ограничено).

Делая ваши члены закрытыми, вы лучше инкапсулируете и изолируете производные классы от изменений в базовом классе. Но в этом случае появляются затраты на создание открытого или защищенного интерфейсов для поддержки всех методов или возможностей доступа, которые необходимы сторонним и/или производным классам. Это дополнительная работа, которая, вероятно, не стоит того, если только вы не ожидаете, что кто-то другой будет наследоваться от вашего класса, или у вас есть огромное количество производных классов, и обновление их всех будет стоить дорого.

Различные виды наследования и их влияние на доступ

Во-первых, классы могут наследоваться от других классов тремя разными способами: открытый ( public ), защищенный ( protected ) и закрытый ( private ).

Для этого при выборе класса для наследования просто укажите, какой тип доступа вам нужен:

Если вы не выбрали тип наследования, C++ по умолчанию использует закрытое наследование (точно так же, как члены по умолчанию имеют закрытый доступ, если вы не укажете иное).

Это дает нам 9 комбинаций: 3 спецификатора доступа к членам ( public , private и protected ) и 3 типа наследования ( public , private и protected ).

Так в чем разница между ними? Вкратце, когда члены наследуются, спецификатор доступа для унаследованного члена может быть изменен (только в производном классе) в зависимости от типа используемого наследования. Другими словами, члены, которые были открытыми или защищенными в базовом классе, могут изменять спецификаторы доступа в производном классе.

Это может показаться немного запутанным, но всё не так уж и плохо. Мы посвятим оставшуюся часть урока подробному изучению этого.

При рассмотрении примеров помните о следующих правилах:

  • Класс всегда может получить доступ к своим собственным (ненаследуемым) членам.
  • Сторонний код получает доступ к членам класса на основе спецификаторов доступа класса, к которому он обращается.
  • Производный класс обращается к унаследованным членам на основе спецификатора доступа, унаследованного от родительского класса. Это зависит от спецификатора доступа и типа наследования.

Открытое наследование

Открытое наследование – это, безусловно, наиболее часто используемый тип наследования. Фактически, вы очень редко увидите или будете использовать другие типы наследования, поэтому ваше основное внимание должно быть сосредоточено на понимании этого раздела. К счастью, открытое наследование также легче всего для понимания. Когда вы наследуете базовый класс открыто, унаследованные открытые члены остаются открытыми, а унаследованные защищенные члены остаются защищенными. Унаследованные закрытые члены, которые были недоступны, потому что они были закрытыми в базовом классе, остаются недоступными.

Спецификатор доступа в базовом классеСпецификатор доступа при открытом наследовании
public public
protected protected
private не доступен

Ниже приведен пример, показывающий, как всё работает:

Это то же самое, что и в приведенном выше примере, где мы ввели спецификатор защищенного доступа, за исключением того, что мы также создали экземпляр производного класса, просто чтобы показать, что с открытым наследованием в базовом и производном классах всё работает одинаково.

Если у вас нет особой причины делать иначе, то вы должны использовать открытое наследование.

Лучшая практика

Используйте открытое наследование, если у вас нет особых причин делать иначе.

Защищенное наследование

Защищенное наследование – наименее распространенный метод наследования. Практически никогда не используется, за исключением очень особых случаев. При защищенном наследовании открытые и защищенные члены становятся защищенными, а закрытые члены остаются недоступными.

Поскольку эта форма наследования встречается очень редко, мы пропустим этот пример и подведем итоги в виде таблицы:

Спецификатор доступа в базовом классеСпецификатор доступа при защищенном наследовании
public protected
protected protected
private не доступен

Закрытое наследование

При закрытом наследовании все члены базового класса наследуются как закрытые. Это означает, что закрытые члены остаются закрытыми, а защищенные и открытые члены становятся закрытыми.

Обратите внимание, что это не влияет на способ доступа производного класса к членам, унаследованным от его родителя! Это влияет только на код, пытающийся получить доступ к этим членам через производный класс.

Подведем итоги в виде таблицы:

Спецификатор доступа в базовом классеСпецификатор доступа при закрытом наследовании
public private
protected private
private не доступен

Закрытое наследование может быть полезно, когда производный класс не имеет очевидной связи с базовым классом, но использует базовый класс для внутренней реализации. В таком случае мы, вероятно, не хотим, чтобы открытый интерфейс базового класса предоставлялся через объекты производного класса (как это было бы, если бы мы наследовали открыто).

На практике закрытое наследование используется редко.

Последний пример

Base может получить доступ к своим членам без ограничений. Внешний код может получить доступ только к m_public . Производные классы могут обращаться к m_public и m_protected .

D2 может получить доступ к своим членам без ограничений. D2 может получить доступ к членам Base m_public и m_protected , но не к m_private . Поскольку D2 наследуется от Base закрыто, при доступе через D2 члены m_public и m_protected теперь считаются закрытыми. Это означает, что внешний код не может получить доступ к этим переменным при использовании объекта D2 , равно как и никакие классы, производные от D2 .

D3 может получить доступ к своим собственным членам без ограничений. D3 может получить доступ к членам m_public2 и m_protected2 класса D2 , но не к m_private2 . Поскольку D3 наследуется от D2 открыто, при доступе через D3 члены m_public2 и m_protected2 сохраняют свои спецификаторы доступа. D3 не имеет доступа к m_private класса Base , который в Base уже был закрытым. У него также нет доступа к членам m_protected или m_public класса Base , которые стали закрытыми, когда D2 унаследовал их.

Резюме

Способ взаимодействия спецификаторов доступа, типов наследования и производных классов вызывает большую путаницу. Чтобы попытаться прояснить ситуацию как можно больше:

Во-первых, класс (и его друзья) всегда могут получить доступ к своим собственным ненаследуемым членам. Спецификаторы доступа влияют только на то, могут ли посторонние и производные классы получить доступ к этим членам.

Во-вторых, когда производные классы наследуют члены, эти члены могут изменять спецификаторы доступа в производном классе. Это не влияет на собственные (ненаследуемые) члены производных классов (которые имеют свои собственные спецификаторы доступа). Это влияет только на то, могут ли посторонний код и классы, производные от производного класса, получить доступ к этим унаследованным членам.

Вот таблица всех комбинаций спецификаторов доступа и типов наследования:

Спецификатор доступа в базовом классеСпецификатор доступа при открытом наследованииСпецификатор доступа при закрытом наследованииСпецификатор доступа при защищенном наследовании
public public private protected
protected protected private protected
private не доступенне доступенне доступен

В заключение, хотя в приведенных выше примерах мы показали только примеры с использованием переменных-членов, эти правила доступа верны для всех членов (например, функций-членов и типов, объявленных внутри класса).

После выхода окончательной версии спецификации ECMA Script 2015 ( ES2015 ) сообщество получило возможность двигаться в направлении ее реализации в движках JavaScript .

ES2015 предлагает для существующих функций множество новых полезных возможностей и более чистый синтаксис. Например, ключевое слово class , а также улучшенный синтаксис для JavaScript prototype .

До ES2015 реализация наследования прототипов с помощью JavaScript была запутанной. В традиционной модели классы наследуются от классов. Классы являются не более чем спецификацией или шаблоном, используемым для создания объектов.

Спецификации могут наследовать свойства других спецификаций. Используя унаследованные и собственные свойства, можно создавать новые объекты. В отличие от традиционного наследования, JavaScript не включает в себя поддержку этих спецификаций.

Что такое наследование прототипов JavaScript?

Наследование прототипов в JavaScript предполагает, что один объект наследуется от другого объекта, вместо того, чтобы одна спецификация наследовалась от другой. Даже ключевое слово нового класса является некорректным, потому что подразумевает спецификацию. Но на самом деле один объект наследуется от другого. Синтаксис в более ранних версиях JavaScript был слишком сложным, и ему трудно было следовать. Поэтому, как только разработчики принимают наследование от объекта к объекту, возникает вторая задача. Она состоит в том, чтобы улучшить синтаксис JavaScript prototype наследования — ввести классы ES2015 .

ES2015 классы в JavaScript

Данная спецификация обеспечивает более четкий синтаксис для определения структур классов, создания функций конструктора, расширения классов, вызова конструктора и функций в супер классе, а также предоставляет статические функции. Также ES2015 улучшает синтаксис для создания стиля ES5 получателя / установщика дескриптора свойств, что позволяет разработчикам использовать эти малоизвестные возможности спецификации.

Определения классов

В ES2015 был введен новый синтаксис, с использованием ключевого слова class :

Нажмите здесь, чтобы загрузить код [typeof.js]

Функция конструктора осталась той же, что определена в ES5 . В обернутом блоке ключевого слова class определяются свойства для JavaScript function prototype . Синтаксис ключевого слова new для установки нового экземпляра класса остался неизменным.

С введением ключевого слова class появляется объект функции, который используется ES5 . Рассмотрим следующий выходной результат среды Node.js REPL . Во-первых, мы определяем новый класс, а затем оператор TypeOf перечисляет типы объекта класса:

Что такое конструкторы в JavaScript?

Конструктор — это функция, которая выполняется, когда используется оператор new для создания нового экземпляра класса. В функцию конструктора могут быть переданы аргументы для инициализации свойств объекта и выполнения других задач.

В ES5 функция конструктора выглядит следующим образом:

Аналог функции конструктора с синтаксисом ES2015 выглядит следующим образом:

Хотя новый синтаксис стал более объемным, но он является более четким и позволяет проще добавлять наследуемые свойства.

Чтобы установить объект с тем же синтаксисом, код должен быть тот же:

Нажмите здесь, чтобы загрузить код [constructors.js]

Расширение классов

Настройка наследования прототипов является непростым делом, а понятие наследования прототипа является неизвестным для большинства JavaScript разработчиков. Вот несколько примеров кода с комментариями, которые поясняют процесс настройки наследования:

Нажмите здесь, чтобы загрузить код [es5_inheritance.js]

Приведенный выше код трудно разобрать, и на то, чтобы установить наследование одного объекта от другого, поддерживая при этом функции конструктора, уходит много времени. Большинство JavaScript разработчиков не могут создать этот код по памяти, а многие вообще никогда не видели ничего подобного при работе с JavaScript .

Чтобы решить эту проблему, в новом синтаксисе структуры классов в ES2015 было введено ключевое слово extends . В следующем коде продемонстрировано то же наследование, что и в первом примере кода, но с использованием синтаксиса ES2015 для JavaScript object prototype :

Нажмите здесь, чтобы загрузить код [es6_inheritance.js]

Очевидно, что второй подход более прост для понимания. Оба кода воспроизводят одинаковую структуру объекта. Таким образом, новый синтаксис улучшает код наследования, но не меняет результат.

Другой способ изучить, как это работает — рассмотреть код наследования ES5 , сгенерированный TypeScript . TypeScript – это препроцессорный язык, который оптимизирует JavaScript через строгую типизацию и транспиллинг кода ES2015 в код ES5 . Транспилинг — это процесс компиляции исходного кода одного языка программирования в исходный код другого языка.

Функция _extends в JavaScript

Для поддержки наследования классов ES2015 TypeScript транспилирует функционал ключевого слова extends в функцию с именем __extends , которая запускает код, необходимый для настройки наследования. Вот код функции __extends :

Приведенный выше код немного труден, поэтому ниже приводится его расширенная, задокументированная версия. Чтобы понять назначение каждой строки кода, прочтите комментарии, добавленные в исходный код JavaScript prototype . Функция __extends работает с любой парой родительских и дочерних объектов:

Остальное содержимое блока — это реализация функции __extends . Она использует как шаблон примеси и JavaScript prototype наследование, чтобы построить взаимосвязь наследования между родительским и дочерним объектами. Шаблон примеси копирует свойства из одного объекта в другой. Приведенный ниже код обрабатывается через функцию __extends :

Следующие две строки кода сбивают с толку многих разработчиков:

Можно подумать, что вместо этого код должен быть составлен следующим образом:

Разработчики ошибочно полагают, что дочерний объект теперь будет наследоваться от объекта прототипа родительской функции конструктора. Но на самом деле объекты, созданные с помощью родительской функции конструктора, а также объекты, созданные с помощью дочерней функции конструктора, наследуются от точно такого же JavaScript object prototype . Это нежелательно, так как свойство прототипа дочерней функции конструктора не может быть изменено без одновременного изменения свойства прототипа родительской функции конструктора. Поэтому все изменения, внесенные в дочерний объект, будут также применены к родителю. Это некорректное наследование:

Функция _extends в JavaScript

При создании нового экземпляра объекта с помощью оператора new и родительской или дочерней функции конструктора, полученные объекты будут наследоваться от того же объекта-прототипа ( РРО ). Установленные родительские и дочерние объекты являются объектами одного уровня с РРО в качестве родителя. Дочерний объект не наследуется от родителя.

Таким образом, целью данного кода является установить следующую структуру наследования:

Функция _extends в JavaScript - 2

С помощью этой новой структуры новые дочерние объекты наследуются от CPO ( объект прототипа потомков ), который наследуется от РРО . Новые свойства могут быть добавлены в CPO , который не влияет на РРО . Новые родительские объекты наследуются от РРО , и не зависят от изменений в СРО . Изменения в РРО будут унаследованы объектом, созданным как с помощью родительской, так и с помощью дочерней функций конструктора. С помощью этой новой структуры дочерние объекты наследуются от родителя.

И в конце закрывающая фигурная скобка относится к изначальному блоку if :

Нажмите здесь, чтобы загрузить код [ts_extends.js]

Синтаксис ES2015 для расширения классов гораздо более прост для понимания JavaScript prototype . Он содержит два новых ключевых слов: extends и super . Ключевое слово extends устанавливает отношения наследования прототипа между родительскими и дочерними классами. Ключевое слово super вызывает конструктор для класса родителя ( он же суперкласс ). Вызов функции super требуется, даже если родительский объект не содержит конфигурацию.

Вызов супер конструктора это то, что создало этот новый объект, который будет использоваться в дочернем классе ( он же подкласс ):

Нажмите здесь, чтобы загрузить код [extends.js]

Объекты JavaScript позволяют только одиночное наследование, и не имеют встроенной поддержки интерфейсов ( TypeScript предоставляет интерфейсы ).

Классы ES2015 значительно улучшают синтаксис для определения свойств наследования объектов, свойств получателя / установщика. Хотя классы ES2015 не меняют характер наследования прототипа, они делает JavaScript prototype более доступным для JavaScript разработчиков.

Дайте знать, что вы думаете по данной теме материала в комментариях. Мы очень благодарим вас за ваши комментарии, подписки, лайки, отклики, дизлайки!

Читайте также: