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

Источник: Medium Это руководство поможет вам лучше понять инкапсуляцию в Java, а также важность соблюдения конфиденциальности и согласованности данных. Кофе-брейк #192. Что такое инкапсуляция? В чем разница между CountDownLatch и CyclicBarrier в Java - 1Являясь одним из основных принципов объектно-ориентированного программирования, инкапсуляция, с моей точки зрения, все еще не совсем ясна. Казалось бы, о ней написано много статей, но, несмотря на это, я пока не нашел хороших (на мой взгляд) ресурсов, объясняющих этот ключевой принцип ООП. В общих чертах идея скрывается в самом названии. “Инкапсуляция” означает упаковку чего-либо в капсулу. Если говорить о программировании, то можно представить, что есть некий “черный ящик”, внутри которого находится часть нашей бизнес-логики. И все, что нам нужно, это вызывать методы, не думая о внутренней реализации. Но дьявол прячется в деталях. Как мы можем добиться инкапсуляции в наших программах? Давайте разберем проблему на примере простого класса BankAccount. Представьте, что вы пишете банковское приложение, которое должно отображать пользователю его денежные средства в разных валютах. В нашем случае это будут доллары США и евро. Как понять, инкапсулирован этот класс или нет? Взглянем на код:

namespace Encapsulation
{
    public class BankAccount
    {
        private string _number;
        private int _clientId;

        private decimal _amountInUsd;
        private decimal _amountInEur;

        public void DepositUsd(decimal amount)
        {
            ... some logic ...
        }
        
        public void DepositEur(decimal amount)
        {
            ... some logic ...
        }
    }
}
Ответ состоит из двух частей, поэтому разберем его более подробно. Причина в том, что инкапсуляция представляет собой комбинацию из конфиденциальности и согласованности данных.

1. Конфиденциальность

Конфиденциальность — это возможность скрыть некоторые детали реализации от посторонних глаз. В приведенном выше примере мы скрыли поля класса и оставили доступными только методы DepositUsd и DepositEur. То есть пользователи нашего класса не могут написать такой код:

var account = new BankAccount();
account._amountInUsd = 100500;
Console.WriteLine(account._amountInUsd);
Компилятор покажет ошибку, потому что поле _ amountInUsd помечено как private. Различные языки программирования реализуют конфиденциальность по-разному, например, C++, C# и Java имеют модификаторы доступа, такие как private, protected, public и так далее. Golang управляет доступом к членам уровня типа или пакета, используя соглашение об именах. Но самое главное, что доступ к полям/функциям/классам/методам может быть ограничен, и никто не может изменить данные, хранящиеся в объекте класса, извне класса. Большинство людей (на самом деле, не только джуниоры, но и разработчики с многолетним стажем) считают, что наличие средств управления доступом и есть инкапсуляция. Давайте вернемся к нашему примеру и заполним методы DepositUsd и DepositEur:

namespace Encapsulation
{
    public class BankAccount
    {
        private string _number;
        private int _clientId;

        private decimal _amountInUsd;
        private decimal _amountInEur;

        public void DepositUsd(decimal amount)
        {
            if (amount <= 0)
            {
                throw new ArgumentException("Values less or equal zero are not allowed!");
            }
        
            _amountInUsd += amount;
        }
        
        public void DepositEur(decimal amount)
        {
            if (amount <= 0)
            {
                throw new ArgumentException("Values less or equal zero are not allowed!");
            }
        
            _amountInEur += amount;
        }
    }
}
Здесь все поля доступны только внутри класса, поэтому никто не может их изменить. Класс предоставляет только два общедоступных члена — методы DepositUsd и DepositEur, которые содержат логику, защищающую поля _amountInUsd и _amountInEur от отрицательных или нулевых значений. Как вы думаете, этот класс сейчас инкапсулирован или нет? НЕТ. Потому что данные в этом классе могут быть противоречивыми, в них отсутствует согласованность.

2. Согласованность данных

Сначала мы должны понять, что такое данные класса. Это просто значения всех полей класса. Проблема с приведенным выше кодом заключается в том, что добавляя сумму к _amountInUsd мы не меняем значение нашего поля _amountInEur. Я думаю, никто не был бы счастлив, если бы они открыли свое банковское приложение и увидели сумму, которую нельзя конвертировать из долларов в евро. Это называется несогласованным состоянием данных класса, то есть поля класса несовместимы. Исправить ошибку можно довольно просто. Когда мы вносим доллары США на наш счет, мы должны конвертировать входящую сумму в евро и добавить ее в _amountInEur. То же самое нужно сделать в методе DepositEur по отношению к сумме в долларах. Исправленная версия будет выглядеть так:

