8.5. Основы Kotlin. Графы

Члены класса

Класс может иметь произвольное количество членов (members), которые делятся на две категории: свойства и функции. Свойства класса определяются как val (неизменяемые) или var (изменяемые). Чаще всего они используются для описания внутренней структуры класса; например, в классе Graph свойство vertices используется для сохранения информации о вершинах графа, а в классе Vertex свойство neighbors сохраняет информацию о вершинах, соседних (то есть соединённых ребром) с данной.

Функции класса определяются как fun и используются для различных операций с объектом данного класса, в данном случае — с графом. В данном случае, функции connect служат для соединения двух вершин графа ребром — то есть, для добавления в граф нового ребра. Функция addVertex добавляет в граф новую вершину.

Видимость

Члены класса могут иметь различную видимость. Чаще всего в Котлине используются два уровня видимости: открытый (public, по умолчанию) и закрытый (private). Для изменения уровня видимости следует указать ключевое слово public или private перед определением члена класса.

Открытые члены класса могут использоваться всеми. В любой части программы мы имеем право написать graph.connect(…​) для добавления в граф нового ребра. Закрытые члены класса могут использоваться только самим классом; при попытке написать graph.vertices снаружи класса для обращения к свойству vertices произойдёт ошибка при компиляции программы.

Закрытые члены класса были придуманы программистами, чтобы разграничить ответственность за разные участки программы. Действует следующий принцип: каждый класс сам отвечает за своё содержимое. В идеале, никакие операции с открытыми членами класса не должны приводить к ошибкам, и состояние объекта класса должно меняться в соответствии с выполненными операциями. Например:

fun useGraph() {
    val g = Graph()
    g.addVertex("A")
    g.addVertex("B")
    g.addVertex("C")
    g.addVertex("D")
    g.connect("A", "C")
    g.connect("B", "D")
    g.connect("B", "C")
    println(g.neighbors("B"))
}
// Должен получиться граф
// A ----- C
//         |
//         |
// D ----- B
// println выведет: ["C", "B"]

Программист, написавший функцию useGraph, резонно ожидает, что после выполнения приведённого кода в графе g будет четыре вершины и три ребра, выглядяющих примерно так, как изображено в комментарии. Он также ожидает, что при поиске вершин, соседних с «B», мы получим список из вершин «C» и «D». При этом ассоциативный массив vertices (который фактически и хранит информацию о вершинах графа), является закрытым и его не может изменять никто, кроме других членов данного класса.

Использованный здесь принцип программисты называют инкапсуляцией. Граф в данном случае подобен капсуле, на которой есть кнопки «addVertex» и «connect» (их нажатие изменяет граф), а также индикатор neighbors (вызов соответствующей функции не изменяет граф). Всё остальное находится внутри капсулы и не видно снаружи; закрытое содержимое графа является его личным (приватным) делом.

Вложенные классы

Довольно часто бывает так, что некоторый класс не имеет смысла без какого-то другого класса. Так произошло и в нашем примере — вершина не имеет никакого смысла без графа. В этом случае класс Vertex, соответствующий вершине, определяется внутри класса Graph. Поскольку в данном случае класс закрытый, то и использоваться он может только внутри класса Graph. Если бы класс Vertex был открыт, его можно было бы использовать снаружи графа как Graph.Vertex.

В данном случае вершина имеет свойство «имя» (name), которое задаётся в её конструкторе, и свойство «соседи» (neighbors), которое хранит мутирующее множество (MutableSet) из других вершин. При создании вершины множество её соседей пусто, но вызовы функции connect из графа расширяют его.

Множества и списки

Множества во многих отношениях похожи на списки, но всё же отличаются от них. Множества, как и списки, содержат внутри себя некоторое количество однотипных элементов; отличие от списков состоит в том, что множество не может содержать одинаковых элементов. При попытке добавить в множество элемент, который там уже есть, множество не изменяется.

Для множества имеется возможность проверить наличие в нём определённого элемента, или же перебрать все элементы множества с помощью цикла for — обе эти возможности есть и у списков. Для множества имеется свойство size и функции isEmpty()isNotEmpty() для определения его размера. Множества можно складывать друг с другом — все перечисленные операции у списков тоже имеются. Множества в Котлине бывают обычными Set<T> либо мутирующими MutableSet<T>.

Является ли множество просто списком, в котором нет одинаковых элементов? Нет, это не так. Множество не поддерживает доступ по индексу, то есть в нём отсутствует операция set[i] — как для чтения, так и для записи. Зато множество умеет значительно быстрее списка определять наличие в нём элементов element in set. Для реализации этой операции над списком необходимо перебрать его весь, а множества имеют более сложную структуру, позволяющую находить элементы в нём быстрее.

Создаются множества в Котлине с помощью функций setOf(…​) и mutableSetOf(…​).

Добавить комментарий