JavaRush /Курсы /Swift SELF /Методы и computed properties внутри enum

Методы и computed properties внутри enum

Swift SELF
25 уровень , 4 лекция
Открыта

1. Иногда нужно спрятать логику внутрь enum

Когда начинаешь писать программы, очень легко попасть в стиль «всё в одном месте»: прочитал ввод, сделал огромный switch, в каждой ветке что-то распечатал, где-то изменил переменную, где-то ещё раз проверил условие… Работает? Работает. Но через пару дней такой код начинает напоминать ящик с проводами: нужный провод вроде есть, но попробуй найди — и желательно не ударься током.

enum хорош тем, что он не просто ограничивает варианты, он ещё и подсказывает архитектуру: раз у нас есть варианты, значит, у каждого варианта может быть свой смысл. И очень удобно хранить этот смысл рядом с вариантом. Тогда вместо десяти switch-ей по проекту у вас будет один — внутри enum. И если вы добавите новый case, компилятор «подсветит» места, где нужно обновить логику.

Представьте, что enum — это не «табличка с надписями», а «переключатель режимов» в устройстве. Переключатель не только перечисляет режимы, он определяет, что каждый режим делает: как выглядит, как называется, что разрешает, какой следующий режим и так далее.

Computed property в enum: значение «на лету»

Computed property (вычисляемое свойство) — это свойство, у которого нет отдельного места в памяти «под значение». Вместо этого при обращении к нему выполняется код, который вычисляет результат. По сути это «функция без скобочек», но с очень важной договорённостью: свойство должно выглядеть как простое чтение данных и не удивлять пользователя побочными эффектами.

Синтаксис вы уже видели в struct:


var name: String {
    return ...
}

В enum всё точно так же. Самый частый паттерн — вычислять что-то в зависимости от текущего case. Например, мы делаем маленькое консольное приложение-библиотеку (мы его будем постепенно «умнеть» дальше по курсу), где пользователь вводит команду. Команда — это enum, а у команды есть «человекочитаемое имя».

import Foundation

enum LibraryCommand {
    case help, list, add, exit

    var title: String {
        switch self {
        case .help: return "Справка"
        case .list: return "Список книг"
        case .add:  return "Добавить книгу"
        case .exit: return "Выход"
        }
    }
}

Обратите внимание на приятный эффект: снаружи вам не нужно помнить, как именно «красиво назвать» команду. Вы пишете cmd.title и получаете аккуратный текст. А самое главное — если завтра появится .remove, компилятор заставит вас обновить title, потому что switch обязан быть исчерпывающим.

2. Главный приём: switch self как таблица соответствий

Когда вы впервые видите switch self, он может выглядеть немного странно: «Я что, делаю switch по самому себе?». Да. Именно. self внутри enum — это текущее значение (один из case-ов). И это превращает computed property в очень понятную «табличку правил»: case → результат.

Давайте сделаем свойство, которое возвращает строку подсказки, как пользоваться командой. В нашем учебном CLI это будет мини-справка.

import Foundation

enum LibraryCommand {
    case help, list, add, exit

    var usage: String {
        switch self {
        case .help: return "help — показать справку"
        case .list: return "list — вывести список книг"
        case .add:  return "add — добавить книгу (пока без параметров)"
        case .exit: return "exit — выйти из программы"
        }
    }
}

Почему это удобно именно как computed property, а не как отдельная функция usage(for:) где-то снаружи? Потому что тогда usage «принадлежит» команде. Команда сама знает, как она выглядит для пользователя.

Здесь важно почувствовать стиль Swift: «данные и поведение рядом». Мы уже делали это в struct, а enum ничуть не хуже — он такой же полноценный тип.

4. Методы в enum: когда свойств уже тесно

Computed property хорош, когда вы просто возвращаете значение и вам не нужны аргументы. Но как только появляется необходимость принять параметр (например, состояние приложения), сделать действие или вернуть результат вычисления на основе входных данных — вам нужен метод.

Метод внутри enum объявляется точно так же, как метод в struct:


func имя(параметры) -> Возврат { ... }

Вернёмся к нашему LibraryCommand. Представим, что мы хотим выполнить команду и получить текст, который нужно вывести пользователю. Состояние программы пока упрощённое: список книг и флаг, что нужно ли продолжать цикл.

Сначала объявим состояние:

import Foundation

struct LibraryState {
    var books: [String] = []
    var isRunning: Bool = true
}

Теперь добавим метод run(state:) внутрь enum:

import Foundation

enum LibraryCommand {
    case help, list, add, exit

