How to keep everything under control

How to keep everything under control

In programming, in order to create reliable, secure, and maintainable applications, it is very important to be able to control what becomes visible and how it can be accessed. 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 to whom the entrance is closed.

What are these levels?

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

Let’s find out in more detail what it is and how to use it.

OPEN

It is 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 imitate and redefine him.

Where can it be useful?

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

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

  • Strong Connection: When we want to create a strong connection between different classes and modules, open can play a significant 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 level of visibility are available to everyone, but with a slight nuance. It’s like advertising on a big billboard – anyone walking by can see it, but they cannot be overridden (for methods) or inherited (for classes) in other modules.

Where can it be useful?

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

  • Utilities and helper classes: If a module contains a set of utilities or auxiliary classes that can be useful for other modules, but we want to prevent their subclassing or overriding – we declare them with access level public.

  • Creating an API for reading data: If we create an API for data access (database, cache, storage) – use public to provide access to this data without the possibility of changing or redefining the methods of working with the data.

// Создаем класс для наследования и расширения
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 the layers that allow access inside and outside of our software space, and moves on to the one that allows us to control communications solely within our module.


INTERNAL

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

Where can it be useful?

  • Internal implementation details: If there are internal 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 an appropriate choice.

  • Compatibility and extensibility: Can be used for methods or properties that should be available to other parts of the project, but not necessarily to 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 that lives in that house has access to.

Where can it be useful?

  • Encapsulation of internal details: We can use fileprivate, to hide internal implementation details from other files and modules. This helps ensure a cleaner and more secure code architecture.

  • Organization of code in a file: Better to use inside a file fileprivateto explicitly indicate which parts of the code are meant to be used internally by each other but are not visible outside the file.

  • Implementation of extensions: When we extend the functionality of classes or structures a good choice would be to use fileprivate for methods or properties that should be accessible only within this file, but not externally.

// Создаем класс для наследования и расширения
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 inside the file where they are created. It’s like a secure vault that only one person has access to. However, in the extension of a given type (class, struct, or enum), there is an attempt to access the private elements of this. 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 it 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 tampering.

  • Maintaining integrity: Prevents from accidentally changing or using certain parts of the code that could affect the operation of other components.

  • Hidden extension: Can be very convenient when we want to add additional 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 are at the finish line, I hope you now have a clear idea of ​​how access levels can make your code more structured and reliable. Remember, when you choose the level of access, it’s like creating the rules of the game in your own code world – decide who gets access and who stays behind. Always keep the basic principles in mind: data protection, encapsulation, maintaining order and security. Do not forget to implement this knowledge in your future programming practice.

Related posts