fix links

This commit is contained in:
Alexander Zhirov 2023-02-27 01:07:49 +03:00
parent 0f288152a4
commit 1d0a2964cf
1 changed files with 40 additions and 38 deletions

View File

@ -722,7 +722,7 @@ unittest
Если бы занявший место экземпляра класса `Contact` экземпляр класса `Friend` вел себя *в точности* так же, как и экземпляр ожидаемого класса, отпали бы все (или почти все) причины использовать класс `Friend`. Одно из основных средств, предоставляемых объектной технологией, возможность классам-наследникам переопределять функции классов-предков и таким образом модульно настраивать поведение сущностей среды. Как можно догадаться, переопределение задается с помощью ключевого слова `override` (класс `Friend` переопределяет метод `bgColor`), которое обозначает, что вызов `c.bgColor()` (где вместо c ожидается объект типа `Contact`, но на самом деле используется объект типа `Friend`) всегда инициирует вызов версии метода, предлагаемой классом `Friend`. Друзья всегда остаются друзьями, даже если компилятор думает, что это обыкновенные контакты. Если бы занявший место экземпляра класса `Contact` экземпляр класса `Friend` вел себя *в точности* так же, как и экземпляр ожидаемого класса, отпали бы все (или почти все) причины использовать класс `Friend`. Одно из основных средств, предоставляемых объектной технологией, возможность классам-наследникам переопределять функции классов-предков и таким образом модульно настраивать поведение сущностей среды. Как можно догадаться, переопределение задается с помощью ключевого слова `override` (класс `Friend` переопределяет метод `bgColor`), которое обозначает, что вызов `c.bgColor()` (где вместо c ожидается объект типа `Contact`, но на самом деле используется объект типа `Friend`) всегда инициирует вызов версии метода, предлагаемой классом `Friend`. Друзья всегда остаются друзьями, даже если компилятор думает, что это обыкновенные контакты.
[В начало ⮍]() [Наверх ⮍](#6-классы-объектно-ориентированный-стиль) [В начало ⮍](#6-4-методы-и-наследование) [Наверх ⮍](#6-классы-объектно-ориентированный-стиль)
### 6.4.1. Терминологический «шведский стол» ### 6.4.1. Терминологический «шведский стол»
@ -736,7 +736,7 @@ unittest
Странно, но несмотря на то что классы это типы, подтип это не то же самое, что и подкласс (а супертип не то же самое, что и суперкласс). Подтип это более широкое понятие: тип `S` является подтипом типа `T`, если значение типа `S` можно без какого-либо риска употреблять во всех контекстах, где ожидается значение типа `T`. Обратите внимание: в этом определении ничего не говорится о наследовании. И на самом деле, наследование это лишь один из способов реализовать порождение подтипов; в общем случае есть и другие средства (в том числе в D). Отношение между порождением подтипов и наследованием можно охарактеризовать так: подтипами класса `C` являются потомки класса `C` плюс сам класс `C`. Подтип `C`, отличный от `C`, это *собственный подтип* (*proper subtype*). Странно, но несмотря на то что классы это типы, подтип это не то же самое, что и подкласс (а супертип не то же самое, что и суперкласс). Подтип это более широкое понятие: тип `S` является подтипом типа `T`, если значение типа `S` можно без какого-либо риска употреблять во всех контекстах, где ожидается значение типа `T`. Обратите внимание: в этом определении ничего не говорится о наследовании. И на самом деле, наследование это лишь один из способов реализовать порождение подтипов; в общем случае есть и другие средства (в том числе в D). Отношение между порождением подтипов и наследованием можно охарактеризовать так: подтипами класса `C` являются потомки класса `C` плюс сам класс `C`. Подтип `C`, отличный от `C`, это *собственный подтип* (*proper subtype*).
[В начало ⮍]() [Наверх ⮍](#6-классы-объектно-ориентированный-стиль) [В начало ⮍](#6-4-1-терминологический-шведский-стол) [Наверх ⮍](#6-классы-объектно-ориентированный-стиль)
### 6.4.2. Наследование это порождение подтипа. Статический и динамический типы ### 6.4.2. Наследование это порождение подтипа. Статический и динамический типы
@ -790,7 +790,7 @@ unittest
} }
``` ```
[В начало ⮍]() [Наверх ⮍](#6-классы-объектно-ориентированный-стиль) [В начало ⮍](#6-4-2-наследование-это-порождение-подтипа-статический-и-динамический-типы) [Наверх ⮍](#6-классы-объектно-ориентированный-стиль)
### 6.4.3. Переопределение только по желанию ### 6.4.3. Переопределение только по желанию
@ -802,7 +802,7 @@ unittest
Таким образом, требование писать `override` позволяет модифицировать классы-предки без риска неожиданно навредить классам-потомкам. Таким образом, требование писать `override` позволяет модифицировать классы-предки без риска неожиданно навредить классам-потомкам.
[В начало ⮍]() [Наверх ⮍](#6-классы-объектно-ориентированный-стиль) [В начало ⮍](#6-4-3-переопределение-только-по-желанию) [Наверх ⮍](#6-классы-объектно-ориентированный-стиль)
### 6.4.4. Вызов переопределенных методов ### 6.4.4. Вызов переопределенных методов
@ -851,7 +851,7 @@ class Derived : Base
Несмотря на то что деструкторы (см. раздел 6.3.4) это всего лишь методы, у обработки вызова деструктора есть особенности. Нельзя явно вызвать деструктор родителя, однако при вызове деструктора текущего класса (или во время цикла сбора мусора, или в результате вызова `clear(obj)`) библиотека D поддержки во время выполнения всегда вызывает все деструкторы вверх по иерархии. Несмотря на то что деструкторы (см. раздел 6.3.4) это всего лишь методы, у обработки вызова деструктора есть особенности. Нельзя явно вызвать деструктор родителя, однако при вызове деструктора текущего класса (или во время цикла сбора мусора, или в результате вызова `clear(obj)`) библиотека D поддержки во время выполнения всегда вызывает все деструкторы вверх по иерархии.
[В начало ⮍]() [Наверх ⮍](#6-классы-объектно-ориентированный-стиль) [В начало ⮍](#6-4-4-вызов-переопределенных-методов) [Наверх ⮍](#6-классы-объектно-ориентированный-стиль)
### 6.4.5. Ковариантные возвращаемые типы ### 6.4.5. Ковариантные возвращаемые типы
@ -902,7 +902,7 @@ void workWith(TextWidget tw)
Чтобы максимизировать количество доступной статической информации о типах, D вводит средство, известное как *ковариантные возвращаемые типы*. Звучит довольно громко, но смысл ковариантности возвращаемых типов довольно прост: если родительский класс возвращает некоторый тип `C`, то переопределенной функции разрешается возвращать не только `C`, но и любого потомка `C`. Благодаря этому средству можно позволить методу `TextWidget.duplicate` возвращать `TextWidget`. Не менее важно, что теперь вы можете прибавить себе веса в дискуссии, вставив при случае фразу «ковариантные возвращаемые типы». (Шутка. Если серьезно, даже не пытайтесь.) Чтобы максимизировать количество доступной статической информации о типах, D вводит средство, известное как *ковариантные возвращаемые типы*. Звучит довольно громко, но смысл ковариантности возвращаемых типов довольно прост: если родительский класс возвращает некоторый тип `C`, то переопределенной функции разрешается возвращать не только `C`, но и любого потомка `C`. Благодаря этому средству можно позволить методу `TextWidget.duplicate` возвращать `TextWidget`. Не менее важно, что теперь вы можете прибавить себе веса в дискуссии, вставив при случае фразу «ковариантные возвращаемые типы». (Шутка. Если серьезно, даже не пытайтесь.)
[В начало ⮍]() [Наверх ⮍](#6-классы-объектно-ориентированный-стиль) [В начало ⮍](#6-4-5-ковариантные-возвращаемые-типы) [Наверх ⮍](#6-классы-объектно-ориентированный-стиль)
## 6.5. Инкапсуляция на уровне классов с помощью статических членов ## 6.5. Инкапсуляция на уровне классов с помощью статических членов
@ -939,7 +939,7 @@ unittest
auto c = (new Widget).defaultBgColor; auto c = (new Widget).defaultBgColor;
``` ```
[В начало ⮍]() [Наверх ⮍](#6-классы-объектно-ориентированный-стиль) [В начало ⮍](#6-5-инкапсуляция-на-уровне-классов-с-помощью-статических-членов) [Наверх ⮍](#6-классы-объектно-ориентированный-стиль)
## 6.6. Сдерживание расширяемости с помощью финальных методов ## 6.6. Сдерживание расширяемости с помощью финальных методов
@ -980,7 +980,7 @@ final void updatePrice(double last)
Если вы работали c языками Java или C#, то немедленно узнаете в ключевом слове `final` старого знакомого, поскольку в D оно обладает той же семантикой, что и в этих языках. Если сравнить положение дел с C++, то можно обнаружить любопытную смену настроек по умолчанию: в C++ методы являются финальными по умолчанию (и не нужно специально что-то указывать, чтобы запретить наследование) и переопределяемыми, если явно пометить их ключевым словом `virtual`. Подчеркнем еще раз: по крайней мере в этом случае было решено предпочесть умолчания, ориентированные на гибкость. Скорее всего, вы будете использовать финальные методы в основном для реализации структурных решений и только иногда чтобы избавиться от нескольких дополнительных циклов процессора. Если вы работали c языками Java или C#, то немедленно узнаете в ключевом слове `final` старого знакомого, поскольку в D оно обладает той же семантикой, что и в этих языках. Если сравнить положение дел с C++, то можно обнаружить любопытную смену настроек по умолчанию: в C++ методы являются финальными по умолчанию (и не нужно специально что-то указывать, чтобы запретить наследование) и переопределяемыми, если явно пометить их ключевым словом `virtual`. Подчеркнем еще раз: по крайней мере в этом случае было решено предпочесть умолчания, ориентированные на гибкость. Скорее всего, вы будете использовать финальные методы в основном для реализации структурных решений и только иногда чтобы избавиться от нескольких дополнительных циклов процессора.
[В начало ⮍]() [Наверх ⮍](#6-классы-объектно-ориентированный-стиль) [В начало ⮍](#6-6-сдерживание-расширяемости-с-помощью-финальных-методов) [Наверх ⮍](#6-классы-объектно-ориентированный-стиль)
### 6.6.1. Финальные классы ### 6.6.1. Финальные классы
@ -996,7 +996,7 @@ class PostUltimateWidget : UltimateWidget { ... } // Ошибка! Нельзя
Любопытный побочный эффект финальных классов твердые гарантии реализации. Клиентский код, использующий финальный класс, может быть уверен, что методы этого класса обладают известными реализациями с гарантированным действием, которое не может быть модифицировано никаким подклассом. Любопытный побочный эффект финальных классов твердые гарантии реализации. Клиентский код, использующий финальный класс, может быть уверен, что методы этого класса обладают известными реализациями с гарантированным действием, которое не может быть модифицировано никаким подклассом.
[В начало ⮍]() [Наверх ⮍](#6-классы-объектно-ориентированный-стиль) [В начало ⮍](#6-6-1-финальные-классы) [Наверх ⮍](#6-классы-объектно-ориентированный-стиль)
## 6.7. Инкапсуляция ## 6.7. Инкапсуляция
@ -1010,7 +1010,7 @@ class PostUltimateWidget : UltimateWidget { ... } // Ошибка! Нельзя
Вернемся к инкапсуляции в контексте языка D. Любые тип, данные, функцию или метод можно определить с одним из следующих пяти спецификаторов. Начнем с самого закрытого спецификатора и постепенно дойдем до полной гласности. Вернемся к инкапсуляции в контексте языка D. Любые тип, данные, функцию или метод можно определить с одним из следующих пяти спецификаторов. Начнем с самого закрытого спецификатора и постепенно дойдем до полной гласности.
[В начало ⮍]() [Наверх ⮍](#6-классы-объектно-ориентированный-стиль) [В начало ⮍](#6-7-инкапсуляция) [Наверх ⮍](#6-классы-объектно-ориентированный-стиль)
### 6.7.1. private ### 6.7.1. private
@ -1018,13 +1018,13 @@ class PostUltimateWidget : UltimateWidget { ... } // Ошибка! Нельзя
В других языках такой синтаксис имеет другую семантику: обычно доступ к закрытым идентификаторам ограничивают лишь до текущего класса. Тем не менее то, что спецификатор `private` ограничивает доступ до уровня модуля, прекрасно согласуется с общим подходом D к защите: все защищаемые единицы соответствуют защищаемым единицам операционной системы (файлу и каталогу). Преимущество защиты на уровне файла в том, что она способствует объединению маленьких тесно взаимосвязанных сущностей, обладающих определенными обязанностями. Если требуется защита на уровне класса, просто выделите ему собственный файл. В других языках такой синтаксис имеет другую семантику: обычно доступ к закрытым идентификаторам ограничивают лишь до текущего класса. Тем не менее то, что спецификатор `private` ограничивает доступ до уровня модуля, прекрасно согласуется с общим подходом D к защите: все защищаемые единицы соответствуют защищаемым единицам операционной системы (файлу и каталогу). Преимущество защиты на уровне файла в том, что она способствует объединению маленьких тесно взаимосвязанных сущностей, обладающих определенными обязанностями. Если требуется защита на уровне класса, просто выделите ему собственный файл.
[В начало ⮍]() [Наверх ⮍](#6-классы-объектно-ориентированный-стиль) [В начало ⮍](#6-7-1-private) [Наверх ⮍](#6-классы-объектно-ориентированный-стиль)
### 6.7.2. package ### 6.7.2. package
Спецификатор доступа `package` можно указывать на уровне класса, за пределами классов (на уровне модуля) и внутри структуры (см. главу 7). Во всех контекстах ключевое слово `package` действует одинаково: доступ к идентификатору, определенному с этим ключевым словом, предоставляется всем файлам в том же каталоге, где находится и текущий модуль. Родительский каталог и подкаталоги каталога текущего модуля не обладают никакими особыми привилегиями. Спецификатор доступа `package` можно указывать на уровне класса, за пределами классов (на уровне модуля) и внутри структуры (см. главу 7). Во всех контекстах ключевое слово `package` действует одинаково: доступ к идентификатору, определенному с этим ключевым словом, предоставляется всем файлам в том же каталоге, где находится и текущий модуль. Родительский каталог и подкаталоги каталога текущего модуля не обладают никакими особыми привилегиями.
[В начало ⮍]() [Наверх ⮍](#6-классы-объектно-ориентированный-стиль) [В начало ⮍](#6-7-2-package) [Наверх ⮍](#6-классы-объектно-ориентированный-стиль)
### 6.7.3. protected ### 6.7.3. protected
@ -1044,7 +1044,7 @@ class C
И снова отметим, что право доступа, предоставляемое спецификатором доступа `protected`, транзитивно: оно переходит не только собственно к дочерним классам, но и ко всем поколениям потомков, наследующих от класса, определенного с ключевым словом `protected`. Это делает спецификатор доступа `protected` довольно щедрым в плане предоставления доступа. И снова отметим, что право доступа, предоставляемое спецификатором доступа `protected`, транзитивно: оно переходит не только собственно к дочерним классам, но и ко всем поколениям потомков, наследующих от класса, определенного с ключевым словом `protected`. Это делает спецификатор доступа `protected` довольно щедрым в плане предоставления доступа.
[В начало ⮍]() [Наверх ⮍](#6-классы-объектно-ориентированный-стиль) [В начало ⮍](#6-7-3-protected) [Наверх ⮍](#6-классы-объектно-ориентированный-стиль)
### 6.7.4. public ### 6.7.4. public
@ -1052,13 +1052,13 @@ class C
В языке D `public` это также уровень доступа, который присваивается всем объектам по умолчанию. Поскольку порядок объявлений на компиляцию не влияет, хорошим тоном будет расположить видимые интерфейсы модуля или класса в начале файла, а затем ограничить доступ, применив (например) спецификатор доступа `private`, после чего можно разместить другие определения. Если придерживаться такой стратегии, клиенту будет достаточно посмотреть в начало файла или класса, чтобы узнать все о его доступных сущностях. В языке D `public` это также уровень доступа, который присваивается всем объектам по умолчанию. Поскольку порядок объявлений на компиляцию не влияет, хорошим тоном будет расположить видимые интерфейсы модуля или класса в начале файла, а затем ограничить доступ, применив (например) спецификатор доступа `private`, после чего можно разместить другие определения. Если придерживаться такой стратегии, клиенту будет достаточно посмотреть в начало файла или класса, чтобы узнать все о его доступных сущностях.
[В начало ⮍]() [Наверх ⮍](#6-классы-объектно-ориентированный-стиль) [В начало ⮍](#6-7-4-public) [Наверх ⮍](#6-классы-объектно-ориентированный-стиль)
### 6.7.5. export ### 6.7.5. export
Казалось бы, спецификатор доступа `public` наименее закрытый из уровней доступа, самый щедрый из них. Тем не менее D определяет уровень доступа, разрешающий еще больше: `export`. Идентификатор, определенный с ключевым словом `export`, становится доступным даже *вне* программы, в которой он был определен. Это случай разделяемых библиотек, выставляющих всему миру напоказ свои интерфейсы. Компилятор выполняет зависящие от системы шаги, необходимые для экспорта идентификатора, часто включая и особые соглашения об именовании символов. Пока что в D не определена сложная инфраструктура динамической загрузки, так что спецификатор доступа `export` выполняет роль заглушки в ожидании более обширной поддержки. Казалось бы, спецификатор доступа `public` наименее закрытый из уровней доступа, самый щедрый из них. Тем не менее D определяет уровень доступа, разрешающий еще больше: `export`. Идентификатор, определенный с ключевым словом `export`, становится доступным даже *вне* программы, в которой он был определен. Это случай разделяемых библиотек, выставляющих всему миру напоказ свои интерфейсы. Компилятор выполняет зависящие от системы шаги, необходимые для экспорта идентификатора, часто включая и особые соглашения об именовании символов. Пока что в D не определена сложная инфраструктура динамической загрузки, так что спецификатор доступа `export` выполняет роль заглушки в ожидании более обширной поддержки.
[В начало ⮍]() [Наверх ⮍](#6-классы-объектно-ориентированный-стиль) [В начало ⮍](#6-7-5-export) [Наверх ⮍](#6-классы-объектно-ориентированный-стиль)
### 6.7.6. Сколько инкапсуляции? ### 6.7.6. Сколько инкапсуляции?
@ -1084,7 +1084,7 @@ class C
***Рис. 6.3.*** *Приблизительные оценки количества строк кода, которые может затронуть изменение идентификатора с соответствующим спецификатором доступа. Вертикальная ось логарифмическая, так что каждый шаг ослабления инкапсуляции на порядок ухудшает положение дел. Стрелки вверх означают, что количество кода, затронутого при уровне защиты `protected`, `public` и `export`, неподвластно программисту, изменившему идентификатор* ***Рис. 6.3.*** *Приблизительные оценки количества строк кода, которые может затронуть изменение идентификатора с соответствующим спецификатором доступа. Вертикальная ось логарифмическая, так что каждый шаг ослабления инкапсуляции на порядок ухудшает положение дел. Стрелки вверх означают, что количество кода, затронутого при уровне защиты `protected`, `public` и `export`, неподвластно программисту, изменившему идентификатор*
[В начало ⮍]() [Наверх ⮍](#6-классы-объектно-ориентированный-стиль) [В начало ⮍](#6-7-6-сколько-инкапсуляции) [Наверх ⮍](#6-классы-объектно-ориентированный-стиль)
## 6.8. Основа безраздельной власти ## 6.8. Основа безраздельной власти
@ -1123,7 +1123,7 @@ class Object
Познакомимся поближе с семантикой каждого из этих идентификаторов. Познакомимся поближе с семантикой каждого из этих идентификаторов.
[В начало ⮍]() [Наверх ⮍](#6-классы-объектно-ориентированный-стиль) [В начало ⮍](#6-8-основа-безраздельной-власти) [Наверх ⮍](#6-классы-объектно-ориентированный-стиль)
### 6.8.1. string toString() ### 6.8.1. string toString()
@ -1140,13 +1140,13 @@ unittest
Обратите внимание: вместе с именем класса возвращено и имя модуля, в котором класс был определен. По умолчанию модуль получает имя файла, в котором он расположен, но это умолчание можно изменить с помощью объявления с ключевым словом `module` (см. раздел 11.8). Обратите внимание: вместе с именем класса возвращено и имя модуля, в котором класс был определен. По умолчанию модуль получает имя файла, в котором он расположен, но это умолчание можно изменить с помощью объявления с ключевым словом `module` (см. раздел 11.8).
[В начало ⮍]() [Наверх ⮍](#6-классы-объектно-ориентированный-стиль) [В начало ⮍](#6-8-1-string-tostring) [Наверх ⮍](#6-классы-объектно-ориентированный-стиль)
### 6.8.2. size_t toHash() ### 6.8.2. size_t toHash()
Этот метод возвращает хеш объекта в виде целого числа без знака (размером в 32 разряда на 32-разрядной машине и в 64 разряда на 64-разрядной). По умолчанию хеш-сумма вычисляется на основе поразрядного представления объекта. Хеш является сжатым, но неточным представлением объекта. Одно из важных требований к функции, вычисляющей хеш-сумму, *постоянство*: если метод `toHash` дважды вызывается с одной и той же ссылкой и между двумя вызовами объект, к которому привязана эта ссылка, не был изменен, то значения, возвращенные при первом и втором вызове, должны совпадать. Кроме того, хеш-коды двух одинаковых объектов тоже должны быть одинаковыми. А хеш-коды двух различных («неравных») объектов вряд ли будут равны. В следующем разделе подробно определено понятие равенства объектов. Этот метод возвращает хеш объекта в виде целого числа без знака (размером в 32 разряда на 32-разрядной машине и в 64 разряда на 64-разрядной). По умолчанию хеш-сумма вычисляется на основе поразрядного представления объекта. Хеш является сжатым, но неточным представлением объекта. Одно из важных требований к функции, вычисляющей хеш-сумму, *постоянство*: если метод `toHash` дважды вызывается с одной и той же ссылкой и между двумя вызовами объект, к которому привязана эта ссылка, не был изменен, то значения, возвращенные при первом и втором вызове, должны совпадать. Кроме того, хеш-коды двух одинаковых объектов тоже должны быть одинаковыми. А хеш-коды двух различных («неравных») объектов вряд ли будут равны. В следующем разделе подробно определено понятие равенства объектов.
[В начало ⮍]() [Наверх ⮍](#6-классы-объектно-ориентированный-стиль) [В начало ⮍](#6-8-2-size_t-tohash) [Наверх ⮍](#6-классы-объектно-ориентированный-стиль)
### 6.8.3. bool opEquals(Object rhs) ### 6.8.3. bool opEquals(Object rhs)
@ -1272,7 +1272,7 @@ override bool opEquals(Object rhs)
Итак, в общем случае сравнение должно выполняться дважды: каждый из операндов сравнения должен подтвердить свое равенство другому операнду. Но есть и хорошие новости: свободная функция `object.opEquals(Object, Object)` избегает процедуры «рукопожатия» при любом совпадении типов участвующих в сравнении объектов, а в других случаях иногда вообще не инициирует дополнительные вызовы. Итак, в общем случае сравнение должно выполняться дважды: каждый из операндов сравнения должен подтвердить свое равенство другому операнду. Но есть и хорошие новости: свободная функция `object.opEquals(Object, Object)` избегает процедуры «рукопожатия» при любом совпадении типов участвующих в сравнении объектов, а в других случаях иногда вообще не инициирует дополнительные вызовы.
[В начало ⮍]() [Наверх ⮍](#6-классы-объектно-ориентированный-стиль) [В начало ⮍](#6-8-3-bool-opequalsobject-rhs) [Наверх ⮍](#6-классы-объектно-ориентированный-стиль)
### 6.8.4. int opCmp(Object rhs) ### 6.8.4. int opCmp(Object rhs)
@ -1324,7 +1324,7 @@ if (x == y) assert(x <= y && y <= x);
Отношение к `opEquals` определено не слишком строго: вполне вероятна ситуация, когда для двух классов неравенства `x <= y` и `y <= x` истинны одновременно здравый смысл продиктовал бы, что значения `x` и `y` равны. Тем не менее вовсе не обязательно, что `x == y`. Простым примером может послужить класс, определяющий равенство в терминах регистрочувствительных строк, а упорядочивание в терминах строк, нечувствительных к регистру. Отношение к `opEquals` определено не слишком строго: вполне вероятна ситуация, когда для двух классов неравенства `x <= y` и `y <= x` истинны одновременно здравый смысл продиктовал бы, что значения `x` и `y` равны. Тем не менее вовсе не обязательно, что `x == y`. Простым примером может послужить класс, определяющий равенство в терминах регистрочувствительных строк, а упорядочивание в терминах строк, нечувствительных к регистру.
[В начало ⮍]() [Наверх ⮍](#6-классы-объектно-ориентированный-стиль) [В начало ⮍](#6-8-4-int-opcmpobject-rhs) [Наверх ⮍](#6-классы-объектно-ориентированный-стиль)
### 6.8.5. static Object factory (string className) ### 6.8.5. static Object factory (string className)
@ -1385,7 +1385,7 @@ void widgetize(string widgetClass)
Теперь функция `widgetize` освобождена от ответственности за выбор конкретного класса из цепочки наследования, которую образует класс `Widget`. Есть и другие пути достижения гибкости конструирования объектов, расширяющие пространство проектирования в разных направлениях. Чтобы познакомиться с всеобъемлющим описанием этой проблемы, внимательно прочтите статью с драматическим названием «Javas new considered harmful» (Оператор new из Java опасен). Теперь функция `widgetize` освобождена от ответственности за выбор конкретного класса из цепочки наследования, которую образует класс `Widget`. Есть и другие пути достижения гибкости конструирования объектов, расширяющие пространство проектирования в разных направлениях. Чтобы познакомиться с всеобъемлющим описанием этой проблемы, внимательно прочтите статью с драматическим названием «Javas new considered harmful» (Оператор new из Java опасен).
[В начало ⮍]() [Наверх ⮍](#6-классы-объектно-ориентированный-стиль) [В начало ⮍](#6-8-5-static-object-factory-string-classname) [Наверх ⮍](#6-классы-объектно-ориентированный-стиль)
## 6.9. Интерфейсы ## 6.9. Интерфейсы
@ -1452,7 +1452,7 @@ aDayInLife(new CardboardBox, "играть");
Любая реализация интерфейса является подтипом этого интерфейса, так что она автоматически конвертируется в него. Мы воспользовались этим механизмом, просто передав объект `CardboardBox` вместо интерфейса `Transmogrifier`, ожидаемого функцией `aDayInLife`. Любая реализация интерфейса является подтипом этого интерфейса, так что она автоматически конвертируется в него. Мы воспользовались этим механизмом, просто передав объект `CardboardBox` вместо интерфейса `Transmogrifier`, ожидаемого функцией `aDayInLife`.
[В начало ⮍]() [Наверх ⮍](#6-классы-объектно-ориентированный-стиль) [В начало ⮍](#6-9-интерфейсы) [Наверх ⮍](#6-классы-объектно-ориентированный-стиль)
### 6.9.1. Идея невиртуальных интерфейсов (NVI) ### 6.9.1. Идея невиртуальных интерфейсов (NVI)
@ -1545,7 +1545,7 @@ class Good : Transmogrifier
Этот код корректен, потому конфликт невозможен: вызов будет выглядеть либо как `obj.thereAndBack()` и направится к методу `Transmogrify.thereAndBack`, либо как `obj.thereAndBack(n)` и направится к методу `Good.thereAndBack`. В частности, реализация `Good.thereAndBack` не обязана относить свой внутренний вызов к реализации одноименной функции интерфейса. Этот код корректен, потому конфликт невозможен: вызов будет выглядеть либо как `obj.thereAndBack()` и направится к методу `Transmogrify.thereAndBack`, либо как `obj.thereAndBack(n)` и направится к методу `Good.thereAndBack`. В частности, реализация `Good.thereAndBack` не обязана относить свой внутренний вызов к реализации одноименной функции интерфейса.
[В начало ⮍]() [Наверх ⮍](#6-классы-объектно-ориентированный-стиль) [В начало ⮍](#6-9-1-идея-невиртуальных-интерфейсов-nvi) [Наверх ⮍](#6-классы-объектно-ориентированный-стиль)
### 6.9.2. Защищенные примитивы ### 6.9.2. Защищенные примитивы
@ -1599,7 +1599,7 @@ class BrokenInTwoWays : Transmogrifier
Технически осуществимо как ослабление, так и ужесточение требований интерфейса к реализации, но эти возможности вряд ли можно использовать во благо. Интерфейс выражает цель, и должно быть достаточно ознакомиться лишь с определением интерфейса, чтобы полноценно использовать его независимо от доступности статического типа реализуемого класса. Технически осуществимо как ослабление, так и ужесточение требований интерфейса к реализации, но эти возможности вряд ли можно использовать во благо. Интерфейс выражает цель, и должно быть достаточно ознакомиться лишь с определением интерфейса, чтобы полноценно использовать его независимо от доступности статического типа реализуемого класса.
[В начало ⮍]() [Наверх ⮍](#6-классы-объектно-ориентированный-стиль) [В начало ⮍](#6-9-2-защищенные-примитивы) [Наверх ⮍](#6-классы-объектно-ориентированный-стиль)
### 6.9.3. Избирательная реализация ### 6.9.3. Избирательная реализация
@ -1628,7 +1628,7 @@ class TimedApp : Timer, Application
Чтобы получить доступ к этим методам объекта `app` типа `TimedApp`, вам придется написать `app.Timer.run()` и `app.Application.run()` для версий интерфейсов `Timer` и `Application` соответственно. Класс `TimedApp` может определить собственные функции, которые будут делегировать вызов этим методам. Главное, чтобы такие функции не пытались подменить `run()`. Чтобы получить доступ к этим методам объекта `app` типа `TimedApp`, вам придется написать `app.Timer.run()` и `app.Application.run()` для версий интерфейсов `Timer` и `Application` соответственно. Класс `TimedApp` может определить собственные функции, которые будут делегировать вызов этим методам. Главное, чтобы такие функции не пытались подменить `run()`.
[В начало ⮍]() [Наверх ⮍](#6-классы-объектно-ориентированный-стиль) [В начало ⮍](#6-9-3-избирательная-реализация) [Наверх ⮍](#6-классы-объектно-ориентированный-стиль)
## 6.10. Абстрактные классы ## 6.10. Абстрактные классы
@ -1792,7 +1792,7 @@ unittest
} }
``` ```
[В начало ⮍]() [Наверх ⮍](#6-классы-объектно-ориентированный-стиль) [В начало ⮍](#6-10-абстрактные-классы) [Наверх ⮍](#6-классы-объектно-ориентированный-стиль)
## 6.11. Вложенные классы ## 6.11. Вложенные классы
@ -1882,7 +1882,7 @@ class Outer
} }
``` ```
[В начало ⮍]() [Наверх ⮍](#6-классы-объектно-ориентированный-стиль) [В начало ⮍](#6-11-вложенные-классы) [Наверх ⮍](#6-классы-объектно-ориентированный-стиль)
### 6.11.1. Вложенные классы в функциях ### 6.11.1. Вложенные классы в функциях
@ -1947,7 +1947,7 @@ Calculation truncate(double limit)
Но благодаря небольшой поддержке компилятора рассмотренный пример работает нормально. Когда бы компилятору ни приходилось компилировать функцию, он всегда просматривает ее в поисках нелокальных утечек (non-local escapes) ситуаций, когда параметр или локальная переменная остается в использовании уже после того, как функция вернула результат[^12]. Если обнаружена такая утечка, компилятор изменяет способ выделения памяти под локальное состояние (параметры плюс локальные переменные): вместо выделения памяти в стеке в этом случае происходит динамическое выделение памяти. Но благодаря небольшой поддержке компилятора рассмотренный пример работает нормально. Когда бы компилятору ни приходилось компилировать функцию, он всегда просматривает ее в поисках нелокальных утечек (non-local escapes) ситуаций, когда параметр или локальная переменная остается в использовании уже после того, как функция вернула результат[^12]. Если обнаружена такая утечка, компилятор изменяет способ выделения памяти под локальное состояние (параметры плюс локальные переменные): вместо выделения памяти в стеке в этом случае происходит динамическое выделение памяти.
[В начало ⮍]() [Наверх ⮍](#6-классы-объектно-ориентированный-стиль) [В начало ⮍](#6-11-1-вложенные-классы-в-функциях) [Наверх ⮍](#6-классы-объектно-ориентированный-стиль)
### 6.11.2. Статические вложенные классы ### 6.11.2. Статические вложенные классы
@ -1976,7 +1976,7 @@ unittest
Будучи обычным классом, статический внутренний класс не имеет доступа к внешнему объекту просто за отсутствием таковых. Зато благодаря контексту определения статический внутренний класс обладает доступом к статическим внутренним элементам внешнего класса. Будучи обычным классом, статический внутренний класс не имеет доступа к внешнему объекту просто за отсутствием таковых. Зато благодаря контексту определения статический внутренний класс обладает доступом к статическим внутренним элементам внешнего класса.
[В начало ⮍]() [Наверх ⮍](#6-классы-объектно-ориентированный-стиль) [В начало ⮍](#6-11-2-статические-вложенные-классы) [Наверх ⮍](#6-классы-объектно-ориентированный-стиль)
### 6.11.3. Анонимные классы ### 6.11.3. Анонимные классы
@ -2002,7 +2002,7 @@ Widget makeWidget(uint w, uint h)
Это средство языка работает почти так же, как анонимные функции. Создание анонимного класса эквивалентно созданию нового именованного класса с последующим созданием его экземпляра. Эти два шага сливаются в один. Такое малопонятное средство может показаться бесполезным, но на практике многие проектные решения широко его используют для связи наблюдателей и субъектов. Это средство языка работает почти так же, как анонимные функции. Создание анонимного класса эквивалентно созданию нового именованного класса с последующим созданием его экземпляра. Эти два шага сливаются в один. Такое малопонятное средство может показаться бесполезным, но на практике многие проектные решения широко его используют для связи наблюдателей и субъектов.
[В начало ⮍]() [Наверх ⮍](#6-классы-объектно-ориентированный-стиль) [В начало ⮍](#6-11-3-анонимные-классы) [Наверх ⮍](#6-классы-объектно-ориентированный-стиль)
## 6.12. Множественное наследование ## 6.12. Множественное наследование
@ -2109,6 +2109,8 @@ class Sprite3 : HyperObservantActor, VisualActor
О достоинствах и недостатках множественного наследования спорят давно. Дебаты продолжаются и вряд ли прекратятся в ближайшем будущем, но в одном оппоненты сошлись: реализовать множественное наследование так, чтобы оно одновременно было простым, эффективным и полезным, непросто. Одни языки, придавая меньшее значение эффективности, делают выбор в пользу дополнительной выразительности, которую привносит множественное наследование. Другие языки, заложив основу высокой производительности (например, с помощью непрерывных объектов и скоростной диспетчеризации функций), ради этого ограничивают гибкость возможных программных решений. Интересное решение, позволяющее задействовать большинство преимуществ множественного наследования без свойственных ему проблем, примеси в языке Scala (по сути, это интерфейсы, укомплектованные реализациями по умолчанию). Подход языка D заключается в разрешении множественного *порождения подтипов*, то есть порождения подтипов без наследования. Посмотрим, как это работает. О достоинствах и недостатках множественного наследования спорят давно. Дебаты продолжаются и вряд ли прекратятся в ближайшем будущем, но в одном оппоненты сошлись: реализовать множественное наследование так, чтобы оно одновременно было простым, эффективным и полезным, непросто. Одни языки, придавая меньшее значение эффективности, делают выбор в пользу дополнительной выразительности, которую привносит множественное наследование. Другие языки, заложив основу высокой производительности (например, с помощью непрерывных объектов и скоростной диспетчеризации функций), ради этого ограничивают гибкость возможных программных решений. Интересное решение, позволяющее задействовать большинство преимуществ множественного наследования без свойственных ему проблем, примеси в языке Scala (по сути, это интерфейсы, укомплектованные реализациями по умолчанию). Подход языка D заключается в разрешении множественного *порождения подтипов*, то есть порождения подтипов без наследования. Посмотрим, как это работает.
[В начало ⮍](#6-12-множественное-наследование) [Наверх ⮍](#6-классы-объектно-ориентированный-стиль)
## 6.13. Множественное порождение подтипов ## 6.13. Множественное порождение подтипов
Продолжим достраивать программу, использующую класс `Shape`. Допустим, требуется определить объекты класса `Shape`, которые можно было бы хранить в базе данных. Мы нашли отличную библиотеку, которая обеспечивает постоянство объектов с помощью базы данных, что прекрасно нам подходит, кроме одного: она требует, чтобы каждый сохраняемый объект наследовал от класса `DBObject`. Продолжим достраивать программу, использующую класс `Shape`. Допустим, требуется определить объекты класса `Shape`, которые можно было бы хранить в базе данных. Мы нашли отличную библиотеку, которая обеспечивает постоянство объектов с помощью базы данных, что прекрасно нам подходит, кроме одного: она требует, чтобы каждый сохраняемый объект наследовал от класса `DBObject`.
@ -2160,7 +2162,7 @@ unittest
В классе может быть неограниченное число объявлений `alias this`, таким образом, класс может стать подтипом неограниченного числа типов[^14]. В классе может быть неограниченное число объявлений `alias this`, таким образом, класс может стать подтипом неограниченного числа типов[^14].
[В начало ⮍]() [Наверх ⮍](#6-классы-объектно-ориентированный-стиль) [В начало ⮍](#6-13-множественное-порождение-подтипов) [Наверх ⮍](#6-классы-объектно-ориентированный-стиль)
### 6.13.1. Переопределение методов в сценариях множественного порождения подтипов ### 6.13.1. Переопределение методов в сценариях множественного порождения подтипов
@ -2242,7 +2244,7 @@ class StorableShape : Shape
} }
``` ```
[В начало ⮍]() [Наверх ⮍](#6-классы-объектно-ориентированный-стиль) [В начало ⮍](#6-13-1-переопределение-методов-в-сценариях-множественного-порождения-подтипов) [Наверх ⮍](#6-классы-объектно-ориентированный-стиль)
## 6.14. Параметризированные классы и интерфейсы ## 6.14. Параметризированные классы и интерфейсы
@ -2323,7 +2325,7 @@ unittest
Как только вы создадите экземпляр параметризированного класса, он превратится в обычный класс, так что `StackImpl!int` это такой же класс, как и любой другой. Именно этот конкретный класс реализует `Stack!int`, поскольку в формочку для вырезания `StackImpl(T)` под видом `T` вставили `int` по всему телу этого класса. Как только вы создадите экземпляр параметризированного класса, он превратится в обычный класс, так что `StackImpl!int` это такой же класс, как и любой другой. Именно этот конкретный класс реализует `Stack!int`, поскольку в формочку для вырезания `StackImpl(T)` под видом `T` вставили `int` по всему телу этого класса.
[В начало ⮍]() [Наверх ⮍](#6-классы-объектно-ориентированный-стиль) [В начало ⮍](#6-14-параметризированные-классы-и-интерфейсы) [Наверх ⮍](#6-классы-объектно-ориентированный-стиль)
### 6.14.1. И снова гетерогенная трансляция ### 6.14.1. И снова гетерогенная трансляция
@ -2362,7 +2364,7 @@ class StackImpl(T, Backend) : Stack!T
} }
``` ```
[В начало ⮍]() [Наверх ⮍](#6-классы-объектно-ориентированный-стиль) [В начало ⮍](#6-14-1-и-снова-гетерогенная-трансляция) [Наверх ⮍](#6-классы-объектно-ориентированный-стиль)
## 6.15. Переопределение аллокаторов и деаллокаторов[^16] ## 6.15. Переопределение аллокаторов и деаллокаторов[^16]
@ -2468,7 +2470,7 @@ unittest
К тому же, как всегда, при большой силе большая ответственность: если вы будете выделять память под классы, не используя кучу под контролем сборщика мусора, и ваш класс содержит указатели (в том числе неявные, унаследованные от классов-родителей) на данные, выделенные в памяти сборщика мусора, вы обязаны декларировать их создание сборщику мусора вызовом функции `core.memory.GC.addRange()`. Иначе сборщик мусора не будет знать о ссылках на данные, хранящихся в ваших классах, и может счесть мусором данные, на которые ваши классы все еще ссылаются. Это может повлечь за собой порчу данных и трудноуловимые ошибки программы, так как в случаях порчи данных сбой может произойти уже спустя пару минут после порчи в совершенно постороннем, невинном коде, который всего лишь пытался использовать свою ненароком испорченную область памяти. Мы вас предупредили! К тому же, как всегда, при большой силе большая ответственность: если вы будете выделять память под классы, не используя кучу под контролем сборщика мусора, и ваш класс содержит указатели (в том числе неявные, унаследованные от классов-родителей) на данные, выделенные в памяти сборщика мусора, вы обязаны декларировать их создание сборщику мусора вызовом функции `core.memory.GC.addRange()`. Иначе сборщик мусора не будет знать о ссылках на данные, хранящихся в ваших классах, и может счесть мусором данные, на которые ваши классы все еще ссылаются. Это может повлечь за собой порчу данных и трудноуловимые ошибки программы, так как в случаях порчи данных сбой может произойти уже спустя пару минут после порчи в совершенно постороннем, невинном коде, который всего лишь пытался использовать свою ненароком испорченную область памяти. Мы вас предупредили!
[В начало ⮍]() [Наверх ⮍](#6-классы-объектно-ориентированный-стиль) [В начало ⮍](#6-15-переопределение-аллокаторов-и-деаллокаторов-16) [Наверх ⮍](#6-классы-объектно-ориентированный-стиль)
## 6.16. Объекты scope[^17] ## 6.16. Объекты scope[^17]
@ -2596,7 +2598,7 @@ unittest
Конструкция `scoped!T`, которую можно найти в модуле `std.typecons`, столь же небезопасна, но не засоряет очередной излишней возможностью язык (осложняя этим жизнь авторам компиляторов D). Конструкция `scoped!T`, которую можно найти в модуле `std.typecons`, столь же небезопасна, но не засоряет очередной излишней возможностью язык (осложняя этим жизнь авторам компиляторов D).
[В начало ⮍]() [Наверх ⮍](#6-классы-объектно-ориентированный-стиль) [В начало ⮍](#6-16-объекты-scope-17) [Наверх ⮍](#6-классы-объектно-ориентированный-стиль)
## 6.17. Итоги ## 6.17. Итоги
@ -2612,7 +2614,7 @@ unittest
D полностью поддерживает технику невиртуальных интерфейсов, а также полуавтоматический механизм для множественного порождения подтипов. D полностью поддерживает технику невиртуальных интерфейсов, а также полуавтоматический механизм для множественного порождения подтипов.
[В начало ⮍]() [Наверх ⮍](#6-классы-объектно-ориентированный-стиль) [В начало ⮍](#6-17-итоги) [Наверх ⮍](#6-классы-объектно-ориентированный-стиль)
[^1]: Язык D также предоставляет возможность «ручного» управления памятью (manual memory management) и на данный момент позволяет принудительно уничтожать объекты с помощью оператора delete: `delete obj;`, при этом значение ссылки `obj` будет установлено в `null` (см. ниже), а память, выделенная под объект, будет освобождена. Если `obj` уже содержит `null`, ничего не произойдет. Однако следует соблюдать осторожность: повторное уничтожение одного объекта или обращение к удаленному объекту по другой ссылке приведет к катастрофическим последствиям (сбои и порча данных в памяти, источники которых порой очень трудно обнаружить), и эта опасность усугубляет необходимость в сборщике мусора. Из-за этих рисков оператор `delete` планируют убрать из самого языка, оставив в виде функции в стандартной библиотеке. Но при этом ручное управление памятью позволяет более эффективно ее использовать. Вердикт: задействуйте эту возможность, если уверены, что на момент вызова `delete` объект `obj` точно не удален и `obj` последняя ссылка на данный объект, и не удивляйтесь, если в один прекрасный день `delete` исчезнет из реализаций языка. *Прим. науч. ред.* [^1]: Язык D также предоставляет возможность «ручного» управления памятью (manual memory management) и на данный момент позволяет принудительно уничтожать объекты с помощью оператора delete: `delete obj;`, при этом значение ссылки `obj` будет установлено в `null` (см. ниже), а память, выделенная под объект, будет освобождена. Если `obj` уже содержит `null`, ничего не произойдет. Однако следует соблюдать осторожность: повторное уничтожение одного объекта или обращение к удаленному объекту по другой ссылке приведет к катастрофическим последствиям (сбои и порча данных в памяти, источники которых порой очень трудно обнаружить), и эта опасность усугубляет необходимость в сборщике мусора. Из-за этих рисков оператор `delete` планируют убрать из самого языка, оставив в виде функции в стандартной библиотеке. Но при этом ручное управление памятью позволяет более эффективно ее использовать. Вердикт: задействуйте эту возможность, если уверены, что на момент вызова `delete` объект `obj` точно не удален и `obj` последняя ссылка на данный объект, и не удивляйтесь, если в один прекрасный день `delete` исчезнет из реализаций языка. *Прим. науч. ред.*
[^2]: На данный момент реализации D предоставляют средства выделения памяти под классы в стеке (с помощью класса памяти `scope`) или вообще в любом фрагменте памяти (с помощью классовых аллокаторов и деаллокаторов). Но поскольку эти возможности небезопасны, они могут быть удалены из языка, так что не рассчитывайте на их вечное существование. *Прим. науч. ред.* [^2]: На данный момент реализации D предоставляют средства выделения памяти под классы в стеке (с помощью класса памяти `scope`) или вообще в любом фрагменте памяти (с помощью классовых аллокаторов и деаллокаторов). Но поскольку эти возможности небезопасны, они могут быть удалены из языка, так что не рассчитывайте на их вечное существование. *Прим. науч. ред.*