← Назад к вопросам
Что сделать чтобы связность компонентов была низкой?
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(),
),
);
}
Вывод
Для низкой связности:
- Зависи от абстракций (интерфейсов), не от конкретных классов
- Внедряй зависимости через конструктор
- Разделяй на слои, зависимости только внутрь
- Предпочитай композицию наследованию
- Используй события вместо прямых вызовов
- Тестируй с mock-объектами
Это делает код более гибким, тестируемым и готовым к изменениям.