    func run(state: inout LibraryState) -> String {
        switch self {
        case .help:
            return "Доступные команды: help, list, add, exit"
        case .list:
            return state.books.isEmpty ? "Список пуст." : state.books.joined(separator: ", ")
        case .add:
            state.books.append("Новая книга")
            return "Добавлено: Новая книга"
        case .exit:
            state.isRunning = false
            return "Пока!"
        }
    }
}

Здесь происходит несколько важных вещей, которые стоит проговорить вслух.

Во-первых, enum не просто «определяет варианты». Он стал «объектом поведения»: команда сама умеет выполняться.

Во-вторых, метод принимает inout-параметр state, потому что он меняет состояние (добавляет книгу, завершает цикл). Мы не трактуем это как «передачу по ссылке» в мистическом смысле, но понимаем практично: функция получает право изменить переменную, которую вы передали с &.

В-третьих, switch по self снова становится центром логики. И если добавится новый case, компилятор снова заставит обновить run.

5. mutating методы в enum: когда меняется сам enum

Очень частая модель для enum — это не только «команда», но и «состояние». Например, уровень громкости, статус заказа, режим приложения. В таких моделях удобно менять само значение enum: было .mute, стало .low.

В Swift это делается через mutating-метод. Причина простая: enum — value type. Чтобы изменить значение, вы фактически присваиваете новое значение в self.

Пример «уровень громкости» (мини, но очень показательный):

import Foundation

enum VolumeLevel {
    case mute, low, high

    mutating func increase() {
        switch self {
        case .mute: self = .low
        case .low:  self = .high
        case .high: self = .high
        }
    }
}

И использование:

import Foundation

var volume: VolumeLevel = .mute
volume.increase()
print(volume) // low

Почему это полезно именно для начинающих? Потому что оно очень наглядно объясняет: enum — это значение. Чтобы изменить значение, мы заменяем его целиком на другое допустимое значение. Примерно как переключатель режимов: вы не «подкручиваете» .mute, вы переводите ручку в .low.

6. Мини-приложение: убираем большой switch из main

В реальной жизни начинающий код часто выглядит так: в main огромный switch, который делает всё. Давайте аккуратно соберём цикл команд так, чтобы main был «диспетчером», а не «кухней, бухгалтерией и отделом кадров» одновременно.

Сделаем LibraryCommand с raw values и CaseIterable, чтобы можно было парсить из ввода и показывать справку. Raw values вы уже знаете из прошлой лекции, сегодня мы используем их как удобную «строку команды».

import Foundation

enum LibraryCommand: String, CaseIterable {
    case help, list, add, exit
}

Добавим computed property, который красиво печатает команды в подсказке:

import Foundation

enum LibraryCommand: String, CaseIterable {
    case help, list, add, exit

    static var helpText: String {
        allCases.map { $0.rawValue }.joined(separator: ", ")
    }
}

Здесь важный момент: helpText — это static computed property. Это не про конкретную команду, это про тип в целом (про весь набор команд). Мы используем allCases, то есть работаем с перечислением вариантов.

Теперь добавим тот самый метод run(state:):

import Foundation

enum LibraryCommand: String, CaseIterable {
    case help, list, add, exit

    func run(state: inout LibraryState) -> String {
        switch self {
        case .help:
            return "Команды: \(Self.helpText)"
        case .list:
            return state.books.isEmpty ? "Список пуст." : state.books.joined(separator: ", ")
        case .add:
            state.books.append("Новая книга")
            return "Ок."
        case .exit:
            state.isRunning = false
            return "Выходим."
        }
    }
}

Обратите внимание: здесь мы разнесли плотные однострочные ветки на несколько строк — так проще читать и расширять код, даже если пример учебный.

И теперь main становится очень компактным и понятным:

import Foundation

var state = LibraryState()

while state.isRunning {
    let input = readLine() ?? ""
    let cmd = LibraryCommand(rawValue: input) ?? .help
    print(cmd.run(state: &state))
}

Вот это ощущение «магии порядка» — не магия, а простая дисциплина: логика команд живёт в enum, а цикл — просто читает ввод, парсит и делегирует выполнение.

7. Полезные нюансы: вывод в строку и выбор между свойством и методом

CustomStringConvertible: делаем print(enum) по‑человечески

Когда вы делаете print(someEnum), Swift должен превратить значение в строку. В простых случаях он печатает имя типа и кейса, но иногда хочется контролировать вывод. Для этого есть протокол CustomStringConvertible и свойство description.

