18 августа 2022

☕ Учебник по Java: инкапсуляция на простых примерах

Веб-разработчик, фрилансер... Пишу об ИТ и смежных технологиях.
Защищаем пользовательскую информацию от ошибочных действий с помощью фундаментальной концепции ООП — инкапсуляции.
☕ Учебник по Java: инкапсуляция на простых примерах

Что такое инкапсуляция?

Инкапсуляция описывает идею объединения данных и методов, работающих с этими данными в одном модуле, подобном Java-классу. А еще концепция довольно часто используется для сокрытия внутреннего представления или состояния объекта извне.

Общая идея этого механизма проста. Например, у вас есть атрибут, невидимый снаружи объекта. Вы связываете его с методами, обеспечивающими доступ для чтения или записи, а инкапсуляция позволяет скрыть конкретную информацию и контролировать доступ к внутреннему состоянию объекта.

Если вы знакомы с каким-либо объектно-ориентированным языком программирования, вы, вероятно, знаете эти методы как методы получения и установки – геттеры и сеттеры. Как видно из названий, метод получения получает атрибут, а метод установки изменяет его. В зависимости от того, какой метод вы используете, вы решаете – читать или изменять атрибут. Вы также можете указать, доступен ли атрибут только для чтения или вообще невидим.

Не теряя времени, разберем пример, наглядно демонстрирующий рассматриваемую концепцию и то, как она работает в Java.

☕ Подтянуть свои знания по Java вы можете на нашем телеграм-канале «Библиотека Java для собеса»

Инкапсуляция в Java

Это базовая концепция говорит о том, как грамотно спроектировать класс в Java, чтобы связать набор атрибутов, хранящих текущее состояние объекта с набором методов, использующих эти атрибуты.

Рассмотрим ее реализацию на примере, описывающем работу кофемашины.

Создадим класс Machinе с атрибутами config, beans, grinder и brewingUnit , хранящими текущее состояние объекта. Методы brewCoffee , brewEspresso , brewFilterCoffee и addBeans реализуют набор операций над этими атрибутами.

Пример 1

        import java.util.HashMap;
import java.util.Map;

public class Machinе{
    private Map config;
    private Map beans;
    private Grinder grinder;
    private BrewingUnit brewingUnit;

    public Machinе(Map beans) {
        this.beans = beans;
        this.grinder = new Grinder();
        this.brewingUnit = new BrewingUnit();
        this.config = new HashMap();
        this.config.put(CoffeeSelection.ESPRESSO, new Configuration(8, 28));
        this.config.put(CoffeeSelection.FILTER_COFFEE, new Configuration(30, 480));
    }

    public Coffee brewCoffee(CoffeeSelection selection) throws CoffeeException {
        switch (selection) {
            case FILTER_COFFEE:
                return brewFilter();
            case ESPRESSO:
                return brewEspresso();
            default:
                throw new CoffeeException("CoffeeSelection [" + selection + "] not supported!");
        }
    }

    private Coffee brewEspresso() {
        Configuration config = configMap.get(CoffeeSelection.ESPRESSO);

        // смолоть кофейные зерна
        GroundCoffee groundCoffee = this.grinder.grind(
            this.beans.get(CoffeeSelection.ESPRESSO), config.getQuantityCoffee());

        // сварить эспрессо
        return this.brewingUnit.brew(CoffeeSelection.ESPRESSO, 
            groundCoffee, config.getQuantityWater());
    }

    private Coffee brewFilter() {
        Configuration config = configMap.get(CoffeeSelection.FILTER_COFFEE);

        // смолоть кофейные зерна снова
        GroundCoffee groundCoffee = this.grinder.grind(
            this.beans.get(CoffeeSelection.FILTER_COFFEE), config.getQuantityCoffee());

        // режим однократного пропуска горячей воды через слой кофе
        return this.brewingUnit.brew(CoffeeSelection.FILTER_COFFEE, 
            groundCoffee, config.getQuantityWater());
    }

    public void addBeans(CoffeeSelection sel, CoffeeBean newBeans) throws CoffeeException {
        CoffeeBean existingBeans = this.beans.get(sel);

        if (existingBeans != null) {
            if (existingBeans.getName().equals(newBeans.getName())) {
                existingBeans.setQuantity(existingBeans.getQuantity() + newBeans.getQuantity());
            } else {
                throw new CoffeeException("Для каждого режима поддерживается только один вид зерен.");
            }
        } else {
            this.beans.put(sel, newBeans);
        }
    }
}

    

Скрытие информации в Java

Как мы уже говорили, вы также можете использовать концепцию инкапсуляции для реализации механизма сокрытия информации. Этот подход, как и абстракция, один из наиболее часто используемых механизмов в Java. Его примеры можно найти почти во всех хорошо реализованных Java-классах. Механизм сокрытия делает атрибуты класса недоступными извне.

🧩☕ Интересные задачи по Java для практики можно найти на нашем телеграм-канале «Библиотека задач по Java»

Модификаторы доступа