namespace Encapsulation
{
    public class BankAccount
    {
        private string _number;
        private int _clientId;

        private decimal _amountInUsd;
        private decimal _amountInEur;

        public void DepositUsd(decimal amount)
        {
            if (amount <= 0)
            {
                throw new ArgumentException("Values less or equal zero are not allowed!");
            }
        
            _amountInUsd += amount;
            _amountInEur += ConvertToEur(amount);
        }
        
        public void DepositEur(decimal amount)
        {
            if (amount <= 0)
            {
                throw new ArgumentException("Values less or equal zero are not allowed!");
            }
        
            _amountInEur += amount;
            _amountInUsd += ConvertToUsd(amount);
        }
    }
}
Теперь мы знаем, что значения в _amountInUsd и _amountInEur согласованы, а это значит, что их всегда можно правильно преобразовать из одного в другое и обратно. Пользователи класса BankAccount могут быть спокойны и использовать методы DepositUsd и DepositEur без беспокойства и необходимости погружаться в реализацию класса, что и было нашей целью в самом начале. Итак, только при выполнении обоих этих двух условий мы можем сказать, что наш класс инкапсулирован. Но этот принцип применим не только к классу! Инкапсуляцию можно назвать “принципом черного ящика”, который можно (и, вообще-то, нужно) использовать для пакетов, библиотек, модулей, микросервисов, сервисов и даже целых систем, когда они интегрируются друг с другом. Надеюсь, что моя статья помогла вам лучше понять этот (возможно, самый) важный принцип объектно-ориентированного программирования!

В чем разница между CountDownLatch и CyclicBarrier в Java

Источник: Medium Благодаря этой публикации вы узнаете принципы работы и разницу между инструментами CountDownLatch и CyclicBarrier. Кофе-брейк #192. Что такое инкапсуляция? В чем разница между CountDownLatch и CyclicBarrier в Java - 2Инструменты CountDownLatch и CyclicBarrier используются для синхронизации логики в нескольких потоках. Однако бывают случаи, когда один из них подходит куда лучше, чем другой. Например, CyclicBarrier позволяет всем потокам ждать друг друга в определенный момент. То есть, ни один из потоков не может пройти через эту точку, пока все потоки ее не достигнут. Допустим, у нас есть три потока с разной логикой, и в какой-то момент они хотят двигаться вперед синхронно.

// поток №1

doThis();

barrier.await();

// ⏳ Ожидает потоки №2 и №3 …

doThat();

// ...

// поток №2

playThis();

barrier.await();

// ⏳ Ожидает потоки №1 и №3 ...

playThat();

// ...

// поток №3

runThis();

barrier.await();

// ⏳ Ожидает потоки №1 и №2 ...
runThat();

// ...
Как только все три потока выполнят код barrier.await(), они будут доступны для дальнейшей работы и выполнения doThat(), playThat() and runThat(). С другой стороны, инструмент CountDownLatch не препятствует дальнейшему перемещению потоков. Давайте посмотрим на пример с применением CountDownLatch:

// поток №1

doThis();

latch.countDown();

// 🚀 Немедленно перемещается вперед 

doThat();

// ...

// поток №2

playThis();

latch.countDown();

// 🚀 Немедленно перемещается вперед 

playThat();

// ...

// поток №3

runThis();

latch.countDown();

// 🚀 Немедленно перемещается вперед

runThat();

// ...

// Основной поток

doSomething();

latch.await();

// ⏳ Ожидает, пока потоки №1, №2 и №3 пройдут необходимые точки

doEverything();
По сути, ничто не мешает всем трем потокам выполнять свою логику как обычно. Единственным заблокированным потоком будет основной поток, ожидающий, пока другие потоки уведомят его о прохождении через определенную точку. Как только все три потока сообщат об этом основному потоку, тогда основной поток может двигаться вперед. Так в чем же разница между CyclicBarrier и CountDownLatch? В случае с CyclicBarrier все друзья ждут друг друга, прежде чем пойти в клуб, тогда как в случае с CountDownLatch руководитель проекта ждет, пока все (дизайнер, разработчик и тестировщик) закончат свои задачи, прежде чем сообщить боссу о завершении проекта. То есть, CyclicBarrier требует, чтобы все стороны ждали друг друга, тогда как CountDownLatch требует, чтобы только одна сторона ждала остальных. Надеюсь, в будущем это вам пригодится. Удачного кодирования!