Важно понимать, что print и вообще превращение в строку завязаны на идею «тип может дать строковое представление сам», и одним из стандартных путей как раз является CustomStringConvertible.

Сделаем так, чтобы команды печатались как их raw value, а не как что-то техническое:

import Foundation

enum LibraryCommand: String, CaseIterable, CustomStringConvertible {
    case help, list, add, exit

    var description: String {
        rawValue
    }
}

Теперь:

import Foundation

let cmd: LibraryCommand = .list
print(cmd) // list

Почему это удобно? Потому что вы можете печатать команду в сообщениях пользователю, логах или подсказках, и она будет выглядеть одинаково везде. И это снова «логика рядом с типом»: команда сама определяет, как она превращается в текст.

Что выбрать: computed property или метод

Когда у вас появляется возможность «добавить поведение в enum», первая реакция иногда такая: «О, теперь всё сделаю properties!». А потом появляется property, которая читает файл, делает сетевой запрос и ещё заодно меняет состояние… и вы внезапно понимаете, что свойства были не для этого.

Давайте зафиксируем простое правило: computed property хороша, когда она выглядит как «характеристика» и не требует входных данных. Метод хорош, когда есть параметры, действие, или смысл ближе к глаголу.

Небольшая таблица (таблица — не список: глаза скажут спасибо):

Ситуация Лучше computed property Лучше метод
Нужно вернуть текст/число «по текущему case»
var title: String
возможно, но избыточно
Нужно принять аргумент (например,
state
)
неудобно/невозможно
func run(state: inout ...)
Нужен побочный эффект (изменить состояние) лучше избегать метод — естественно
Нужно изменить
self
(переключить состояние enum)
обычно не подходит
mutating func next()

Свойства — это про «какой ты». Методы — это про «что ты умеешь делать». Если держать это в голове, код получается предсказуемым даже для человека, который видит его впервые (например, для вас через неделю).

8. Типичные ошибки

Ошибка №1: делать computed property с “сюрпризами”.
Новички иногда пишут var message: String { ... }, а внутри меняют глобальные переменные, печатают в консоль, добавляют элементы в массив и вообще ведут себя как метод. Это ломает ожидания: свойство выглядит как «просто чтение», а по факту делает действие. Лучше честно назвать это методом (func makeMessage() -> String или func run(...) -> String) и не вводить читателя в заблуждение.

Ошибка №2: прятать пропущенные cases за default.
Кажется удобным: «да ладно, сделаю default: return "???"». Но вы тем самым отключаете помощь компилятора. Вся сила enum в том, что при добавлении нового case компилятор заставляет пройтись по важным местам. Если вы пишете default, вы говорите: «компилятор, не мешай мне ошибаться». Обычно это плохая сделка.

Ошибка №3: забыть mutating, когда меняете self.
Если внутри метода вы делаете self = .something, Swift справедливо требует mutating. Это не придирка: так язык подчёркивает, что value type меняется. В enum-состояниях (громкость, статус, режим) это встречается постоянно.

Ошибка №4: разносить “таблицу соответствий” по всему проекту.
Классика: в одном месте switch cmd { case .help: ... }, в другом — ещё один switch cmd, и в третьем — третий. Через неделю вы добавляете новый case и забываете обновить один из switch. Если вы уже доросли до идеи «команда — это enum», логично дойти до идеи «команда сама знает, как выполняться» и держать основную логику внутри enum.

Ошибка №5: путать raw value и “человекочитаемый текст”.
Raw value — это чаще всего формат (то, что пользователь вводит, или то, что сохраняется как стабильный идентификатор). А красивый текст — это то, что вы показываете человеку. Иногда они совпадают, иногда нет. Если вы начнёте использовать rawValue как UI-текст, то потом будет больно, когда захотите локализацию или более дружелюбные подписи. Хорошая привычка: raw value оставлять «для машины», а для человека делать отдельное computed property вроде title или usage.

1
Задача
Swift SELF, 25 уровень, 4 лекция
Недоступна
Подсказка светофора
Подсказка светофора
1
Задача
Swift SELF, 25 уровень, 4 лекция
Недоступна
Оценка курса
Оценка курса
1
Задача
Swift SELF, 25 уровень, 4 лекция
Недоступна
Температура мира
Температура мира
1
Задача
Swift SELF, 25 уровень, 4 лекция
Недоступна
Консоль заметок
Консоль заметок
1
Опрос
Перечисления Swift, 25 уровень, 4 лекция
Недоступен
Перечисления Swift
Работа с enum и switch
Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