Какие знаешь принципы SOLID?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Какие знаешь принципы SOLID?
SOLID — это пять фундаментальных принципов объектно-ориентированного программирования, которые помогают создавать более гибкий, масштабируемый и поддерживаемый код. Эти принципы особенно важны в Flutter при разработке больших приложений.
1. S — Single Responsibility Principle (Принцип единственной ответственности)
Класс должен иметь только одну причину для изменения. Каждый класс отвечает за одно.
Плохо:
class UserManager {
// Слишком много ответственности
void createUser(String name, String email) {
// Валидация
if (email.isEmpty) throw Exception("Email required");
// Работа с БД
database.insert("users", {"name": name, "email": email});
// Отправка email
emailService.send(email, "Welcome!");
// Логирование
logger.info("User $name created");
}
}
Хорошо:
// Класс для валидации
class UserValidator {
bool isValidEmail(String email) => email.contains("@");
}
// Класс для работы с БД
class UserRepository {
void save(User user) {
database.insert("users", user.toMap());
}
}
// Класс для отправки уведомлений
class EmailService {
void sendWelcomeEmail(String email) {
sendEmail(email, "Welcome!");
}
}
// Класс для логирования
class Logger {
void logUserCreated(String name) {
print("User $name created");
}
}
// Оркестратор - использует все компоненты
class UserService {
final UserValidator validator;
final UserRepository repository;
final EmailService emailService;
final Logger logger;
UserService(this.validator, this.repository, this.emailService, this.logger);
void createUser(String name, String email) {
if (!validator.isValidEmail(email)) {
throw Exception("Invalid email");
}
var user = User(name, email);
repository.save(user);
emailService.sendWelcomeEmail(email);
logger.logUserCreated(name);
}
}
2. O — Open/Closed Principle (Принцип открытости/закрытости)
Классы открыты для расширения, но закрыты для модификации.
Плохо (модифицируем класс):
class PaymentProcessor {
void processPayment(String type, double amount) {
if (type == "credit_card") {
// Логика для кредитной карты
} else if (type == "paypal") {
// Логика для PayPal
} else if (type == "stripe") {
// Логика для Stripe
// При добавлении нового способа меняем класс!
}
}
}
Хорошо (расширяем через наследование):
abstract class PaymentMethod {
Future<bool> process(double amount);
}
class CreditCardPayment extends PaymentMethod {
@override
Future<bool> process(double amount) async {
// Логика для кредитной карты
return true;
}
}
class PayPalPayment extends PaymentMethod {
@override
Future<bool> process(double amount) async {
// Логика для PayPal
return true;
}
}
class StripePayment extends PaymentMethod {
@override
Future<bool> process(double amount) async {
// Логика для Stripe
return true;
}
}
// Новый способ - просто добавляем класс, ничего не меняя!
class ApplePayment extends PaymentMethod {
@override
Future<bool> process(double amount) async {
return true;
}
}
class PaymentProcessor {
Future<bool> processPayment(PaymentMethod method, double amount) {
return method.process(amount);
}
}
3. L — Liskov Substitution Principle (Принцип подстановки Лисков)
Объекты подклассов должны корректно заменять объекты базовых классов.
Плохо (нарушение контракта):
class Bird {
void fly() => print("Flying");
}
class Penguin extends Bird {
@override
void fly() {
throw UnimplementedError("Penguins can't fly!"); // Нарушает контракт!
}
}
void makeBirdFly(Bird bird) {
bird.fly(); // Крах при передаче пингвина!
}
Хорошо (правильная иерархия):
abstract class Bird {
void move();
}
class FlyingBird extends Bird {
@override
void move() => print("Flying");
}
class WalkingBird extends Bird {
@override
void move() => print("Walking");
}
class Penguin extends WalkingBird {
@override
void move() => print("Walking and swimming");
}
void moveBird(Bird bird) {
bird.move(); // Всегда работает корректно
}
4. I — Interface Segregation Principle (Принцип разделения интерфейсов)
Клиенты не должны зависеть от интерфейсов, которые они не используют.
Плохо (большой интерфейс):
abstract class Worker {
void work();
void eat();
void sleep();
void payTaxes();
}
class Robot implements Worker {
@override
void work() => print("Working");
@override
void eat() => throw UnimplementedError(); // Robot не ест!
@override
void sleep() => throw UnimplementedError(); // Robot не спит!
@override
void payTaxes() => throw UnimplementedError(); // Robot не платит налоги!
}
Хорошо (раздельные интерфейсы):
abstract class Workable {
void work();
}
abstract class Eatable {
void eat();
}
abstract class Sleepable {
void sleep();
}
class Human implements Workable, Eatable, Sleepable {
@override
void work() => print("Working");
@override
void eat() => print("Eating");
@override
void sleep() => print("Sleeping");
}
class Robot implements Workable {
@override
void work() => print("Working");
// Только необходимый интерфейс!
}
5. D — Dependency Inversion Principle (Принцип инверсии зависимостей)
Высокоуровневые модули не должны зависеть от низкоуровневых. Оба должны зависеть от абстракций.
Плохо (прямые зависимости):
class EmailService {
void sendEmail(String email, String message) {
// Отправляем по SMTP
print("Sending email to $email");
}
}
class UserNotifier {
final EmailService emailService = EmailService(); // Прямая зависимость!
void notifyUser(String email) {
emailService.sendEmail(email, "Hello!");
}
}
Хорошо (инверсия через интерфейс):
abstract class NotificationService {
Future<void> send(String recipient, String message);
}
class EmailService implements NotificationService {
@override
Future<void> send(String email, String message) async {
print("Email to $email: $message");
}
}
class SMSService implements NotificationService {
@override
Future<void> send(String phone, String message) async {
print("SMS to $phone: $message");
}
}
class UserNotifier {
final NotificationService notificationService;
// Инжекция зависимости через конструктор
UserNotifier(this.notificationService);
Future<void> notifyUser(String recipient) async {
await notificationService.send(recipient, "Hello!");
}
}
// Использование
void main() {
// Легко менять реализацию без изменения UserNotifier
var notifier1 = UserNotifier(EmailService());
var notifier2 = UserNotifier(SMSService());
}
Практический пример в Flutter
// Абстракция
abstract class UserRepository {
Future<List<User>> getUsers();
Future<void> saveUser(User user);
}
// Реализация
class MockUserRepository implements UserRepository {
@override
Future<List<User>> getUsers() async => [User("John"), User("Jane")];
@override
Future<void> saveUser(User user) async {}
}
class ApiUserRepository implements UserRepository {
@override
Future<List<User>> getUsers() async {
final response = await http.get(Uri.parse("api/users"));
return parseUsers(response.body);
}
@override
Future<void> saveUser(User user) async {
await http.post(Uri.parse("api/users"), body: user.toJson());
}
}
//ViewModel/provider
class UserViewModel extends ChangeNotifier {
final UserRepository repository;
List<User> users = [];
UserViewModel(this.repository);
Future<void> loadUsers() async {
users = await repository.getUsers();
notifyListeners();
}
}
// Widget
class UserListWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
// Инжекция зависимости
return Provider(
create: (_) => UserViewModel(ApiUserRepository()),
child: Consumer<UserViewModel>(
builder: (context, viewModel, _) {
return ListView(
children: viewModel.users
.map((user) => ListTile(title: Text(user.name)))
.toList(),
);
},
),
);
}
}
Таблица сравнения
Принцип Суть Пример
─────────────────────────────────────────────────────
S Одна ответственность Отдельные классы для валидации, БД, уведомлений
O Расширение без модификации Использование абстрактных классов и наследования
L Корректная подстановка Правильная иерархия классов
I Маленькие специфичные интерфейсы Несколько интерфейсов вместо одного большого
D Зависимость от абстракций Инжекция зависимостей через конструктор
Когда применять SOLID
- Для больших приложений — критично важно
- В команде — облегчает код review и maintenance
- Для тестирования — SOLID код легче тестировать
- Для масштабирования — новые разработчики быстрее разбираются
Когда НЕ переусложнять
- Маленькие скрипты — overkill
- Прототипирование — сначала работает, потом рефакторит
- Простые компоненты — если один класс делает всё, это OK
Принципы SOLID — это инвестиция в будущее проекта. Code написанный с их учётом значительно проще поддерживать, тестировать и расширять.