← Назад к вопросам

Что сделать чтобы связность компонентов была низкой?

2.0 Middle🔥 111 комментариев
#Архитектура Flutter#ООП и паттерны

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI26 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Как обеспечить низкую связность компонентов в Flutter

Что такое связность (Coupling)

Связность — это степень зависимости одного компонента от другого. Высокая связность означает, что изменение одного компонента требует изменения другого. Это делает код хрупким, сложным в тестировании и трудным для повторного использования.

Принцип: Зависимости от интерфейсов, не от реализации

Плохо - высокая связность

class UserRepository {
  Future<List<User>> getUsers() async {
    final response = await http.get(
      Uri.parse('https://api.example.com/users')
    );
    if (response.statusCode == 200) {
      return jsonDecode(response.body);
    }
    throw Exception('Failed');
  }
}

class UserBloc extends Bloc<UserEvent, UserState> {
  final UserRepository repository;
  
  UserBloc(this.repository) : super(UserInitial()) {
    on<FetchUsers>((event, emit) async {
      emit(UserLoading());
      try {
        final users = await repository.getUsers();
        emit(UserLoaded(users));
      } catch (e) {
        emit(UserError(e.toString()));
      }
    });
  }
}

void main() {
  // BLoC жестко завязан на конкретную реализацию UserRepository
  // Если нужно изменить способ получения данных, нужно менять BLoC
  runApp(BlocProvider(
    create: (_) => UserBloc(UserRepository()),
    child: MyApp(),
  ));
}

Хорошо - низкая связность

// Абстрактный интерфейс
abstract class IUserRepository {
  Future<List<User>> getUsers();
}

// Конкретная реализация
class HttpUserRepository implements IUserRepository {
  @override
  Future<List<User>> getUsers() async {
    final response = await http.get(
      Uri.parse('https://api.example.com/users')
    );
    if (response.statusCode == 200) {
      return (jsonDecode(response.body) as List)
          .map((json) => User.fromJson(json))
          .toList();
    }
    throw Exception('Failed');
  }
}

// Альтернативная реализация для тестирования
class MockUserRepository implements IUserRepository {
  @override
  Future<List<User>> getUsers() async {
    return [
      User(id: 1, name: 'Alice'),
      User(id: 2, name: 'Bob'),
    ];
  }
}

class UserBloc extends Bloc<UserEvent, UserState> {
  final IUserRepository repository; // Зависит от интерфейса!
  
  UserBloc(this.repository) : super(UserInitial()) {
    on<FetchUsers>((event, emit) async {
      emit(UserLoading());
      try {
        final users = await repository.getUsers();
        emit(UserLoaded(users));
      } catch (e) {
        emit(UserError(e.toString()));
      }
    });
  }
}

void main() {
  // Легко переключаться между реализациями
  runApp(BlocProvider(
    create: (_) => UserBloc(HttpUserRepository()),
    child: MyApp(),
  ));
}

void testUserBloc() {
  test('UserBloc loads users', () async {
    final bloc = UserBloc(MockUserRepository());
    bloc.add(FetchUsers());
    
    await expectLater(
      bloc.stream,
      emitsInOrder([
        UserLoading(),
        isA<UserLoaded>(),
      ]),
    );
  });
}

Инъекция зависимостей (Dependency Injection)

Плохо - зависимости создаются внутри

class OrderService {
  late final PaymentProcessor _paymentProcessor = PaymentProcessor();
  late final NotificationService _notificationService = NotificationService();
  late final DatabaseService _db = DatabaseService();
  
  Future<void> processOrder(Order order) async {
    await _paymentProcessor.charge(order.total);
    await _db.saveOrder(order);
    await _notificationService.sendConfirmation(order.userId);
  }
}

Хорошо - зависимости внедряются

abstract class IPaymentProcessor {
  Future<void> charge(double amount);
}

abstract class INotificationService {
  Future<void> sendConfirmation(String userId);
}

abstract class IOrderDatabase {
  Future<void> saveOrder(Order order);
}

class OrderService {
  final IPaymentProcessor _paymentProcessor;
  final INotificationService _notificationService;
  final IOrderDatabase _db;
  
  OrderService(
    this._paymentProcessor,
    this._notificationService,
    this._db,
  );
  
  Future<void> processOrder(Order order) async {
    await _paymentProcessor.charge(order.total);
    await _db.saveOrder(order);
    await _notificationService.sendConfirmation(order.userId);
  }
}

