Уровни доступа в Swift: Как Держать Всё под Контролем
В программировании, чтобы создать надежные, безопасные и удобные в обслуживании приложения, крайне важно уметь контролировать, что становится видимым и как можно получить к этому доступ. И вот здесь на сцену выходят уровни доступа, подобно страховому полису для нашего кода: мы сами определяем, кто получает доступ к "игре", а кому вход закрыт.
Что это за уровни?
Существует 5 уровней доступа: open, public, internal, fileprivate и private
Давай разберёмся подробнее, что это такое и как применять.
OPEN
Это как особая входная дверь в дом. Когда ты объявляешь что-то с уровнем доступа open это означает, что другие модули могут не только видеть и использовать этот код, но и наследовать и переопределять его.
Где может пригодиться?
Расширяемые Классы: Идеально подходит когда мы хотим создать класс, который другие разработчики могут наследовать и дополнять своими методами и свойствами. Создаем "родительский" класс с базовым функционалом, а другие классы могут строить на нем, как на фундаменте, добавляя свои индивидуальные детали.
Библиотеки и Фреймворки: Если мы пишем код, который будет использоваться в других проектах, уровень доступа open может быть очень полезным. Это позволяет разработчикам, использующим нашу библиотеку, расширять и адаптировать ее компоненты под свои потребности.
Сильная Связь: Когда мы хотим создать крепкую связь между разными классами и модулями, open может сыграть важную роль. Предоставление "открытого" интерфейса, который может быть наследован и расширен, способствует гибкости и взаимодействию.
// Открываем класс для наследования и расширения
open class Shape {
open func area() -> Double {
return 0.0
}
}
// Cоздаем наследника в своем модуле
open class Circle: Shape {
private var radius: Double
public init(radius: Double) {
self.radius = radius
}
// Переопределяем метод рассчета площади
open override func area() -> Double {
return Double.pi * radius * radius
}
}
// Создаем наследника в другом модуле
class CustomShape: Shape {
override func area() -> Double {
return super.area() * 2
}
}
/* Другой модуль может наследовать и расширять классы, но если метод родительского
класса не будет помечен как open переопределить его мы не сможем */
PUBLIC
Элементы с этим уровнем видимости доступны для всех, но с небольшим нюансом. Это как реклама на большом щите - каждый, кто проходит мимо, может это видеть, но они не могут быть переопределены (для методов) или наследованы (для классов) в других модулях.
Где может пригодиться?
Базовые функциональности: Мы можем использовать public для методов или свойств, которые предоставляют базовую функциональность нашего модуля и которые мы не хотим, чтобы можно было изменять или расширять в других модулях. Это помогает поддерживать стабильный интерфейс и избегать непредвиденных изменений.
Утилиты и вспомогательные классы: Если модуль содержит набор утилит или вспомогательных классов, которые могут быть полезными для других модулей, но мы хотим предотвратить их подклассирование или переопределение - объявляем их с уровнем доступа public.
Создание API для чтения данных: Если мы создаем API для доступа к данным (база данных, кеш, хранилище) - используем public для предоставления доступа к этим данным без возможности изменения или переопределения методов работы с данными.
// Создаем класс для наследования и расширения
public class Shape {
public func area() -> Double {
return 0.0
}
}
// Cоздаем наследника в своем модуле
class Circle: Shape {
private var radius: Double
public init(radius: Double) {
self.radius = radius
}
// Переопределяем метод рассчета площади
open override func area() -> Double {
return Double.pi * radius * radius
}
}
/* Создаем наследника в другом модуле - ошибка компиляции, вызванная нарушением правил уровней доступа.
Cannot inherit from non-open class 'Shape' outside of its defining module
Overriding non-open instance method outside of its defining module
*/
class CustomShape: Shape {
public override func area() -> Double {
return super.area() * 2
}
}
На этом мы завершаем рассмотрение уровней дающих доступ внутри нашего программного пространства и за его пределами и переходим к тому, что позволяет нам контролировать связи исключительно внутри своего модуля.
INTERNAL
Уровень доступа по умолчанию. Элементы с этим уровнем доступа видны внутри всего "модуля". Модуль - это что-то вроде соседства в программировании где элементы делят один и тот же "двор". Другие модули извне не видят эти элементы, но все внутри модуля могут.
Где может пригодиться?
Внутренние реализационные детали: Если есть внутренние классы, структуры или функции, которые не должны быть видны извне, но играют важную роль внутри модуля, уровень доступа internal будет подходящим выбором.
Совместимость и расширяемость: Можно использовать для методов или свойств, которые должны быть доступны для других частей проекта, но не обязательно для внешних модулей. Это обеспечивает баланс между функциональностью и скрытностью.
// Создаем класс для наследования и расширения
internal class Shape {
func area() -> Double {
return 0.0
}
}
// Cоздаем наследника в своем модуле
internal class Circle: Shape {
private var radius: Double
init(radius: Double) {
self.radius = radius
}
// Переопределяем метод рассчета площади
override func area() -> Double {
return Double.pi * radius * radius
}
}
// Другой класс внутри модуля может наследовать и расширять классы
class CustomShape: Shape {
override func area() -> Double {
return super.area() * 2
}
}
FILEPRIVATE
Этот уровень "расширяет" видимость до всего файла .swift, где элемент создан. То есть, любой код в этом файле может иметь доступ к элементам с такой видимостью. Это как комната в доме, к которой есть доступ только у семьи, живущей в этом доме.
Где может пригодиться?
Инкапсуляция внутренних деталей: Мы можем использовать fileprivate, чтобы скрыть внутренние детали реализации от других файлов и модулей. Это помогает обеспечить более чистую и защищенную архитектуру кода.
Организация кода в файле: Внутри файла лучше использовать fileprivate, чтобы явно указать какие части кода предназначены для внутреннего использования друг другом, но не видны за пределами файла.
Реализация расширений: Когда мы расширяем функциональность классов или структур хорошим выбором будет использовать fileprivate для методов или свойств, которые должны быть доступны только в пределах этого файла, но не извне.
// Создаем класс для наследования и расширения
fileprivate class Shape {
func area() -> Double {
return 0.0
}
}
// Cоздаем наследника внутри того же файла .swift
fileprivate class Circle: Shape {
private var radius: Double
init(radius: Double) {
self.radius = radius
}
// Переопределяем метод рассчета площади
override fileprivate func area() -> Double {
return Double.pi * radius * radius
}
}
/* Важно: Наследник обязательно должен быть помечен как private или fileprivate
т.к не может иметь более высокий уровень доступа, чем его родитель. Это связано с
тем, что подкласс может получить доступ к членам суперкласса, и если бы уровень
доступа подкласса был более высоким, это могло бы привести к
утечке информации из модуля с более ограниченным уровнем доступа. */
PRIVATE
Самый закрытый уровень. Элементы с таким уровнем видимости доступны только внутри того файла где они созданы. Это подобно защищенному хранилищу доступ к которому имеет лишь один человек. Однако в расширении данного типа (класса, структуры или перечисления) есть попытка получить доступ к private элементам этого типа. Это позволяет расширить функциональность типа используя private элементы, которые в противном случае не видны за пределами этого файла.
Где может пригодиться?
Тайные Детали: Используй private, когда хочешь создать внутренние функции, переменные или свойства, которые должны оставаться невидимыми для всех остальных частей кода. Это улучшает безопасность, предотвращая нежелательное вмешательство.
Поддержание целостности: Предотвращает случайное изменение или использование определенных частей кода, которые могли бы повлиять на работу других компонентов.
Скрытое расширение: Может быть очень удобным, когда мы хотим добавить дополнительную функциональность типу, не раскрывая все детали внешнему миру.
// Создаем класс для наследования и расширения
private class Shape {
private func area() -> Double {
return 0.0
}
func printInfo() {
print("Some shape")
}
}
// Cоздаем наследника внутри того же файла .swift
private class Circle: Shape {
private var radius: Double
init(radius: Double) {
self.radius = radius
}
// Ошибка компиляции, переопределение private метода запрещено
// Method does not override any method from its superclass
override func area() -> Double {
return Double.pi * radius * radius
}
// Переопределяем метод т.к он не помечен как private
override func printInfo() {
print("Circle")
}
}
// Создаем расширение внутри того же файла .swift
extension Shape {
func calculateArea() -> Double {
return area() // Обращение к приватному методу area() внутри расширения
}
}
Заключение
Ну вот мы и подошли к финишу, надеюсь, теперь у тебя есть ясное представление о том, как уровни доступа могут сделать твой код более структурированным и надежным. Помни, когда ты выбираешь уровень доступа, словно создаешь правила игры в своем собственном кодовом мире – решай, кто получит доступ, а кто останется за дверью. Всегда держи в уме основные принципы: защита данных, инкапсуляция, поддержание порядка и безопасности. Не забывай воплощать эти знания в своей будущей практике программирования.