How to keep everything under control

In programming, in order to create reliable, secure, and maintainable applications, it is essential to be able to control what becomes visible and how you can access it. And this is where access levels come into play, like an insurance policy for our code: we ourselves determine who gets access to the “game” and who is denied entry.

What are these levels?

There are 5 access levels: open, public, internal, fileprivate and private

Let’s take a closer look at what it is and how to apply it.

OPEN

It’s like a special front door to the house. When you declare something with an access level open this means that other modules can not only see and use this code, but also inherit And redefine his.

Where can it be useful?

  • Extendable Classes: Ideal when we want to create a class that other developers can inherit and add their own methods and properties. We create a “parent” class with basic functionality, and other classes can build on it as a foundation, adding their own individual details.

  • Libraries and Frameworks: If we are writing code that will be used in other projects, the access level open can be very helpful. This allows developers using our library to extend and adapt its components to suit their needs.

  • Strong Connection: When we want to create a strong connection between different classes and modules, open can play an important role. Providing an “open” interface that can be inherited and extended promotes flexibility and interoperability.

// Открываем класс для наследования и расширения
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

Elements with this visibility level are available to everyone, but with a slight nuance. It’s like advertising on a big billboard – everyone who passes by can see it, but they cannot be overridden (for methods) or inherited (for classes) in other modules.

Where can it be useful?

  • Basic functionality: We can use public for methods or properties that provide the core functionality of our module and that we don’t want to be able to change or extend in other modules. This helps to maintain a stable interface and avoid unexpected changes.

  • Utilities and helper classes: If a module contains a set of utilities or helper classes that can be useful to other modules, but we want to prevent them from being subclassed or overridden, we declare them with access level public.

  • Creating an API for reading data: If we create an API for accessing data (database, cache, storage) – we use public to provide access to this data without the ability to change or override data methods.

// Создаем класс для наследования и расширения
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
    }
}

This concludes our consideration of levels that give access inside and outside our program space and move on to what allows us to control communications exclusively within our module.


INTERNAL

Default access level. Elements with this access level are visible within the entire “module”. A module is something like a neighborhood in programming where the elements share the same “yard”. Other modules from the outside do not see these elements, but everyone inside the module can.

Where can it be useful?

  • Internal Implementation Details: If there are inner classes, structures, or functions that should not be visible from the outside, but play an important role inside the module, the access level internal would be a suitable choice.

  • Compatibility and extensibility: Can be used for methods or properties that should be available to other parts of the project, but not necessarily for external modules. This strikes a balance between functionality and stealth.

// Создаем класс для наследования и расширения
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

This level “extends” visibility to the entire file .swiftwhere the element is created. That is, any code in this file can access elements with this visibility. It’s like a room in a house that only the family living in that house has access to.

Where can it be useful?

  • Encapsulation of internal parts: We can use fileprivateto hide internal implementation details from other files and modules. This helps provide a cleaner and more secure code architecture.

  • Organization of code in a file: Inside the file is better to use fileprivateto explicitly indicate which parts of the code are intended to be used internally by each other, but are not visible outside of the file.

  • Implementation of Extensions: When we extend the functionality of classes or structs, a good choice is to use fileprivate for methods or properties that should only be accessible within that file, not from outside.

// Создаем класс для наследования и расширения
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

The most closed level. Elements with this level of visibility are only available within the file where they are created. It is like a secure vault that only one person has access to. However, in an extension of a given type (class, structure, or enumeration), there is an attempt to access private elements of this type. This allows you to extend the functionality of the type using private elements that are not otherwise visible outside of this file.

Where can it be useful?

  • Secret Details: Use privatewhen you want to create internal functions, variables, or properties that should remain invisible to all other parts of the code. This improves security by preventing unwanted interference.

  • Maintaining Integrity: Prevents accidental modification or use of certain parts of the code that could affect the operation of other components.

  • Hidden extension: Can be very handy when we want to add extra functionality to a type without exposing all the details to the outside world.

// Создаем класс для наследования и расширения
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() внутри расширения
    }
}

Conclusion

Well, here we come to the finish line, I hope now you have a clear idea of ​​\u200b\u200bhow access levels can make your code more structured and reliable. Remember, when you choose an access level, it’s like creating the rules of the game in your own code world – decide who gets access and who stays out the door. Always keep in mind the basic principles: data protection, encapsulation, maintenance of order and security. Don’t forget to implement this knowledge in your future programming practice.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *