Одной из задач разработки программного обеспечения является создание многократно используемых компонентов, которые могут быть легко интегрированы в более крупную систему. Компонент — это часть кода, которая выполняет определенную функцию и может использоваться другими частями системы. Например, компонентом может быть кнопка, текст, чекбокс или изображение. Система может состоять из множества компонентов, которые взаимодействуют друг с другом, обеспечивая требуемую функциональность.
Однако добавление нового компонента в существующую систему не всегда просто. Это может потребовать изменения кода системы или других компонентов для размещения нового компонента.
Поэтому предполагается реализовать компоненты таким образом, чтобы они требовали минимальных изменений в будущем, когда мы захотим использовать их повторно.
В этой статье я расскажу о решении, позволяющем писать составные функции, которые в большей степени пригодны для повторного использования.
Проектирование
Атомарное проектирование (Atomic design) — ценная техника для создания многократно используемых составных компонентов.
Атомарный дизайн — это методология создания и поддержки согласованных, многократно используемых и масштабируемых компонентов пользовательского интерфейса. В ее основе лежит идея разбиения элементов пользовательского интерфейса на пять уровней абстракции: атомы, молекулы, организмы, шаблоны и страницы.
Чтобы использовать весь потенциал этих элементов, необходимо придерживаться правильного подхода к их реализации.
Ошибка
Ниже я покажу вам распространенную ошибку, с которой могут столкнуться разработчики при использовании Jetpack Compose.
🔴 Это неправильный способ создания пользовательского текстового компонента в Compose, поскольку он не имеет всех атрибутов текста, таких как цвет, модификатор, fontFamily и т.д.
// This is a wrong implementation for Compose components @Composable fun HeaderText( text: String, ) { Text( text = text, style = MaterialTheme.typography.headlineLarge, maxLines = 1, ) } // This is an usage @Composable fun Usage() { HeaderText( text = "Products", ) }
Основная проблема заключается в том, что если нам нужно изменить цвет HeaderText()
, то мы должны изменить его атрибуты.
@Composable fun HeaderText( text: String, color: Color, ) { Text( text = text, style = MaterialTheme.typography.headlineLarge, color = color, maxLines = 1, ) }
Здесь можно увидеть изменения компонентов.
Решение
Используя паттерн делегирования, мы можем уменьшить количество изменений, которые необходимо внести в Compose компонент.
Делегирование — это просто передача обязанностей кому-то/чему-то другому. Делегирование может быть альтернативой наследованию.
Это означает использование свойств компонента в другом компоненте и передачу ему функциональности.
Преимущества:
- Минимальные изменения для добавления свойства в Compose компонент
- Разделение Compose компонентов и повышение возможности повторного использования
- Отсутствует необходимость написания менее используемых компонентов
Используя делегирование, мы можем унаследовать все поведение HeaderText()
от Text()
. Для этого мы передаем все параметры Text()
в HeaderText()
, присваивая им значения по умолчанию.
Кроме того, мы можем не передавать некоторые параметры Text()
, такие как textAlign
, softWrap
, style
, и определять их в HeaderText()
в зависимости от наших потребностей.
Мы завершили реализацию компонента HeaderText()
, использующего правило Делегирования.
@NonRestartableComposable @Composable fun HeaderText( text: String, modifier: Modifier = Modifier, color: Color = Color.Unspecified, fontSize: TextUnit = TextUnit.Unspecified, fontStyle: FontStyle? = null, fontWeight: FontWeight? = null, fontFamily: FontFamily? = null, letterSpacing: TextUnit = TextUnit.Unspecified, textDecoration: TextDecoration? = null, lineHeight: TextUnit = TextUnit.Unspecified, overflow: TextOverflow = TextOverflow.Clip, maxLines: Int = Int.MAX_VALUE, onTextLayout: (TextLayoutResult) -> Unit = {}, ) { Text( text = text, modifier = modifier, color = color, fontSize = fontSize, fontStyle = fontStyle, fontWeight = fontWeight, fontFamily = fontFamily, letterSpacing = letterSpacing, textDecoration = textDecoration, textAlign = TextAlign.Center, lineHeight = lineHeight, overflow = overflow, softWrap = false, maxLines = maxLines, onTextLayout = onTextLayout, style = MaterialTheme.typography.headlineLarge, ) }
Не забудьте добавить @NonRestartableComposable
к функции HeaderText()
.
Это предотвращает пропуск или перезапуск функции средой выполнения Compose. Это означает, что функция всегда будет выполняться при рекомпозиции ее родителя, и на нее не будут влиять изменения ее параметров или состояния.
Счетчик рекомпозиций для @NonRestartableComposable
:
Вернемся к первому примеру и разберем его более подробно.
Без делегирования 😥:
С делегированием:
Используя этот шаблон проектирования, разработчики могут создать набор для пользовательского интерфейса, содержащий множество составных компонентов, требующих минимальных изменений.
Заключение
В этой статье я рассказал, как использовать паттерн делегирования и паттерн атомарного проектирования для создания многократно используемых составных компонентов в Jetpack Compose. Применяя эти техники, мы можем избежать модификации кода компонентов или системы при добавлении нового свойства в компонент. Кроме того, мы можем разделить компоненты и способствовать их повторному использованию. Таким образом, мы можем повысить качество и удобство сопровождения разрабатываемого кода. Надеюсь, что эта статья была для вас полезной и вы узнали что-то новое.
Спасибо за прочтение.