Говоря об этой концепции, не лишним будет разобрать инструменты для обозначения доступности элементов – модификаторы. Java поддерживает четыре модификатора доступа, используемые для определения видимости классов, методов и атрибутов. Каждый из них указывает уровень доступности, и вы можете использовать только один для каждого класса, метода или атрибута. Перечислим их, начиная с наиболее ограничивающих и заканчивая наименее строгими:

  1. private.
  2. no modifier.
  3. protected.
  4. public.

Рассмотрим более детально каждый из них и поговорим, когда вам следует их использовать.

Модификатор private

Самый ограничивающий и наиболее часто используемый модификатор доступа private делает атрибут или метод доступным только в пределах одного и того же класса. Подклассы или любые другие классы в том же или другом пакете не могут получить доступ к этому атрибуту или методу. Используем его только для атрибутов и методов, которые мы больше никогда не захотим вызвать.

В нашем примере мы используем его, чтобы ограничить доступ ко всем атрибутам и методам brewEspresso и brewFilter. Эти атрибуты и методы должны использоваться только в классе Machine и не являются частью общедоступного API. Вначале это может показаться немного запутанным. Однако очень полезно, когда классы в вашем пакете реализуют четко определенный набор логики. Это также удобно, если вы хотите управлять API, доступным для классов за пределами этого пакета. Впоследствии вы можете использовать видимость пакета для реализации метода, используемого только классами в этом пакете.

Модификатор no modifier

Отсутствие модификатора означает, что вы можете получить доступ к атрибутам и методам внутри вашего класса и из всех классов в одном пакете. Вот почему его часто называют пакетным.

Модификатор protected

Атрибуты и методы с модификатором доступа protected могут быть доступны внутри вашего класса всеми классами в одном пакете, а также всеми подклассами в том же или других пакетах. Этот модификатор, как правило, используется для внутренних методов, которые должны вызываться или переопределяться подклассами. Вы также можете использовать его, чтобы разрешить подклассам прямой доступ к внутренним атрибутам суперкласса.

Модификатор public

Доступ к методам и атрибутам, использующим модификатор public, можно получить как в текущем классе, так и во всех других классах.

Публичные методы и атрибуты становятся частью общедоступного API вашего класса и любого компонента, в котором вы их используете. Когда метод общедоступен, вам необходимо убедиться, что он хорошо задокументирован и надежно обрабатывает любые входные значения. Также имейте в виду, что этот метод будет использоваться какой-то частью вашего приложения, что затруднит его изменение или удаление.

Как правило, ваш общедоступный API должен быть максимально компактным и должен включать только методы, предназначенные для других частей вашего приложения или доступа внешних клиентов.

В нашем примере класс Machine должен быть общедоступным, поскольку он представляет интерфейс кофемашины и предназначен для использования другими классами, которые не обязательно должны быть частью того же пакета. Конструктор и методы brewCoffee и addBeans доступны другим классам для создания нового экземпляра Machine, когда вы добавляете в автомат кофейные зерна или завариваете свежую чашку кофе.

Пример 2

Здесь наглядно демонстрируется механизм сокрытия информации, описывающей напиток, приготовленный с помощью нашей кофемашины.

        public class Coffee {
    private CoffeeSelection selection;
    private double quantity;

    public Coffee (CoffeeSelection selection, double quantity) {
        this.selection = selection;
        this.quantity = quantity;
    }

    public CoffeeSelection getSelection() {
        return selection;
    }

    public double getQuantity() {
        return quantity;
    }

    public void setQuantity(double quantity) throws CoffeeException {
        if (quantity >= 0.0) {  
            this.quantity = quantity;
        } else {
            throw new CoffeeException("Количество должно быть >= 0.0");
        }
    }
}

    

Класс Coffee использует два закрытых атрибута для хранения информации: CoffeeSelection и quantity. Модификатор доступа private делает оба атрибута недоступными для других классов в том же или других пакетах. Если вы хотите получить информацию о текущем состоянии объекта, вы можете вызвать один из общедоступных методов.

Метод getSelection обеспечивает доступ для чтения к атрибуту selection . Он представляет тип кофе, приготовленный кофемашиной. Как видно из фрагмента кода, мы не реализовали метод set для этого атрибута, поскольку мы не можем изменить сорт кофе после того, как он заварен. Доступное количество напитка меняется со временем. После каждого глотка в вашей чашке остается немного меньше. Поэтому мы реализовали метод получения и установки для атрибута quantity .

Если вы внимательно посмотрите на метод setQuantity, то увидите, что мы также реализовали дополнительную проверку. Если кофе особенно вкусный, вы можете пить его, пока ваша чашка не опустеет. Когда вы это сделаете, ваш кофе закончится, и вы больше не сможете его пить. Таким образом, количество кофе должно быть больше или равно нулю.

***

Инкапсуляция описывает объединение данных и методов, работающих с этими данными, в один модуль и используется для реализации механизма сокрытия информации. Это концепция ООП помогает нам защитить пользовательскую информацию от ошибочных действий, тем самым повышая эффективность дальнейшей работы с кодом.

Материалы по теме

Комментарии

ВАКАНСИИ

Добавить вакансию
Разработчик C++
Москва, по итогам собеседования

ЛУЧШИЕ СТАТЬИ ПО ТЕМЕ