void main() {
  final service = OrderService(
    StripePaymentProcessor(),
    FirebaseNotificationService(),
    PostgresOrderDatabase(),
  );
}

Изоляция слоев (Layered Architecture)

Presentation (UI Widgets, Screens)
    ↓ зависит от
Application (BLoCs, Controllers)
    ↓ зависит от
Domain (Entities, UseCases, Repositories interfaces)
    ↓ зависит от
Infrastructure (API, Database, specific implementations)

Правило: зависимости только внутрь, никогда наружу!

// Domain - должен быть независимым
class User {
  final int id;
  final String name;
  User(this.id, this.name);
}

abstract class IUserRepository {
  Future<User?> getUserById(int id);
}

class GetUserUseCase {
  final IUserRepository repository;
  GetUserUseCase(this.repository);
  
  Future<User?> call(int id) => repository.getUserById(id);
}

// Application - зависит от Domain
class UserDetailBloc extends Bloc<UserDetailEvent, UserDetailState> {
  final GetUserUseCase getUserUseCase;
  
  UserDetailBloc(this.getUserUseCase) : super(UserDetailInitial()) {
    on<LoadUser>(_onLoadUser);
  }
  
  Future<void> _onLoadUser(
    LoadUser event,
    Emitter<UserDetailState> emit,
  ) async {
    final user = await getUserUseCase(event.userId);
    // ...
  }
}

// Infrastructure - реализует Domain интерфейсы
class ApiUserRepository implements IUserRepository {
  @override
  Future<User?> getUserById(int id) async {
    // HTTP запрос
  }
}

// Presentation - зависит от Application
class UserDetailScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return BlocBuilder<UserDetailBloc, UserDetailState>(
      builder: (context, state) {
        // UI
      },
    );
  }
}

Composition over Inheritance

Плохо - глубокая иерархия наследования

class Widget {}
class StatefulWidget extends Widget {}
class FormWidget extends StatefulWidget {}
class LoginFormWidget extends FormWidget {}

Хорошо - композиция

class LoginForm extends StatefulWidget {
  final FormValidator validator;
  final FormSubmitter submitter;
  final FormStyler styler;
  
  LoginForm({
    required this.validator,
    required this.submitter,
    required this.styler,
  });
}

Event-driven и Messaging

Вместо прямого вызова методов используй события:

Плохо - прямая связь

class CheckoutButton extends StatelessWidget {
  final OrderService orderService;
  final PaymentProcessor paymentProcessor;
  final NotificationService notificationService;
  
  void onPressed() {
    orderService.checkout();
    paymentProcessor.process();
    notificationService.notify();
  }
}

Хорошо - через события

class CheckoutButton extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ElevatedButton(
      onPressed: () {
        context.read<CheckoutBloc>().add(CheckoutStarted());
      },
      child: Text('Checkout'),
    );
  }
}

class CheckoutBloc extends Bloc<CheckoutEvent, CheckoutState> {
  final OrderService orderService;
  
  CheckoutBloc(this.orderService) : super(CheckoutInitial()) {
    on<CheckoutStarted>(_onCheckoutStarted);
  }
  
  Future<void> _onCheckoutStarted(
    CheckoutStarted event,
    Emitter<CheckoutState> emit,
  ) async {
    // Все побочные эффекты здесь
  }
}

Service Locator (GetIt)

Для управления зависимостями на уровне приложения:

final getIt = GetIt.instance;

void setupServiceLocator() {
  // Domain
  getIt.registerSingleton<IUserRepository>(
    ApiUserRepository(),
  );
  
  // Application
  getIt.registerSingleton<GetUserUseCase>(
    GetUserUseCase(getIt<IUserRepository>()),
  );
  
  getIt.registerFactory<UserDetailBloc>(
    () => UserDetailBloc(getIt<GetUserUseCase>()),
  );
}

void main() {
  setupServiceLocator();
  runApp(
    BlocProvider(
      create: (_) => getIt<UserDetailBloc>(),
      child: MyApp(),
    ),
  );
}

Вывод

Для низкой связности:

  1. Зависи от абстракций (интерфейсов), не от конкретных классов
  2. Внедряй зависимости через конструктор
  3. Разделяй на слои, зависимости только внутрь
  4. Предпочитай композицию наследованию
  5. Используй события вместо прямых вызовов
  6. Тестируй с mock-объектами

Это делает код более гибким, тестируемым и готовым к изменениям.