Інкапсуляція має мету, але її також можна зловживати або зловживати.
Розглянемо щось на зразок Android API, який має класи з десятками (якщо не сотнями) полів. Розкриваючи ці поля, споживачем API ускладнює навігацію та використання, а також дає користувачеві помилкове уявлення про те, що він може робити все, що завгодно, із тими полями, які можуть суперечити тому, як вони повинні використовуватися. Таким чином, інкапсуляція чудова в цьому сенсі для ремонтопридатності, зручності використання, читабельності та уникнення шалених помилок.
З іншого боку, POD або звичайні старі типи даних, як структура з C / C ++, в якій усі поля є загальнодоступними, також можуть бути корисними. Маючи непотрібні геттери / сетери, як ті, що генеровані анотацією @data в Ломбоку, - це лише спосіб зберегти "шаблон інкапсуляції". Однією з небагатьох причин, за якими ми робимо «марні» геттери / сетери в Java, - це те, що методи надають контракт .
У Java не можна мати поля в інтерфейсі, тому ви використовуєте getters та setters, щоб вказати спільне властивість, яке мають усі реалізатори цього інтерфейсу. У більш пізніх мовах, таких як Котлін або C #, ми бачимо поняття властивості як поля, для яких можна оголосити сеттером і геттером. Зрештою, марні геттери / сетери - це більше спадщина, з якою має жити Java, якщо Oracle не додасть до неї властивостей. Наприклад, Котлін, що є ще однією мовою JVM, розробленою JetBrains, має класи даних, які в основному роблять те, що анотація @data робить у Ломбоку.
Ось також кілька прикладів:
class DataClass
{
private int data;
public int getData() { return data; }
public void setData(int data) { this.data = data; }
}
Це поганий випадок інкапсуляції. Геттер і сетер ефективно марні. Інкапсуляція використовується в основному, оскільки це стандарт у таких мовах, як Java. Насправді це не допомагає, крім збереження послідовності в кодовій базі.
class DataClass implements IDataInterface
{
private int data;
@Override public int getData() { return data; }
@Override public void setData(int data) { this.data = data; }
}
Це хороший приклад інкапсуляції. Інкапсуляція використовується для забезпечення виконання контракту, в цьому випадку IDataInterface. Мета інкапсуляції в цьому прикладі - змусити споживача цього класу використовувати методи, які надає інтерфейс. Незважаючи на те, що геттер і сеттер нічого не фантазують, ми тепер визначили загальну рису між DataClass та іншими реалізаторами IDataInterface. Таким чином, у мене може бути такий метод:
void doSomethingWithData(IDataInterface data) { data.setData(...); }
Тепер, кажучи про інкапсуляцію, я думаю, що важливо також вирішити проблему синтаксису. Я часто бачу, як люди скаржаться на синтаксис, необхідний для забезпечення інкапсуляції, а не самої інкапсуляції. Один із прикладів, що спадає на думку, - це від Кейсі Мураторі (ви можете побачити його тут, тут ).
Припустимо, у вас є клас гравців, який використовує інкапсуляцію і хочете перемістити його позицію на 1 одиницю. Код виглядатиме так:
player.setPosX(player.getPosX() + 1);
Без капсулювання це виглядатиме так:
player.posX++;
Тут він стверджує, що інкапсуляція призводить до набагато більше введення тексту без додаткових переваг, і це в багатьох випадках може бути правдою, але щось помітити. Аргумент проти синтаксису, а не інкапсуляції. Навіть у таких мовах, як C, у яких відсутня концепція інкапсуляції, ви часто будете бачити змінні в структурах, встановлених з попереднім тиском або доданими до '_' або 'my', або що б там не означало, що вони не повинні використовуватися споживачем API, як ніби вони приватний.
Справа в тому, що інкапсуляція може допомогти зробити код набагато більш доступним та простим у використанні. Розглянемо цей клас:
class VerticalList implements ...
{
private int posX;
private int posY;
... //other members
public void setPosition(int posX, int posY)
{
//change position and move all the objects in the list as well
}
}
Якщо змінні в цьому прикладі були загальнодоступними, то споживач цього API був би заплутаний у тому, коли використовувати posX і posY та коли використовувати setPosition (). Приховуючи ці деталі, ви допомагаєте споживачеві краще використовувати ваш API інтуїтивно зрозумілим способом.
Синтаксис є обмеженням для багатьох мов. Однак новіші мови пропонують властивості, що дає нам приємний синтаксис членів публікації та переваги інкапсуляції. Ви знайдете властивості в C #, Kotlin, навіть у C ++, якщо використовуєте MSVC. ось приклад у Котліні.
клас VerticalList: ... {var posX: Int set (x) {field = x; ...} var posY: Int set (y) {поле = y; ...}}
Тут ми досягли того самого, що і в прикладі Java, але ми можемо використовувати posX і posY так, як якщо б вони були загальнодоступними змінними. Коли я спробую змінити їх значення, буде виконано тіло набору відстійника ().
Наприклад, у Котліні це буде еквівалент Java Bean з реалізованими getters, setters, hashcode, equals та toString:
data class DataClass(var data: Int)
Зверніть увагу, як цей синтаксис дозволяє нам робити Java Bean в одному рядку. Ви правильно помітили проблему, якою є така мова, як Java, при здійсненні інкапсуляції, але в тому, що Java не в самій інкапсуляції.
Ви сказали, що використовуєте @Data Lombok для створення гетерів та сеттерів. Зауважте ім’я, @Data. Він здебільшого призначений для використання у класах даних, які зберігають лише дані та мають на увазі серіалізацію та десеріалізацію. Придумайте щось на зразок збереження файлу з гри. Але в інших сценаріях, як, наприклад, з елементом інтерфейсу, ви найчастіше бажаєте встановити, оскільки просто зміна значення змінної може бути недостатньою для отримання очікуваної поведінки.
"It will create getters, setters and setting constructors for all private fields."
- Те , як ви описуєте цей інструмент, це звучить , як він буде підтримувати инкапсуляцию. (Принаймні, у вільному, автоматизованому, дещо анемічно-модельному сенсі). Тож у чому саме проблема?