Содержание
Основные понятия ООП (объектно-ориентированного программирования)
В этом разделе мы начнем двигаться вперед и рассмотрим одну из основных концепций языка Dart, а именно объектно-ориентированное программирование.
Dart использует объектно-ориентированное программирование или ООП как парадигму или стиль для написания кода и проектирования приложений с ООП. Мы создаем наши приложения из коллекции объектов.
Объект (Object) содержит некоторое количество данных, относящихся к нашему приложению, например, эти фрагменты данных (Piece of Data), хранящиеся здесь внутри того, что мы называем полями или свойствами. Эти данные скрыты от других частей нашего приложения. Единственный способ получить доступ к этим данным здесь — использовать методы (Method), которые являются функциями, которые определены непосредственно в этом объекте.
Одна вещь, на которую я хочу здесь обратить внимание, заключается в том, что мы используем термин объект для обозначения этой вещи, которая содержит некоторое количество данных. Но в Dart мы с вами будем работать над чем-то немного другим. Это своего рода другая часть этой головоломки.
Итак, в Dart мы с вами собираемся создать то, что называется классами. Класс содержит набор правил, которые определяют, как он работает.
Вы можете представить этот класс здесь как своего рода чертеж или набор правил для работы с этим классом. Мы создаем экземпляры класса, которые называются объектами или экземплярами. Таким образом, класс — это просто набор чертежей, тогда как объект или экземпляр представляет собой реальную рабочую копию этого класса.
Подведем итог: мы с вами пишем код для создания классов. Затем мы используем этот класс для создания объектов или экземпляров.
Хорошая аналогия между классами и экземплярами — представить себе набор чертежей, которые можно использовать для строительства дома. Когда вы впервые хотите построить дом, вы идете к архитектору и просите его составить набор чертежей, описывающих, как будет выглядеть и функционировать дом. После того, как вы соберете этот набор чертежей, вы сможете использовать его для создания неограниченного количества домов или экземпляров.
Итак, в этой аналогии, как вы можете догадаться, набор чертежей здесь подобен классу, а фактически построенный дом или фактически стоящий дом, который физически существует перед вами, будет подобен объекту или экземпляру класса (Instance).
Теперь в Dart объектно-ориентированное программирование — это то, что возникает снова и снова и снова. Очень похоже на типы, которые мы рассматривали ранее. Это то, что мы будем обсуждать на протяжении всего курса много раз.
Итак, один из последних проектов, над которым мы будем работать в рамках этого курса, будет еще до того, как мы начнем работать над Flutter, и он будет направлен на то, чтобы дать вам прочную основу в объектно-ориентированном программировании. Так что вы получите много опыта с этим.
Дальше начнем говорить о том, как мы собираем классы в Dart и о некоторых основных базовых концепциях вокруг них.
Создание классов в Dart
Мы собираемся написать немного кода, чтобы лучше понять, как работают классы в Dart.
Итак, чтобы лучше понять, как работают классы, мы с вами создадим новый класс внутри Dart. Этот класс будет моделировать поведение человека или как бы моделировать человека.
Класс person будет иметь один фрагмент данных или одно поле (Fields), связанное с ним, которое мы называем имя человека (firstName). Это первое именованное свойство или поле будет иметь значение типа String, и так же, как и раньше в нашем последнем приложении, мы возвращали строку Stephen. По сути, эта строка теперь будет существовать в экземпляре класса person, то есть будет полем в классе.
После создания этого поля также будет связан один метод с классом person. Мы назовем этот метод printName. Это будет функция, которая берет свойство имени человека и затем выводит его на терминал.
В целом, это будет класс, который выполняет очень похожее поведение на текущую программу, которую мы рассматривали здесь.
Начнем с реализации нашего класса person здесь. Помните, у каждой создаваемой нами программе должна быть функция main. Но сначала мы займемся классом, а потом уже займемся функцией main.
class Person { }
Итак, чтобы создать класс person, пишем ключевое слово class, а затем имя класса Person. Всякий раз, когда мы создаем класс, мы всегда используем заглавный начальный символ. Затем внутри фигурных скобок у нас есть тело класса в самом верху или в первых двух строках внутри тела класса.
У нас есть возможность объявить все различные поля, которые должны быть связаны с этим классом. Мы с вами хотим иметь одно поле типа String, и поле должно иметь имя firstName, так что чтобы сделать это или реализовать это, нужно вывести строку firstName, а затем запятую или точку с запятой вот так.
class Person { String firsName; }
Итак, это объявляет новое поле типа string с именем firstName в классе Person. Далее добавим метод с именем printName, и всякий раз, когда мы его вызываем, мы хотим выводить значение имени этого человека. Давайте вернемся внутрь DartPad, я могу добавить метод, записав имя метода сначала это будет printName, затем вместо скобок — набор фигурных скобок.
class Person { String firsName; printName() { } }
Таким образом, это выглядит как идентичный синтаксис функций, которые мы рассматривали ранее. Затем внутри этих фигурных скобок мы можем добавить некоторый код, который будет выполняться каждый раз, когда этот метод запускается. Таким образом, в этом случае мы хотим вывести свойство имени этого человека. Итак, чтобы сделать это, я просто выведу print(firstName);
class Person { String firsName; printName() { print(firstName); } }
Теперь, одна вещь, которую вы здесь заметите, это то, что для ссылки на свойство firstName, которое принадлежит этому Person, мы просто написали имя.
Это немного отличается от многих других языков, в частности Java или JavaScript, где вы можете сначала написать this. , а затем фактическое имя свойства (this.firstName). Вот это, добавление в this. — это полностью допустимый синтаксис внутри Dart, так что если вы хотите записать this.firstName, то вы, безусловно, можете это сделать. Но это не требует Dart.
Если вы просто напишете имя свойства, на которое пытаетесь сослаться, и опустите этот this. Dart все равно поймет, что вы пытаетесь сделать, и как бы заполнит это сам.
В целом наш класс Person готов. Помните, это всего лишь своего рода чертеж, и мы пока не можем использовать его.
Мы должны использовать его для создания объекта или экземпляра этого класса. И затем, как только у нас будет этот экземпляр, мы можем начать назначать или задавать некоторые данные внутри экземпляра класса.
Создание экземпляра класса в Dart
Теперь мы собираемся использовать этот класс для создания его нового экземпляра, а затем каким-то образом вызвать метод printName, назначенный этому классу, чтобы создать новый экземпляр из этого класса.
Сначала нам нужно определить основную функцию main. Помните, всякий раз, когда запускается программа на Dart, она будет искать функцию с именем main и выполнять ее в первую очередь. Поэтому, если мы хотим создать новый экземпляр этого класса, нам нужно сделать это внутри этой функции main.
void main() { }
Кстати, мы никогда не говорили о слове void. Помните, что всякий раз, когда мы определяем функцию слева от имени функции, мы помещаем тип, который указывает, какой тип значения мы возвращаем из этой функции. И поэтому, прежде чем у нас было что-то вроде String здесь, когда мы делали эту функцию printName ранее, если у нас есть слово void прямо здесь, это означает, что мы вообще ничего не возвращаем из этой функции.
Теперь, когда у нас есть основная функция main, мы можем использовать этот класс Person для создания нового экземпляра ее.
Чтобы создать новый экземпляр person, сначала объявим новую переменную в нижнем регистре person.
var person =
Это объявление переменной. А затем с правой стороны мы создадим новый экземпляр класса person, написав
var person = new Person();
Таким образом, это дает нам ссылку на новый объект person, который был создан внутри нашего приложения. Теперь установим свойство firstName для экземпляра person.
person.firstName = 'Stephen';
Обратите внимание, что мы смогли задать значение имени прямо здесь как строку, в частности, потому что мы обозначили его тип внутри определения класса. Если бы мы вместо этого попытались присвоить этому целое число, например, 123, мы бы в итоге увидели сообщение об ошибке.
Теперь, когда я попытался присвоить целое число свойству firstName, он сказал: эй, вы не можете сделать это. Вы пытаетесь присвоить целое число строке.
Теперь, когда у нас есть определенное свойство имени для нашего объекта person, мы можем вызвать метод printName для него, и я ожидаю увидеть в консоле имя.
person.printName();
Правильно, так и есть, это короткий пример того, как мы работаем с классами, но я хочу указать на кое-что внутри этого.
Обратите внимание, как создается этот объект person прямо здесь, а затем устанавливается первое именованное свойство для него. Чтобы сделать это, нам пришлось написать две отдельных строки кода. Ну, я не знаю, как вы, но мне кажется, что каждый человек, которого мы создаем внутри нашего приложения вероятно, всегда захочет иметь имя.
void main() { var person = new Person(); person.firstName = 'Stephen'; person.printName(); } class Person { String firstName = ''; printName() { print(firstName); } }
Другими словами, как часто вы встречаете в реальной жизни человека, у которого нет имени? Я думаю, у каждого есть имя, или почти у каждого. Поэтому я думаю, что в следующем разделе мы должны придумать способ, который позволит убедиться, что каждый человек, который создается внутри этого приложения, всегда мгновенно получает свойство имени, назначенное ему, то есть зачем создавать человека и потом определять это свойство имени на следующей строке. Как сделать все это за один шаг?
Функция конструктора в Dart
Мы использовали наш класс person для создания нового экземпляра person, который мы присвоили переменной с маленькой буквы person.
После этого мы устанавливаем значение свойства firstName для этого экземпляра person. Потом мы подумали, что, конечно, кажется немного странным сначала объявлять новый экземпляр человека, а после этого устанавливать свойство имени. Мне кажется, что каждый человек, который существует внутри нашего приложения, всегда должен иметь имя, связанное с ним. Давайте мы добавим немного кода, чтобы убедиться, что любой, кто хочет использовать этот класс человека, должен предоставить свойство имени классу в тот момент, когда создается экземпляр класса.
То есть когда мы создаем новый экземпляр класса, нам необходимо установить сразу свойство этого экземпляра класса. Мы собираемся добавить нечто, называемое функцией конструктора.
Функция конструктора будет другой функцией или методом, добавленным к этому классу. Эта функция конструктора выполняется автоматически каждый раз, когда мы создаем новый экземпляр класса человек.
И поэтому это идеальное место для небольшой начальной настройки или инициализации нашего класса. Итак, чтобы просто представить это в виде диаграммы, вот что будет происходить.
1) Всякий раз, когда мы с вами создаем новый экземпляр класса, будет вызываться функция-конструктор с любыми предоставленными нами аргументами.
2) Когда мы пытаемся создать новый экземпляр, эта функция конструктора будет выполнена, и она выполнит некоторую начальную настройку для нас.
3) И затем, в конце концов, это запускает наш новый экземпляр класса, затем возвращается и затем присваивается этой переменной person.
var person = new Person();
Давайте определим конструктор для нашего класса person.
Определяем новую функцию с очень особенным именем.
Таким образом, имя функции будет Person() с заглавной буквы P. Итак, обратите внимание, что имя этой функции здесь абсолютно идентично имени класса, потому что эти два имени полностью идентичны по регистру и написанию. Итак, они оба с заглавной буквы P.
Эта функция Person() здесь будет использоваться как функция-конструктор. И поэтому эта функция будет вызываться автоматически каждый раз, когда мы пытаемся создать новый экземпляр класса person. И снова, это делает его идеальным местом для выполнения некоторой начальной настройки нашего экземпляра класса.
Теперь, как мы говорили только что, мы хотим добавить требование к нашему приложению. Мы хотим сказать, что всякий раз, когда вы пытаетесь создать экземпляр человека, вы должны немедленно предоставить имя.
Другими словами, в нашем приложении не должно быть людей, у которых нет имени. Так что, чтобы добавить это требование, мы можем указать аргумент внутри этого аргумента функций конструктора списка или набора скобок. Так что внутри набора скобок я собираюсь написать имя, например, так:
Person(name) { }
Как только я это ввожу, вы заметите, что справа появляется ошибка здесь:
var person = new Person();
Так что как только мы начинаем добавлять аргумент в функцию, это означает, что где бы мы ни вызывали эту функцию она должна предоставлять правильное количество и тип аргументов. Таким образом, чтобы убедиться, что каждый человек создается с именем, нам просто нужно добавить аргумент и если вы собираетесь попытаться создать человека, вам также нужно передать какой-то аргумент.
Нам нужно это исправить это при создании нового экземпляра класса.
Но сначала давайте закончим наш конструктор. Поэтому, как только кто-то предоставит свойство name этой функции конструктора, мы, вероятно, захотим взять это предоставленное имя, и присвоить его свойству firstName. Сделаем это:
Person(name) { firstName = name; }
Теперь, одна вещь, на которую я хочу обратить внимание, это то, что мой выбор имен переменных немного неясен. Я указал здесь имя переменной или имя аргумента просто name. И поэтому внутри моего конструктора не совсем ясно, name это фамилия или имя человека?
Это не совсем ясно. И поэтому вы можете подумать, ну, было бы немного понятнее, если бы мы просто написали имя firstName. Но тогда это делает эту строку кода немного менее понятной, потому что технически это будет firstName и теперь мы находимся в состоянии, когда все очень неясно. У нас есть имя аргумента, имя fistName, но имя нашего свойства также является именем name.
Person(firstName) { firstName = firstName; }
Так что Dart понимает, что это, вероятно, строка кода, которую вы захотите написать всегда очень часто встречаются аргументы конструктора с именами, идентичными именам свойств, которым вы пытаетесь подписать их, чтобы присвоить им.
Так что вместо того, чтобы заставлять вас писать какой-то очень неуклюжий код, как этот, у Dart есть встроенный сокращенный код, который как бы решает всю эту ситуацию за нас. Итак, давайте напишем этот сокращенный код, и я думаю, вы будете довольны тем, как это получится.
Person(this.firstName);
Это довольно безумный синтаксис, но вот что происходит сейчас. Всякий раз, когда мы создаем новый экземпляр класса person и передаем первый аргумент этому созданию, Dart будет автоматически принимать этот первый аргумент, назначать его свойству firstName экземпляру класса который создается.
Обычно, когда мы видим этот набор скобок после имени функции, мы думаем о нем как о списке аргументов, но DART вместо этого берет этот список аргументов и как бы делает для вас небольшое сокращение. И он говорит: ну, ну, поскольку вы назвали этот аргумент именем this.firstName, я просто возьму любой аргумент, переданный этому новому человеку при его создании, и автоматически назначу его свойству имени для вас.
Теперь я использую много слов, чтобы описать это, но это может быть не очень понятно, поэтому давайте как-то завершим этот пример, и я думаю, что со временем он станет немного более очевидным.
Возвращаемся в функцию main и пишем так.
var person = new Person('Stephen');
Мы создаем экземпляр класса person и передаем первый аргумент напрямую в эту функцию-конструктор.
Теперь запустив программу, мы снова увидим в консоле имя Stephen.
void man() { var person = new Person('Stephen'); person.printName(); } class Person { String firstName; Person(this.firstName); printName() { print(firstName); }