🧑‍🏫10. Инкапсуляция

Инкапсуляция — это принцип, который позволяет скрывать детали внутренней работы объектов и предоставлять только необходимый функционал другим частям программы. Это как коробка с кнопками на поверхности, где внешнему миру доступны лишь кнопки, но внутренние детали и механизмы коробки скрыты.

Зачем нужна инкапсуляция?

  1. Защищает данные: предотвращает случайные или неправильные изменения данных извне.

  2. Упрощает использование: делает объекты проще и понятнее для других частей программы.

  3. Сохраняет стабильность кода: позволяет менять внутреннюю реализацию без изменения внешнего интерфейса, что уменьшает риск поломок.

Как создавать инкапсуляцию в Dart

  1. Использование приватных переменных и методов — в Dart переменные и методы становятся приватными, если их имя начинается с _ (например, _balance).

  2. Методы для доступа к данным (геттеры и сеттеры): создаем публичные методы, чтобы безопасно читать и изменять приватные переменные.

Примеры с аналогиями

  1. Банковский счёт

    • Мы можем создать класс BankAccount, где баланс (_balance) скрыт от внешнего мира.

    • Для доступа есть методы deposit() и withdraw(), через которые можно добавлять или снимать деньги, проверяя корректность данных (например, не позволять снять больше, чем на счёте).

    class BankAccount {
      double _balance = 0; // приватная переменная
    
      void deposit(double amount) {
        if (amount > 0) {
          _balance += amount;
        }
      }
    
      void withdraw(double amount) {
        if (amount > 0 && amount <= _balance) {
          _balance -= amount;
        }
      }
    
      double get balance => _balance; // геттер для доступа к балансу
    }

    Аналогия: Представьте, что это сейф с дверью. Мы не видим, что находится внутри, но можем положить или забрать что-то, используя комбинацию.

  2. Телефон

    • В классе Phone можно скрыть сложные внутренние процессы, такие как проверка заряда батареи и соединение с сетью.

    • При этом мы оставляем доступ к публичным методам makeCall() или sendMessage(), которые работают независимо от внутреннего устройства телефона.

    class Phone {
      bool _isCharged = true; // приватная переменная
      
      void makeCall(String number) {
        if (_isCharged) {
          print('Звоним на $number');
        } else {
          print('Батарея разряжена');
        }
      }
    }
  3. Машина

    • В классе Car скрываем двигатель и топливную систему, которые не видны пользователю.

    • Публичный метод drive() позволяет пользователю ехать, но не требует знания о том, как работают двигатель или трансмиссия.

    class Car {
      bool _engineRunning = false; // приватное состояние двигателя
      
      void startEngine() {
        _engineRunning = true;
      }
    
      void drive() {
        if (_engineRunning) {
          print('Машина едет');
        } else {
          print('Сначала запустите двигатель');
        }
      }
    }

    Аналогия: Это как педали и руль, которые управляют машиной, не раскрывая детали того, как работает двигатель.

  4. Робот-пылесос

    • В классе RobotVacuum можно скрыть логику передвижения и сенсоры, чтобы пользователь мог просто использовать метод startCleaning().

    class RobotVacuum {
      bool _isBatteryCharged = true;
      
      void startCleaning() {
        if (_isBatteryCharged) {
          print('Начинаю уборку');
        } else {
          print('Нужно зарядить батарею');
        }
      }
    }

    Аналогия: Представьте, что робот-пылесос просто "убирается", и мы не видим, как он определяет, где грязь, а где чисто.

  5. Электронная книга

    • В классе EBookReader скрыты сложные процессы обработки и отображения текста.

    • У пользователя есть публичные методы openBook() и turnPage(), чтобы читать книгу без вмешательства в детали работы программы.

    class EBookReader {
      String _bookContent = "Содержание книги...";
    
      void openBook() {
        print("Книга открыта");
      }
    
      void turnPage() {
        print("Переворачиваем страницу");
      }
    }

    Аналогия: Электронная книга позволяет читать текст, не раскрывая, как устройство обрабатывает и отображает данные.

Резюме

Инкапсуляция помогает:

  • Скрывать внутренние данные и процессы, предоставляя только нужные методы.

  • Защищать данные и создавать единый интерфейс, которым легко пользоваться.

Инкапсуляция делает код более надежным и удобным для работы с ним в больших проектах, особенно при поддержке и изменениях.

Пример:

Создадим мини-приложение для управления профилем пользователя. В этом приложении будет инкапсулирована информация о пользователе, такая как имя, возраст и email. Мы будем использовать инкапсуляцию, чтобы скрыть эти данные от прямого доступа и предоставить методы для их безопасного изменения и получения.

