В программировании, чтобы создать надежные, безопасные и удобные в обслуживании приложения, крайне важно уметь контролировать, что становится видимым и как можно получить к этому доступ. И вот здесь на сцену выходят уровни доступа, подобно страховому полису для нашего кода: мы сами определяем, кто получает доступ к "игре", а кому вход закрыт.
Что это за уровни?
Существует 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() внутри расширения
}
}
Заключение
Ну вот мы и подошли к финишу, надеюсь, теперь у тебя есть ясное представление о том, как уровни доступа могут сделать твой код более структурированным и надежным. Помни, когда ты выбираешь уровень доступа, словно создаешь правила игры в своем собственном кодовом мире – решай, кто получит доступ, а кто останется за дверью. Всегда держи в уме основные принципы: защита данных, инкапсуляция, поддержание порядка и безопасности. Не забывай воплощать эти знания в своей будущей практике программирования.
Комментарии (8)
Maksclub
20.08.2023 15:52internal прямо хороший модификатор организовывать пакеты, и не раскрывать классы как внутреннюю часть пакета
Rusrst
Больше интересно как вы там без try catch с runtime error живёте :) и что делаете, если у вас у вас есть дубликат в diffable data source? :)
debug45
try/catch в Swift очень даже есть
Rusrst
Но не для runtime exception, если функция не выбрасывает исключение, перехватить его нельзя. Как раз если diffable data source имеет duplicate, то перехватить его нельзя, будет краш в runtime, так себе try catch, не находите?
nikita_dol
Это достаточно обычная проблема, когда в Swift используется что-то из Objective-C. Если какой-то Objective-C код падает в рантайме, то можно обернуть его в другой Objective-C код, который это отловит. Тыц
Или от меня скриншот
Rusrst
Спасибо, я не swift разраб, этого не знал, но это же не swift по идее, так что конечно спорно, но спасибо!
nikita_dol
Сам враппер пишется на Objective-C, но его можно использовать внутри Swift.
Например, у меня на скриншоте с кодом на Swift в 56 строке используется
tryBlock
из Objective-C. А сам код Objective-C со Stack Overflow. Поэтому, мы можем писать врапперы для Objective-C в Objective-C и потом использовать в SwiftRusrst
Прикольно, у нас таких проблем не стыке java и kotlin (за исключением null) нет, поэтому выглядит слегка непривычно. Ещё раз спасибо! :)