Функциональная декомпозиция
Вы уже знаете, как создавать простые функции в Kotlin. Это очень полезный навык, который сокращает код, улучшает его читаемость и позволяет повторно использовать ранее написанные части кода.
По мере усложнения ваших задач программирования усложняются и ваши функции. Хотя вы можете создать сложную программу, заключенную в одну сплошную функцию или даже в main
функцию, лучше разделить программу на более конкретные части, которые легко читать и понимать. Подход к разделению сложной программы на ряд функций называется функциональной декомпозицией.
В этом разделе мы увидим, как разложить решение конкретной задачи на отдельные функции.
Решение сложных задач
Идея разбиения большой проблемы на несколько подзадач вполне интуитивно понятна. Если вы хотите приготовить пиццу, вы не просто помещаете все ингредиенты в духовку: вместо этого вы разбиваете процесс на отдельные задачи — от приготовления теста до фактического приготовления пищи. Функциональная декомпозиция — это не приготовление пиццы, но она основана на том же принципе разбиения большой проблемы на более мелкие части,
Давайте рассмотрим пример. Подумайте о программе, которая имитирует приложение "Умный дом". Это приложение используется для управления домашними устройствами, к которым можно получить удаленный доступ: беспроводными акустическими системами, освещением, домашней безопасностью, дверными замками и даже роботами. Представьте, что у нас есть простое приложение для умного дома, которое может выполнять три действия: включать или выключать музыку, включать и выключать свет и управлять дверным замком. Давайте рассмотрим эти действия как части нашей компьютерной программы.
Общий алгоритм работы приложения "Умный дом" можно разбить на следующие этапы:
Проанализировать входные данные (введённый пароль)
Проверить правильность пароля
Спросить пользователя, что он хочет сделать
Если действие поддерживается, выполнить его
Представьте, что вы завернули эту программу в код, но без единой дополнительной функции. Вот как выглядела бы его структура:
fun main() {
// ...
val password = "76543210"
var speakersState: String
var lampState: String
var doorState: String
// ...
// reading the password
println("Enter password: ")
val passwordInput = readln()
// checking if the password is correct
if (passwordInput != password) {
println("Incorrect password!")
} else {
// asking the user what they want to do
println("Choose the object: 1 – speakers, 2 – lamp, 3 – door")
val action = readln()
when (action) {
"1" -> {
// asking the user about the speakers
when (speakersState) {
"on" -> {
// ...
}
"off" -> {
// ...
}
else -> {
// ...
}
}
}
"2" -> {
// asking the user about the lights...
}
"3" -> {
// asking the user about the door...
}
else -> {
// ...
}
}
}
}
Хотя вы видите усеченную версию реальной программы, код все равно выглядит перегруженным. В то же время это прекрасно подходит для нашей проблемы, и мы могли бы оставить все как есть. Однако позже мы можем захотеть настроить его в соответствии с нашими потребностями или расширить его функциональность.
Что, если мы хотим, чтобы этот код работал для нескольких пользователей? Или расширить количество действий и сделать их более сложными? Некоторые части кода все равно будут использоваться, а некоторые из них, вероятно, будут удалены. Чтобы сделать этот код менее специфичным и более гибким, мы можем использовать функциональную декомпозицию.
Разложение программы на функции
Функциональная декомпозиция — это процесс разложения задачи на несколько функций. Каждая функция выполняет определенную задачу, которую мы можем выполнять подряд, чтобы получить нужные нам результаты. Рассматривая проблему, нам необходимо определить действия, которые будут повторяться несколько раз или, как вариант, выполняться отдельно. Именно так мы получаем желаемые функции, которые легче читать, понимать, повторно использовать, тестировать и отлаживать.
Давайте еще раз посмотрим на наше приложение для умного дома и выясним, какие шаги можно превратить в отдельные функции. Прежде всего, мы можем разделить действия пользователя и создать соответствующие функции: одну для управления музыкой, другую для включения и выключения света, а третью для управления дверным замком.
Взгляните на функцию controlMusic()
, которая управляет музыкой. Функции controlLight()
и controlDoor()
следуют одному и тому же алгоритму.
// turns the music on and off
fun controlMusic() {
println("on/off?")
val tumbler = readln()
when (tumbler) {
"on" -> println("The music is on")
"off" -> println("The music is off")
else -> println("Invalid operation")
}
}
Эти управляющие функции выполняют основные действия, которые предоставляет наше приложение. Действия значительно упрощены и используются только для иллюстрации процесса пересмотра функциональности.
Ещё одна функция, которая может быть разделена — это проверка паролей:
// verifies the password and gives the access to Smart home actions if the password is correct
fun accessSmartHome() {
val password = "76543210"
print("Enter password: ")
val passwordInput = readln()
if (passwordInput == password)
chooseAction()
else
println("Incorrect password!")
}
Мы также создали функцию chooseAction()
с меню, в котором пользователь может выбрать действие. Эта функция запрашивает пользователя, какое действие он хочет выполнить, и передает управление соответствующей функции.
Наконец, мы можем запустить нашу разложенную программу в main
функции, которая вызывается после запуска нашей программы:
fun main() {
accessSmartHome()
}
Эта функция вызывает accessSmartHome
, который просит пользователя ввести пароль и, если он правильный, позволяет ему управлять умным домом.
Добавление новых возможностей
Теперь, если мы хотим добавить еще одно действие, все, что нам нужно сделать, это определить соответствующую функцию. Например, у нас появилось новое умное устройство — электрический чайник. Мы создаем функцию, которая включает и выключает его. Чтобы получить доступ к новой функции, нам нужно изменить функцию chooseAction()
, добавив новое доступное значение действия:
// controls electric kettle
fun controlKettle() {
// ...
}
// main menu for choosing the action
fun chooseAction() {
// ...
// adding new action 4
println("Choose the object: 1 – speakers, 2 – lamp, 3 – door, 4 – kettle")
// ...
"4" -> controlKettle()
// ...
}
Как вы можете видеть, теперь у нас есть реально функционирующая программа, которая не развалится, если мы решим ее немного изменить. Мы можем легко протестировать отдельные компоненты, поскольку они определены в отдельных функциях. Это также облегчает поддержку программы в будущем.
Идиомы
Вы уже знаете, что if
и when
могут быть выражениями. Таким образом, одним из очевидных способов упрощения вашего кода является использование их форм выражения. Мы предлагаем вам использовать эту форму в простых функциях:
fun transform(color: String): Int { // you can miss one of the returns
when (color) {
"Red" -> return 0
"Green" -> return 1
"Blue" -> return 2
else -> return -1
}
fun transform(color: String): Int { // you can accidentally change the variable `colorNumber`
var colorNumber = -1
when (color) {
"Red" -> colorNumber = 0
"Green" -> colorNumber = 1
"Blue" -> colorNumber = 2
}
return colorNumber
}
fun transform(color: String): Int { // nice and concise code
return when (color) {
"Red" -> 0
"Green" -> 1
"Blue" -> 2
else -> -1
}
}
Кроме того, вы можете использовать эту идиому в функциях с одним выражением:
fun transform(color: String) = when (color) {
"Red" -> 0
"Green" -> 1
"Blue" -> 2
else -> -1
}
Существует также краткая форма выражений if
. Попробуйте написать короткие функции таким образом:
fun max(a: Int, b: Int) = if (a > b) a else b
Как вы можете видеть, выражения when
проясняют ситуацию и помогают вам не потерять свои ветви. Попробуйте использовать эту идиому при написании кода.
Заключение
Функциональная декомпозиция — чрезвычайно полезный подход к программированию, который поможет вам:
Структурируйте код, сделайте его более читабельным и понятным
Легко модифицируйте код
Повторно используйте функции и сделайте код более лаконичным
Сделайте процесс тестирования более удобным, протестировав компоненты по отдельности
Конечно, функциональная декомпозиция не является универсальным подходом для всего, но она может помочь вам создавать аккуратные и понятные программы, с которыми легко работать.
Last updated