Почему использовать инкапсуляцию?

Инкапсуляция позволяет:

  1. Защищать данные: Мы можем контролировать, как данные изменяются, например, проверять, чтобы возраст был положительным, а email соответствовал определённому формату.

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

  3. Сохранять целостность данных: Мы можем изменять внутреннюю реализацию без необходимости обновлять код, который использует эти объекты.

Структура приложения

  1. Класс UserProfile — инкапсулирует данные о пользователе.

  2. Публичные методы — позволяют безопасно изменять и получать данные.

  3. Экран профиля — демонстрирует использование класса UserProfile и взаимодействие с ним.

Код приложения

import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: ProfileScreen(),
    );
  }
}

// Класс UserProfile, который инкапсулирует данные о пользователе
class UserProfile {
  String _name; // Приватное поле для имени
  int _age; // Приватное поле для возраста
  String _email; // Приватное поле для email

  UserProfile(this._name, this._age, this._email);

  // Геттеры
  String get name => _name;
  int get age => _age;
  String get email => _email;

  // Сеттеры
  void setName(String name) {
    _name = name;
  }

  void setAge(int age) {
    if (age > 0) { // Проверка на положительный возраст
      _age = age;
    } else {
      print("Возраст должен быть положительным");
    }
  }

  void setEmail(String email) {
    if (RegExp(r"^[a-zA-Z0-9]+@[a-zA-Z0-9]+\.[a-zA-Z]+").hasMatch(email)) {
      _email = email;
    } else {
      print("Неверный формат email");
    }
  }
}

// Экран профиля пользователя
class ProfileScreen extends StatefulWidget {
  @override
  _ProfileScreenState createState() => _ProfileScreenState();
}

class _ProfileScreenState extends State<ProfileScreen> {
  UserProfile _userProfile = UserProfile("Иван", 30, "ivan@example.com");

  final TextEditingController _nameController = TextEditingController();
  final TextEditingController _ageController = TextEditingController();
  final TextEditingController _emailController = TextEditingController();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text("Профиль пользователя")),
      body: Padding(
        padding: EdgeInsets.all(16.0),
        child: Column(
          children: [
            Text("Имя: ${_userProfile.name}"),
            Text("Возраст: ${_userProfile.age}"),
            Text("Email: ${_userProfile.email}"),
            SizedBox(height: 20),
            TextField(
              controller: _nameController,
              decoration: InputDecoration(labelText: "Введите новое имя"),
            ),
            TextField(
              controller: _ageController,
              decoration: InputDecoration(labelText: "Введите новый возраст"),
              keyboardType: TextInputType.number,
            ),
            TextField(
              controller: _emailController,
              decoration: InputDecoration(labelText: "Введите новый email"),
            ),
            SizedBox(height: 20),
            ElevatedButton(
              onPressed: () {
                _userProfile.setName(_nameController.text);
                _userProfile.setAge(int.tryParse(_ageController.text) ?? 0);
                _userProfile.setEmail(_emailController.text);
                setState(() {}); // Обновляем состояние, чтобы показать изменения
              },
              child: Text("Обновить профиль"),
            ),
          ],
        ),
      ),
    );
  }
}

Объяснение кода

  1. Класс UserProfile:

    • Инкапсуляция данных: Приватные переменные _name, _age и _email скрывают внутренние данные пользователя. Это предотвращает случайное или неправильное изменение этих данных.

    • Геттеры и сеттеры: Публичные методы get name, get age и get email позволяют безопасно получать данные. Методы setName, setAge и setEmail предоставляют контролируемый доступ для изменения этих данных с проверкой правильности (например, положительный возраст и корректный email).

  2. Экран профиля (ProfileScreen):

    • Создаёт экземпляр UserProfile и предоставляет текстовые поля для ввода нового имени, возраста и email.

    • Кнопка "Обновить профиль" вызывает сеттеры, чтобы обновить данные пользователя. Если введенные данные некорректны, это обрабатывается в сеттерах, а пользователю не нужно беспокоиться о деталях проверки.

Аналогия

Представьте, что класс UserProfile — это прачечная. Внутри прачечной находятся стиральные машины и сушилки (приватные данные), и вы не можете просто зайти и изменить их настройки. Вместо этого есть администратор (публичные методы), который проверяет ваши запросы и управляет процессом стирки. Вы можете сказать администратору, какую одежду вам нужно постирать, но не можете напрямую изменять оборудование.

Заключение

Инкапсуляция позволяет защитить данные и контролировать их изменение, что делает код более безопасным и понятным. Мы можем менять внутренние детали класса, не нарушая взаимодействия с ним, что удобно для поддержки и расширения приложения в будущем.

Last updated