Что такое инкапсуляция?
Источник:
Medium
Это руководство поможет вам лучше понять инкапсуляцию в Java, а также важность соблюдения конфиденциальности и согласованности данных.
Являясь одним из основных принципов объектно-ориентированного программирования, инкапсуляция, с моей точки зрения, все еще не совсем ясна. Казалось бы, о ней написано много статей, но, несмотря на это, я пока не нашел хороших (на мой взгляд) ресурсов, объясняющих этот ключевой принцип ООП.
В общих чертах идея скрывается в самом названии. “Инкапсуляция” означает упаковку чего-либо в капсулу. Если говорить о программировании, то можно представить, что есть некий “черный ящик”, внутри которого находится часть нашей бизнес-логики. И все, что нам нужно, это вызывать методы, не думая о внутренней реализации.
Но дьявол прячется в деталях. Как мы можем добиться инкапсуляции в наших программах? Давайте разберем проблему на примере простого класса 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.
Инструменты 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 требует, чтобы только одна сторона ждала остальных.
Надеюсь, в будущем это вам пригодится. Удачного кодирования!
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