Концепции объектно-ориентированного программирования (ООП) в Dart | Флаттер
1. Класс и объект —план будущего:
В ООП класс служит планом или шаблоном для создания объектов. Он определяет свойства (атрибуты) и методы (поведение), которыми будут обладать объекты класса. Объект, с другой стороны, является экземпляром класса. В Dart классы определяются с помощью ключевого слова class
.
class Person { String name; int age; void sayHello() { print("Hello, I'm $name!"); } } void main() { var person1 = Person(); person1.name = "John"; person1.age = 30; person1.sayHello(); // Output: "Hello, I'm John!" }
— — —
2. Инкапсуляция — защита сокровища:
Инкапсуляция — это концепция объединения данных (атрибутов) и методов (поведения) внутри класса, скрывающая детали внутренней реализации от внешнего мира. Это способствует безопасности данных и снижает сложность кода. В Dart вы можете использовать модификаторы доступа, такие как public
, private
и protected
, чтобы контролировать видимость членов класса.
class BankAccount { double _balance; // Private attribute double get balance => _balance; // Getter for balance void deposit(double amount) { _balance += amount; } void withdraw(double amount) { if (_balance >= amount) { _balance -= amount; } else { print("Insufficient funds!"); } } } void main() { var account = BankAccount(); account.deposit(1000); print("Balance: \$${account.balance}"); // Output: "Balance: $1000.0" account.withdraw(500); print("Balance: \$${account.balance}"); // Output: "Balance: $500.0" }
— — —
3. Наследование — повторное использование силы:
Наследование — это механизм, при котором класс (подкласс или производный класс) наследует свойства и методы другого класса (суперкласса или базового класса). Это способствует повторному использованию кода и иерархической организации. В Dart вы можете использовать ключевое слово extends
для создания подкласса.
class Animal { String name; void speak() { print("Animal makes a sound"); } } class Dog extends Animal { @override void speak() { print("Dog barks"); } } void main() { var dog = Dog(); dog.name = "Buddy"; dog.speak(); // Output: "Dog barks" }
— — —
4. Множественное наследование:
Множественное наследование относится к ситуации, когда класс может наследовать свойства и поведение более чем от одного суперкласса. В некоторых языках программирования, таких как C++, множественное наследование поддерживается напрямую, но в Dart это достигается за счет использования примесей.
class A { void methodA() { print("Method from A"); } } class B { void methodB() { print("Method from B"); } } class C with A, B { void methodC() { print("Method from C"); } } void main() { var c = C(); c.methodA(); // Output: "Method from A" c.methodB(); // Output: "Method from B" c.methodC(); // Output: "Method from C" }
В приведенном выше примере класс C
использует ключевое слово with
для смешивания обоих классов A
и B
, что позволяет ему наследовать методы обоих суперклассов.
— — —
5. Многоуровневое наследование:
Многоуровневое наследование относится к ситуации, когда класс может наследовать от другого класса, который, в свою очередь, наследуется от еще одного класса. Он создает цепочку наследования, и производный класс имеет доступ к свойствам и методам всех своих предков.
class A { void methodA() { print("Method from A"); } } class B extends A { void methodB() { print("Method from B"); } } class C extends B { void methodC() { print("Method from C"); } } void main() { var c = C(); c.methodA(); // Output: "Method from A" c.methodB(); // Output: "Method from B" c.methodC(); // Output: "Method from C" }
В этом примере класс C
расширяет класс B
, который, в свою очередь, расширяет класс A
, создавая многоуровневую цепочку наследования.
— — —
6. Полиморфизм — многоликость ООП
Полиморфизм позволяет рассматривать объекты разных классов как объекты общего суперкласса. Это обеспечивает динамическую диспетчеризацию методов и достигается за счет переопределения и перегрузки методов. В Dart переопределение метода достигается с помощью аннотации @override
.
class Shape { void draw() { print("Drawing a shape"); } } class Circle extends Shape { @override void draw() { print("Drawing a circle"); } } class Square extends Shape { @override void draw() { print("Drawing a square"); } } void main() { Shape circle = Circle(); Shape square = Square(); circle.draw(); // Output: "Drawing a circle" square.draw(); // Output: "Drawing a square" }
=› Переопределение метода и ключевое слово Super — тонкая настройка поведения:
Переопределение метода — мощная функция ООП, которая позволяет подклассу предоставлять конкретную реализацию метода, определенного в его суперклассе. Это позволяет настраивать поведение на основе требований подкласса. Ключевое слово super
играет решающую роль в переопределении методов, обеспечивая доступ к методам и свойствам суперкласса.
class Animal { void makeSound() { print("Animal makes a sound"); } } class Dog extends Animal { @override void makeSound() { print("Dog barks"); super.makeSound(); // Call to superclass method } } void main() { var dog = Dog(); dog.makeSound(); // Output: // "Dog barks" // "Animal makes a sound" }
В этом примере у нас есть суперкласс Animal
с методом makeSound()
. Подкласс Dog
расширяет Animal
и переопределяет метод makeSound()
, используя аннотацию @override
. Когда мы создаем объект класса Dog
и вызываем makeSound()
, выполняется переопределенный метод в Dog
, печатая «Собачий лай».
— —
=› Перегрузка метода:
Перегрузка методов — это функция некоторых языков программирования, позволяющая классу иметь несколько методов с одинаковыми именами, но разными списками параметров. Методы могут выполнять аналогичные операции, но с разными входными данными, что делает код более гибким и читабельным.
Dart не поддерживает традиционную перегрузку методов, поскольку использует необязательные именованные параметры и значения по умолчанию для достижения аналогичной функциональности. Давайте посмотрим на пример:
class MathOperations { int add(int a, int b) { return a + b; } double add(double a, double b) { return a + b; } } void main() { var math = MathOperations(); int result1 = math.add(5, 10); double result2 = math.add(3.14, 2.71); print("Result 1: $result1"); // Output: "Result 1: 15" print("Result 2: $result2"); // Output: "Result 2: 5.85" }
В этом примере класс MathOperations
имеет два метода с именами add
. Один принимает два параметра int
и возвращает int
, а другой принимает два параметра double
и возвращает double
. Хотя в Dart нет традиционной перегрузки методов, мы достигаем аналогичного эффекта, определяя методы с различными типами параметров.
— — —
7. Абстрактные классы:
Абстрактный класс в Dart — это класс, экземпляр которого нельзя создать напрямую. Он служит образцом для других классов, предоставляя общий набор методов (функциональных возможностей), которые должны быть реализованы в подклассах. Абстрактные классы используются для определения общей структуры для связанных классов, гарантируя, что все подклассы имеют определенные методы.
В контексте кодирования абстрактный класс создается с использованием ключевого слова abstract
, и он может иметь или не иметь абстрактные методы (методы без реализации). Подклассы абстрактного класса должны переопределять абстрактные методы, чтобы обеспечить собственную реализацию.
// Abstract class definition abstract class Shape { // Abstract method without implementation void draw(); // Non-abstract method void calculateArea() { print("Calculating area of the shape"); } } // Subclass Circle that extends Shape class Circle extends Shape { @override void draw() { print("Drawing a circle"); } } // Subclass Square that extends Shape class Square extends Shape { @override void draw() { print("Drawing a square"); } } void main() { var circle = Circle(); var square = Square(); circle.draw(); // Output: "Drawing a circle" square.draw(); // Output: "Drawing a square" circle.calculateArea(); // Output: "Calculating area of the shape" square.calculateArea(); // Output: "Calculating area of the shape" }
В этом примере мы определяем абстрактный класс Shape
с абстрактным методом draw()
и неабстрактным методом calculateArea()
. Оба класса Circle
и Square
расширяют абстрактный класс Shape
и предоставляют собственную реализацию метода draw()
. Они также наследуют неабстрактный метод calculateArea()
от абстрактного класса.
— — —
8. Интерфейсы:
Интерфейс в Dart представляет собой контракт, которого должны придерживаться классы. Он определяет набор сигнатур методов (имена функций и их параметры) без предоставления реализации. Другими словами, интерфейс указывает, какие методы должен иметь класс, но не указывает, как эти методы реализованы.
В отличие от некоторых других языков, в Dart нет специального ключевого слова interface
. Вместо этого любой класс может действовать как интерфейс, а другие классы могут реализовать его, предоставляя требуемые методы.
// Interface for a Flyable object class Flyable { void fly() { print("Flying..."); } } // Bird class implements the Flyable interface class Bird implements Flyable { @override void fly() { print("Bird is flying"); } } // Airplane class implements the Flyable interface class Airplane implements Flyable { @override void fly() { print("Airplane is flying"); } } void main() { var bird = Bird(); var airplane = Airplane(); bird.fly(); // Output: "Bird is flying" airplane.fly(); // Output: "Airplane is flying" }
В этом примере мы определяем класс Flyable
с методом fly()
. Оба класса Bird
и Airplane
реализуют интерфейс Flyable
, предоставляя собственную реализацию метода fly()
. Класс Flyable
действует как интерфейс, определяя сигнатуру метода, которая должна присутствовать в реализующих его классах.
— — —
9. Конструкторы — создание объектов с жизнью
Конструкторы в Dart — это специальные методы, которые вызываются при создании объекта. Они позволяют инициализировать свойства объекта и выполнять операции настройки. Dart предоставляет конструкторы по умолчанию и именованные конструкторы для большей гибкости.
class Person { String name; int age; Person(this.name, this.age); // Default constructor Person.fromBirthYear(int birthYear) { name = "Unknown"; age = DateTime.now().year - birthYear; } // Named constructor void sayHello() { print("Hello, I'm $name!"); } } void main() { var person1 = Person("John", 30); // Using default constructor var person2 = Person.fromBirthYear(1995); // Using named constructor person1.sayHello(); // Output: "Hello, I'm John!" person2.sayHello(); // Output: "Hello, I'm Unknown!" }
— —
=› Статические члены и конструкторы — поведение всего класса
Статические элементы в Dart принадлежат классу, а не отдельным объектам. Они являются общими для всех экземпляров класса, и доступ к ним можно получить, используя имя класса. Статические конструкторы, также известные как фабричные конструкторы, — это специальные конструкторы, которые возвращают экземпляр класса на основе определенных условий.
class MathUtils { static const double pi = 3.14159; static double multiply(double a, double b) { return a * b; } factory MathUtils.fromRadius(double radius) { return MathUtils.multiply(radius, radius) * pi; } } void main() { var area = MathUtils.fromRadius(5); print("Area of circle with radius 5: $area"); // Output: "Area of circle with radius 5: 78.53975" }
В этом примере класс MathUtils
имеет статическую константу pi
и статический метод multiply()
. Кроме того, у него есть фабричный конструктор fromRadius()
, который вычисляет площадь круга на основе радиуса, используя статический метод multiply()
.
— —
=› Именованные конструкторы и заводские конструкторы — индивидуальное создание
В Dart у вас может быть несколько конструкторов для класса. Именованные конструкторы позволяют создавать специализированные конструкторы с произвольными именами. Конструкторы фабрик используются для возврата экземпляра класса на основе определенных условий.
class Person { String name; int age; Person(this.name, this.age); Person.fromBirthYear(int birthYear) { name = "Unknown"; age = DateTime.now().year - birthYear; } factory Person.createGuest() { return Person("Guest", 0); } } void main() { var person1 = Person("John", 30); var person2 = Person.fromBirthYear(1995); var guest = Person.createGuest(); print("Person 1: ${person1.name} (${person1.age})"); // Output: "Person 1: John (30)" print("Person 2: ${person2.name} (${person2.age})"); // Output: "Person 2: Unknown (28)" print("Guest: ${guest.name} (${guest.age})"); // Output: "Guest: Guest (0)" }
В этом примере класс Person
имеет несколько конструкторов. Конструктор по умолчанию Person(this.name, this.age)
инициализирует свойства name
и age
. Именованный конструктор Person.fromBirthYear(int birthYear)
создает объект Person
с заданным годом рождения. Конструктор фабрики Person.createGuest()
возвращает предварительно определенного человека Guest
.
— — —
10. Final и Const — неизменяемые переменные
В Dart вы можете сделать переменные неизменяемыми, используя ключевые слова final
и const
. Переменным final
можно присвоить значение только один раз, тогда как переменные const
являются константами времени компиляции и должны иметь постоянное значение во время компиляции.
void main() { final int age = 30; final String name = "John"; // age = 31; // Error: The final variable 'age' can only be set once. const double pi = 3.14159; // const double radius = getRadius(); // Error: Constant variables must be initialized with a constant value at compile-time. }
В примере age
и name
являются переменными final
, и после присвоения их значения нельзя изменить. С другой стороны, pi
— это переменная const
, и во время компиляции ей должно быть присвоено постоянное значение.
— — —
11. Mixins — повторно используемые блоки кода
Примеси — это способ повторного использования кода класса в нескольких иерархиях классов. Они позволяют расширять функциональность класса без использования наследования. В Dart вы можете создавать примеси, используя ключевое слово mixin
.
mixin Flying { void fly() { print("Flying..."); } } class Bird with Flying { void chirp() { print("Bird is chirping"); } } class Plane with Flying { void takeOff() { print("Plane is taking off"); } } void main() { var bird = Bird(); var plane = Plane(); bird.fly(); // Output: "Flying..." plane.fly(); // Output: "Flying..." plane.takeOff(); // Output: "Plane is taking off" }
В этом примере мы определяем миксин Flying
с методом fly()
. Оба класса Bird
и Plane
используют этот миксин, чтобы получить функциональность полета, демонстрируя повторное использование кода без наследования.
— — —
Заключение
Все эти темы также часто задаются в интервью. Вы можете использовать эту статью, чтобы подготовиться к следующему собеседованию. Я объяснил почти все темы вместе с примерами кодирования.
Если у вас есть какие-либо вопросы или вопросы, не стесняйтесь спрашивать меня. Следуйте предоставленным ссылкам и посетите мой профиль для получения дополнительных статей.
Linkedin: https://www.linkedin.com/in/ahsan-saeed-11a787183/
Подпишитесь, чтобы узнать больше о Ahsi Dev 💙