Объектно-ориентированное программирование

Классы и объекты

Последнее обновление: 11.04.2023

Dart является объектно-ориентированным языком, и каждое значение, которым мы манипулируем в программе на Dart, является объектом.

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

Класс определяется с помощью ключевого слова сlass:

class Person{

}

Здесь определен класс Person. После названия класса идут фигурные скобки, между которыми помещается тело класса - то есть его поля и методы. Несмотря на то, что класс фактически пуст, но это фактически уже новый тип. И мы можем определять переменные этого типа.

class Person{ } // пустой класс Person

void main() {
     
    Person tom;     // определяем переменную класса Person
}

В данном случае переменная tom представляет класс Person.

Создание объекта. Конструктор

Переменная tom еще не представляет никакой объект, и использовать ее никак нельзя. Нам надо создать объект класса Person. Для создания объектов класса применяются специальные функции, которые называются по имени класса - конструкторы. Если в классе не определено ни одного конструктора (как в случае класса Person), то для этого класса автоматически создается конструктор без параметров. Применим конструктор по умолчанию для создания объекта Person:

class Person{ } // пустой класс Person

void main() {
     
    Person tom = Person();     // создаем объект класса Person
    print(tom);
}

Для создания объекта Person используется выражение Person(). Конструктор по умолчанию не принимает никаких параметров. В итоге после выполнения данного выражения в памяти будет выделен участок, где будут храниться все данные объекта Person. А переменная tom получит ссылку на созданный объект. И мы сможем использовать ее, например, передать в функцию print и вывести на консоль. Хотя консоль малоинформативное сообщение вроде "Instance of 'Person'", но без вызова конструктора мы с переменной tom даже этого не смогли бы сделать.

Поля и методы класса

Любой объект может обладать двумя основными характеристиками: состояние - некоторые данные, которые хранит объект, и поведение - действия, которые может совершать объект.

Для хранения состояния объекта в классе применяются поля или переменные класса. Для определения поведения объекта в классе применяются методы. Например, класс Person, который представляет человека, мог бы иметь следующее определение:

class Person{
	
	String name = "";		// имя
	int age = 0;					// возраст
	void display(){
		print("Name: $name \tAge: $age");
	}
}

В классе Person определены два поля: name представляет имя человека, а age - его возраст. И также определен метод display, который ничего не возвращает и просто выводит эти данные на консоль.

После создания объекта мы можем обратиться к переменным и методам объекта Person через имя переменной. Для этого используется оператор точка (.) - то есть через точку указываем название поля или метода: tom.name.

имя_объекта.поле
имя_объекта.метод()

Например, используем переменные и метод класса Person:

class Person{ 

    String name = "";		// имя
	int age = 0;					// возраст
    void display(){
        print("Name: $name \tAge: $age");
    }
}

void main() {
     
    Person tom = Person();  // создаем объект класса Person
	print(tom.age);		// обращаемся к полю age
    tom.display();          // обращаемся к методу display

    // изменяем имя и возраст
    tom.name = "Tom";
    tom.age = 38;
    tom.display();
}

Таким образом, обратившись к полям объекта, мы можем получить или изменить их значения:

print(tom.age);
tom.name = "Tom";
tom.age = 38;

Обратившись к методам объекта, мы можем вызвать действия, которые прописаны в этом методе:

tom.display();

Консольный вывод программы:

Unknown
Name:           Age: 0
Name: Tom       Age: 38

Инициализация полей

Стоит учитывать, что поскольку переменные name и age представляют типы String и int, которые не допускают значение null, то нам необходимо предоставить этим переменным начальные значения. Либо мы могли бы использовать nullable-типы, тогда предоставление начальных значений было бы необязательно:

class Person{ 

    String? name;		// равно null
	int? age;			// равно null
    void display(){
        print("Name: $name \tAge: $age");
    }
}

void main() {
     
    Person tom = Person();
    tom.display();          // Name: null      Age: null

    tom.name = "Tom";
    tom.age = 38;
    tom.display();          // Name: Tom       Age: 38
}

В данном случае поля name и age по умолчанию равны null. И в данном случае мы получим следующий консольный вывод:

Name: null      Age: null
Name: Tom       Age: 38

В то же время к null-переменным следует относится осторожно, поскольку если мы вдруг задействуем такие переменные до установки им конкретного значения, то мы столкнемся с ошибкой:

Person tom = Person();
tom.age = tom.age + 1;  // ! Ошибка

Поэтому более предпочтительным вариантом является все таки установка некоторых начальных значений.

Каскадная нотация

Каскадная нотация - операция .. позволяет выполнить последовательность операций над одним объектом:

class Person{
 
    String name = "";
	int age = 0;
     
    void display(){
        print("Name: $name \tAge: $age");
    }
}
void main (){
	
	Person tom = Person()
		..name = "Tom"
		..age = 38
		..display();	// Name: Tom  Age: 38
} 

В данном случае код в функции main будет аналогичен следующему:

void main (){
	
	Person tom = Person();
	tom.name = "Tom"
	tom.age = 36
	tom.display();	// Name: Tom  Age: 36
} 

Копирование объектов

Переменный и константы классов в языке Dart фактически представляют ссылки на объекты классов в памяти. Это означает, что если одной переменной класса присваивается значение другой переменной, то переменная в реальности получает ссылку на тот же объект в памяти. И таким образом, две переменных будут ссылаться на один и тот же объект в памяти. Посмотрим на примере:

class Person{

	String name ="";
}

void main() {

    Person tom = Person();
    tom.name = "Tom";

    Person bob = tom;   // bob указывает на тот же объект, что и tom
    bob.name = "Bob";   // меняем у boba имя
    print(tom.name);    // Bob - у toma тоже поменялось имя
}

После присвоения переменной bob значения переменной tom обе эти переменных будут ссылаться на один и тот же объект в памяти:

Person bob = tom;

Соответственно если мы у объекта bob изменим значение переменной name:

bob.name = "Bob";

То для объекта tom также изменится значение name, потому что tom и bob указывают на один и тот же объект:

print(tom.name);    // Bob

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

Дополнительные материалы
Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850