This commit is contained in:
Alexander Zhirov 2023-02-27 01:22:09 +03:00
parent 1d0a2964cf
commit 151d77ee54
11 changed files with 340 additions and 0 deletions

View File

@ -105,6 +105,8 @@ unittest
Обратите внимание на небольшую хитрость. В приведенном коде использовано выражение `w.defaultName`, а не `Widget.defaultName`. Для обращения к статическому члену класса всегда можно вместо имени класса использовать имя экземпляра класса. Это возможно, потому что при обработке выражения слева от точки сначала выполняется разрешение имени и только потом идентификация объекта (если потребуется). Выражение w в любом случае вычисляется: будет оно использовано или нет. Обратите внимание на небольшую хитрость. В приведенном коде использовано выражение `w.defaultName`, а не `Widget.defaultName`. Для обращения к статическому члену класса всегда можно вместо имени класса использовать имя экземпляра класса. Это возможно, потому что при обработке выражения слева от точки сначала выполняется разрешение имени и только потом идентификация объекта (если потребуется). Выражение w в любом случае вычисляется: будет оно использовано или нет.
[Исходный код](src/chapter-6-1/)
[В начало ⮍](#6-1-классы) [Наверх ⮍](#6-классы-объектно-ориентированный-стиль) [В начало ⮍](#6-1-классы) [Наверх ⮍](#6-классы-объектно-ориентированный-стиль)
## 6.2. Имена объектов это ссылки ## 6.2. Имена объектов это ссылки
@ -214,6 +216,8 @@ if (‹условие›)
Сравним ссылочную семантику с семантикой значений а-ля `int`. У семантики значений есть свои преимущества, среди которых выделяется логический вывод: в выражениях всегда можно заменять равные значения друг на друга, при этом результат не изменяется. (А к ссылкам, использующим для изменения состояния объектов вызовы методов, такой подход неприменим.) Другое важное преимущество семантики значений скорость. Но даже если вы воспользуетесь динамической щедростью полиморфизма, от ссылочной семантики никуда не деться. Некоторые языки пытались предоставить возможность использовать и ту, и другую семантику и заслужили прозвище «нечистых» (в противоположность чисто объектно-ориентированным языкам, использующим ссылочную семантику унифицированно для всех типов). D нечист и очень гордится этим. Во время разработки необходимо принять решение: если вы желаете работать с некоторым типом в рамках объектно-ориентированной парадигмы, следует выбрать тип `class`; иначе придется использовать тип `struct` и поступиться всеми удобствами ООП, присущими ссылочной семантике. Сравним ссылочную семантику с семантикой значений а-ля `int`. У семантики значений есть свои преимущества, среди которых выделяется логический вывод: в выражениях всегда можно заменять равные значения друг на друга, при этом результат не изменяется. (А к ссылкам, использующим для изменения состояния объектов вызовы методов, такой подход неприменим.) Другое важное преимущество семантики значений скорость. Но даже если вы воспользуетесь динамической щедростью полиморфизма, от ссылочной семантики никуда не деться. Некоторые языки пытались предоставить возможность использовать и ту, и другую семантику и заслужили прозвище «нечистых» (в противоположность чисто объектно-ориентированным языкам, использующим ссылочную семантику унифицированно для всех типов). D нечист и очень гордится этим. Во время разработки необходимо принять решение: если вы желаете работать с некоторым типом в рамках объектно-ориентированной парадигмы, следует выбрать тип `class`; иначе придется использовать тип `struct` и поступиться всеми удобствами ООП, присущими ссылочной семантике.
[Исходный код](src/chapter-6-2/)
[В начало ⮍](#6-2-имена-объектов-это-ссылки) [Наверх ⮍](#6-классы-объектно-ориентированный-стиль) [В начало ⮍](#6-2-имена-объектов-это-ссылки) [Наверх ⮍](#6-классы-объектно-ориентированный-стиль)
## 6.3. Жизненный цикл объекта ## 6.3. Жизненный цикл объекта
@ -239,6 +243,8 @@ unittest
При вычислении выражения `new Test` конструируется объект типа `Test` с состоянием по умолчанию, то есть экземпляр класса `Test`, каждое из полей которого инициализировано своим значением по умолчанию. Любой тип `T` обладает статически известным значением по умолчанию, обратиться к которому можно через свойство `T.init` (значения свойств `.init` для базовых типов приведены в табл. 2.1). Если вы хотите инициализировать некоторые поля значениями, отличными от соответствующих значений свойства `.init`, укажите при определении этих полей статически известные инициализирующие значения, как показано в предыдущем примере для поля `a`. Выполнение теста модуля при этом не порождает исключений, так как это поле явно инициализируется константой `0.4`, а поле `b` не трогали, а значит, оно неявно инициализируется значением выражения `double.init`, то есть NaN («нечисло»). При вычислении выражения `new Test` конструируется объект типа `Test` с состоянием по умолчанию, то есть экземпляр класса `Test`, каждое из полей которого инициализировано своим значением по умолчанию. Любой тип `T` обладает статически известным значением по умолчанию, обратиться к которому можно через свойство `T.init` (значения свойств `.init` для базовых типов приведены в табл. 2.1). Если вы хотите инициализировать некоторые поля значениями, отличными от соответствующих значений свойства `.init`, укажите при определении этих полей статически известные инициализирующие значения, как показано в предыдущем примере для поля `a`. Выполнение теста модуля при этом не порождает исключений, так как это поле явно инициализируется константой `0.4`, а поле `b` не трогали, а значит, оно неявно инициализируется значением выражения `double.init`, то есть NaN («нечисло»).
[Исходный код](src/chapter-6-3/)
[В начало ⮍](#6-3-жизненный-цикл-объекта) [Наверх ⮍](#6-классы-объектно-ориентированный-стиль) [В начало ⮍](#6-3-жизненный-цикл-объекта) [Наверх ⮍](#6-классы-объектно-ориентированный-стиль)
### 6.3.1. Конструкторы ### 6.3.1. Конструкторы
@ -303,6 +309,8 @@ class NoGo
Обычные правила перегрузки функций (раздел 5.5) применимы и к конструкторам: класс может определять любое количество конструкторов, но каждый из них должен обладать уникальной сигнатурой (отличающейся числом или типом параметров, хотя бы на один параметр). Обычные правила перегрузки функций (раздел 5.5) применимы и к конструкторам: класс может определять любое количество конструкторов, но каждый из них должен обладать уникальной сигнатурой (отличающейся числом или типом параметров, хотя бы на один параметр).
[Исходный код](src/chapter-6-3-1/)
[В начало ⮍](#6-3-1-конструкторы) [Наверх ⮍](#6-классы-объектно-ориентированный-стиль) [В начало ⮍](#6-3-1-конструкторы) [Наверх ⮍](#6-классы-объектно-ориентированный-стиль)
### 6.3.2. Делегирование конструкторов ### 6.3.2. Делегирование конструкторов
@ -722,6 +730,8 @@ 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`. Друзья всегда остаются друзьями, даже если компилятор думает, что это обыкновенные контакты.
[Исходный код](src/chapter-6-4/)
[В начало ⮍](#6-4-методы-и-наследование) [Наверх ⮍](#6-классы-объектно-ориентированный-стиль) [В начало ⮍](#6-4-методы-и-наследование) [Наверх ⮍](#6-классы-объектно-ориентированный-стиль)
### 6.4.1. Терминологический «шведский стол» ### 6.4.1. Терминологический «шведский стол»
@ -902,6 +912,8 @@ void workWith(TextWidget tw)
Чтобы максимизировать количество доступной статической информации о типах, D вводит средство, известное как *ковариантные возвращаемые типы*. Звучит довольно громко, но смысл ковариантности возвращаемых типов довольно прост: если родительский класс возвращает некоторый тип `C`, то переопределенной функции разрешается возвращать не только `C`, но и любого потомка `C`. Благодаря этому средству можно позволить методу `TextWidget.duplicate` возвращать `TextWidget`. Не менее важно, что теперь вы можете прибавить себе веса в дискуссии, вставив при случае фразу «ковариантные возвращаемые типы». (Шутка. Если серьезно, даже не пытайтесь.) Чтобы максимизировать количество доступной статической информации о типах, D вводит средство, известное как *ковариантные возвращаемые типы*. Звучит довольно громко, но смысл ковариантности возвращаемых типов довольно прост: если родительский класс возвращает некоторый тип `C`, то переопределенной функции разрешается возвращать не только `C`, но и любого потомка `C`. Благодаря этому средству можно позволить методу `TextWidget.duplicate` возвращать `TextWidget`. Не менее важно, что теперь вы можете прибавить себе веса в дискуссии, вставив при случае фразу «ковариантные возвращаемые типы». (Шутка. Если серьезно, даже не пытайтесь.)
[Исходный код](src/chapter-6-4-5/)
[В начало ⮍](#6-4-5-ковариантные-возвращаемые-типы) [Наверх ⮍](#6-классы-объектно-ориентированный-стиль) [В начало ⮍](#6-4-5-ковариантные-возвращаемые-типы) [Наверх ⮍](#6-классы-объектно-ориентированный-стиль)
## 6.5. Инкапсуляция на уровне классов с помощью статических членов ## 6.5. Инкапсуляция на уровне классов с помощью статических членов
@ -1140,6 +1152,8 @@ unittest
Обратите внимание: вместе с именем класса возвращено и имя модуля, в котором класс был определен. По умолчанию модуль получает имя файла, в котором он расположен, но это умолчание можно изменить с помощью объявления с ключевым словом `module` (см. раздел 11.8). Обратите внимание: вместе с именем класса возвращено и имя модуля, в котором класс был определен. По умолчанию модуль получает имя файла, в котором он расположен, но это умолчание можно изменить с помощью объявления с ключевым словом `module` (см. раздел 11.8).
[Исходный код](src/chapter-6-8-1/)
[В начало ⮍](#6-8-1-string-tostring) [Наверх ⮍](#6-классы-объектно-ориентированный-стиль) [В начало ⮍](#6-8-1-string-tostring) [Наверх ⮍](#6-классы-объектно-ориентированный-стиль)
### 6.8.2. size_t toHash() ### 6.8.2. size_t toHash()
@ -1882,6 +1896,8 @@ class Outer
} }
``` ```
[Исходный код](src/chapter-6-11/)
[В начало ⮍](#6-11-вложенные-классы) [Наверх ⮍](#6-классы-объектно-ориентированный-стиль) [В начало ⮍](#6-11-вложенные-классы) [Наверх ⮍](#6-классы-объектно-ориентированный-стиль)
### 6.11.1. Вложенные классы в функциях ### 6.11.1. Вложенные классы в функциях
@ -2244,6 +2260,8 @@ class StorableShape : Shape
} }
``` ```
[Исходный код](src/chapter-6-13-1/)
[В начало ⮍](#6-13-1-переопределение-методов-в-сценариях-множественного-порождения-подтипов) [Наверх ⮍](#6-классы-объектно-ориентированный-стиль) [В начало ⮍](#6-13-1-переопределение-методов-в-сценариях-множественного-порождения-подтипов) [Наверх ⮍](#6-классы-объектно-ориентированный-стиль)
## 6.14. Параметризированные классы и интерфейсы ## 6.14. Параметризированные классы и интерфейсы
@ -2325,6 +2343,8 @@ unittest
Как только вы создадите экземпляр параметризированного класса, он превратится в обычный класс, так что `StackImpl!int` это такой же класс, как и любой другой. Именно этот конкретный класс реализует `Stack!int`, поскольку в формочку для вырезания `StackImpl(T)` под видом `T` вставили `int` по всему телу этого класса. Как только вы создадите экземпляр параметризированного класса, он превратится в обычный класс, так что `StackImpl!int` это такой же класс, как и любой другой. Именно этот конкретный класс реализует `Stack!int`, поскольку в формочку для вырезания `StackImpl(T)` под видом `T` вставили `int` по всему телу этого класса.
[Исходный код](src/chapter-6-14/)
[В начало ⮍](#6-14-параметризированные-классы-и-интерфейсы) [Наверх ⮍](#6-классы-объектно-ориентированный-стиль) [В начало ⮍](#6-14-параметризированные-классы-и-интерфейсы) [Наверх ⮍](#6-классы-объектно-ориентированный-стиль)
### 6.14.1. И снова гетерогенная трансляция ### 6.14.1. И снова гетерогенная трансляция

View File

@ -0,0 +1,43 @@
class Widget
{
// Константа
enum fudgeFactor = 0.2;
// Разделяемое неизменяемое значение
static immutable defaultName = "A Widget";
// Некоторое состояние, определенное для всех экземпляров класса Widget
string name = defaultName;
uint width, height;
// Статический метод
static double howFudgy()
{
return fudgeFactor;
}
// Метод
void changeName(string another)
{
name = another;
}
// Метод, который нельзя переопределить
final void quadrupleSize()
{
width *= 2;
height *= 2;
}
}
unittest
{
// Обратиться к статическому методу класса Widget
assert(Widget.howFudgy() == 0.2);
// Создать экземпляр класса Widget
auto w = new Widget;
// Поиграть с объектом типа Widget
assert(w.name == w.defaultName); // Или Widget.defaultName
w.changeName("Мой виджет");
assert(w.name == "Мой виджет");
}

View File

@ -0,0 +1,23 @@
class Outer
{
int x;
class Inner
{
int y;
this()
{
x = 42;
// x то же, что this.outer.x
assert(this.outer.x == 42);
}
}
}
unittest
{
auto outer = new Outer;
auto inner = outer.new Inner;
assert(outer.x == 42); // Вло­жен­ный объ­ект inner из­ме­нил внеш­ний объ­ект outer
}

View File

@ -0,0 +1,60 @@
import std.stdio : writeln;
class Shape
{
protected string _name;
abstract void print();
}
class DBObject
{
protected string _name;
abstract void print();
void saveState()
{
writeln(_name);
}
}
class StorableShape : Shape
{
private class MyDBObject : DBObject
{
this(string name)
{
_name = name;
}
override void print()
{
writeln(_name);
}
final override void saveState()
{
writeln(this.outer._name, "; ", _name);
}
}
private MyDBObject _store;
alias _store this;
this(string outName, string inName)
{
_name = outName;
_store = new MyDBObject(inName);
}
override void print()
{
writeln(_name);
}
}
void main()
{
auto s = new StorableShape("first", "second");
s.print(); // StorableShape._name
s._store.print(); // StorableShape.MyDBObject._name
s.saveState(); // StorableShape._name and StorableShape.MyDBObject._name
}

View File

@ -0,0 +1,50 @@
import std.array;
interface Stack(T)
{
@property bool empty();
@property ref T top();
void push(T value);
void pop();
}
class StackImpl(T) : Stack!T
{
private T[] _store;
@property bool empty()
{
return _store.empty;
}
@property ref T top()
{
assert(!empty);
return _store.back;
}
void push(T value)
{
_store ~= value;
}
void pop()
{
assert(!empty);
_store.popBack();
}
}
void main()
{
auto stack = new StackImpl!int;
assert(stack.empty);
stack.push(3);
assert(stack.top == 3);
stack.push(5);
assert(stack.top == 5);
stack.pop();
assert(stack.top == 3);
stack.pop();
assert(stack.empty);
}

View File

@ -0,0 +1,29 @@
import std.stdio;
class A
{
int x = 42;
}
unittest
{
{
auto a1 = new A;
assert(a1.x == 42);
auto a2 = a1;
a2.x = 100;
assert(a1.x == 100);
}
{
auto a1 = new A;
auto a2 = new A;
a1.x = 100;
a2.x = 200;
// Заставим a1 и a2 обменяться привязками
auto t = a1;
a1 = a2;
a2 = t;
assert(a1.x == 200);
assert(a2.x == 100);
}
}

View File

@ -0,0 +1,23 @@
import std.math;
class Test
{
double a = 0.4;
double b;
this(int b)
{
this.b = b;
}
this() {}
}
unittest
{
auto t = new Test(5);
auto z = new Test;
assert(t.b == 5);
assert(isNaN(z.b));
}

View File

@ -0,0 +1,14 @@
import std.math;
class Test
{
double a = 0.4;
double b;
}
unittest
{
// Объект создается с помощью выражения new
auto t = new Test;
assert(t.a == 0.4 && isNaN(t.b));
}

View File

@ -0,0 +1,31 @@
class Widget
{
this(Widget source)
{
}
Widget duplicate()
{
return new Widget(this); // Вы­де­ля­ет па­мять и вы­зы­ва­ет this(Widget)
}
}
class TextWidget : Widget
{
this(TextWidget source)
{
super(source);
}
override TextWidget duplicate()
{
return new TextWidget(this);
}
}
void main()
{
TextWidget tw;
TextWidget clone = tw.duplicate();
}

View File

@ -0,0 +1,39 @@
class Contact
{
string bgColor()
{
return "Серый";
}
}
class Friend : Contact
{
string currentBgColor = "Светло-зеленый";
string currentReminder;
this(ref string c)
{
currentBgColor = c;
}
override string bgColor()
{
return currentBgColor;
}
string reminder()
{
return currentReminder;
}
}
unittest
{
string startColor = "Синий";
Friend f = new Friend(startColor);
Contact c = f; // Подставить экземпляр класса Friend вместо экземпляра класса Contact
auto color = c.bgColor(); // Вызвать метод класса Friend
import std.stdio : writeln;
writeln(color);
}

View File

@ -0,0 +1,8 @@
module test;
class Widget {}
unittest
{
assert((new Widget).toString() == "test.Widget");
}