Концепции объектно-ориентированного программирования (ООП) в 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 💙

УДАЧНОГО КОДИРОВАНИЯ!