Чи є інкапсуляція ще одним із слонів, на яких стоїть ООП?


97

Інкапсуляція підказує мені зробити всі або майже всі поля приватними та викрити їх за допомогою геттерів / сетерів. Але зараз з'являються такі бібліотеки, як Lombok, які дозволяють нам виставити всі приватні поля одним коротким анотацією @Data. Він створить геттери, сетери та конструктори налаштування для всіх приватних полів.

Чи може хтось мені пояснити, який сенс ховати всі поля як приватні і після цього виставляти їх усі за допомогою якоїсь додаткової технології? Чому тоді ми просто не використовуємо лише загальнодоступні поля? Я відчуваю, що ми пройшли довгий і важкий шлях лише для того, щоб повернутися до початкової точки.

Так, є й інші технології, які працюють за допомогою геттерів та сетерів. І ми не можемо їх використовувати через прості загальнодоступні поля. Але ці технології з'явилися лише тому, що у нас були ті численні властивості - приватні поля за громадськими геттерами / сетерами. Якби ми не мали властивостей, ці технології розвивали б інший шлях і підтримували б публічні сфери. І все було б просто, і нам би не було потреби в Ломбоку зараз.

Який сенс усього цього циклу? І чи має інкапсуляція справді якийсь сенс у програмі реального життя?


19
"It will create getters, setters and setting constructors for all private fields."- Те , як ви описуєте цей інструмент, це звучить , як він буде підтримувати инкапсуляцию. (Принаймні, у вільному, автоматизованому, дещо анемічно-модельному сенсі). Тож у чому саме проблема?
Девід

78
Інкапсуляція приховує внутрішню реалізацію об'єкта за його публічним договором (як правило, інтерфейсом). Геттери і сетери роблять зовсім навпаки - вони викривають внутрішні об'єкти, тому проблема полягає в геттерах / сеттерах, а не в капсуляції.

22
Класи даних @VinceEmigh не мають інкапсуляції . Екземпляри їх - це значення саме в тому сенсі, що і примітиви
Калета

10
@VinceEmigh з використанням JavaBeans не є ООС , це процедурний . Те, що література називає їх «Об’єктами», - це помилка історії.
Калет

28
Я багато років замислювався над цим протягом багатьох років; Я думаю, що це випадок, коли наміри ООП відрізнялися від реалізації. Після вивчення Smalltalk ясно , що ООП - х інкапсуляція був призначений , щоб бути (тобто, кожен клас , який є як самостійний комп'ютер, з допомогою методів , як загальний протокол), хоча з причин , до цих пір невідомих мені, це безперечно зловили на популярність , щоб зробити це об'єкти getter / setter, які не пропонують концептуальної інкапсуляції (вони нічого не приховують, нічого не управляють і не несуть ніякої відповідальності, крім даних), і все ж вони використовують властивості.
jrh

Відповіді:


82

Якщо ви розкриєте всі свої атрибути за допомогою getters / setters, ви отримуєте лише структуру даних, яка все ще використовується на C або будь-якій іншій процедурній мові. Це не капсулювання, і Ломбок просто робить роботу з процесуальним кодексом менш болісною. Геттери / сетери такі ж погані, як і звичайні загальнодоступні поля. Дійсно немає різниці.

І структура даних не є об’єктом. Якщо ви почнете створювати об'єкт із написання інтерфейсу, ви ніколи не додаватимете геттерів / сетерів до інтерфейсу. Розкриття ваших атрибутів призводить до процедурного коду спагетті, коли маніпуляція з даними знаходиться поза об'єктом і розповсюджує базу даних коду. Тепер ви маєте справу з даними та з маніпуляціями з даними замість того, щоб говорити з об’єктами. З Getters / Setters, у вас буде керуватися даними процедурне програмування, де маніпуляції з цим проводяться в прямому порядку. Отримати дані - зробити щось - встановити дані.

У OOP інкапсуляція є слоном, якщо це зроблено правильно. Ви повинні інкапсулювати інформацію про стан та реалізацію, щоб об'єкт мав повний контроль над цим. Логіка буде зосереджена всередині об'єкта і не поширюватиметься по всій кодовій базі. І так - інкапсуляція все ще важлива в програмуванні, оскільки код буде більш ретельним.

РЕДАКТИ

Після перегляду дискусій, що тривають, я хочу додати кілька речей:

  • Не має значення, скільки атрибутів ви виставляєте за допомогою геттерів / сеттерів і наскільки ретельно ви це робите. Більш вибірковість не зробить ваш код OOP з інкапсуляцією. Кожен атрибут, який ви викриєте, призведе до певної процедури роботи з цими голими даними в імперативному процедурному порядку. Ви просто поширюватимете код менш повільно, будучи більш виборчими. Це не змінює ядро.
  • Так, за межами системи ви отримуєте голі дані з інших систем або бази даних. Але ці дані - лише черговий момент інкапсуляції.
  • Об'єкти повинні бути надійними . Вся ідея об'єктів несе відповідальність, так що вам не потрібно віддавати накази, які є прямими та необхідними. Натомість ви просите об'єкта зробити те, що він робить добре через контракт. Ви можете безпечно делегувати частину діючих на об'єкт. Об'єкти інкапсулюють деталі стану та імплементації.

Тож якщо ми повернемося до питання, навіщо нам це робити. Розглянемо цей простий приклад:

public class Document {
    private String title;

    public String getTitle() {
        return title;
    }
}

public class SomeDocumentServiceOrHandler {

    public void printDocument(Document document) {
        System.out.println("Title is " + document.getTitle());
    }
}

Тут ми маємо Документ, що розкриває внутрішні деталі гетером та має зовнішній процедурний код у printDocumentфункції, який працює з тим, що знаходиться поза об'єктом. Чому це погано? Тому що тепер у вас просто код стилю C. Так, вона структурована, але в чому насправді різниця? Ви можете структурувати функції C у різних файлах та з іменами. І саме так звані шари роблять саме це. Клас обслуговування - це лише купа процедур, які працюють з даними. Цей код є менш ретельним і має багато недоліків.

public interface Printable {
    void print();
}

public final class PrintableDocument implements Printable {
    private final String title;

    public PrintableDocument(String title) {
        this.title = title;
    }

    @Override
    public void print() {
        System.out.println("Title is " + title);
    }
}

Порівняйте з цим. Зараз у нас є контракт, і деталі реалізації цього договору приховані всередині об'єкта. Тепер ви можете по-справжньому перевірити цей клас і цей клас інкапсулює деякі дані. Об'єкт стосується того, як це працює з цими даними. Для того, щоб зараз розмовляти з об'єктом, вам потрібно попросити його надрукувати себе. Це капсулювання, і це об'єкт. Ви отримаєте повну потужність введення залежностей, глузування, тестування, одиночні обов'язки та безліч переваг з OOP.


Коментарі не для розширеного обговорення; ця розмова переміщена до чату .
maple_shaft

1
"Геттери / сетери такі ж погані, як і звичайні загальнодоступні поля" - це неправда, принаймні для багатьох мов. Зазвичай ти не можеш перекрити доступ простого поля, але можеш перекрити геттери / сетери. Це дає універсальність дитячим заняттям. Це також дозволяє легко змінити поведінку класів. наприклад, ви можете почати з геттера / сетера, підкріпленого приватним полем, а пізніше перейти до іншого поля або обчислити значення за іншими значеннями. Це не можна зробити з простими полями. Деякі мови дозволяють полям мати автоматичні геттери та сетери, але Java - це не така мова.
Кет

Е, досі не переконаний. Ваш приклад добре, але просто позначайте інший стиль кодування, а не "справжній правильний" - якого не існує. Майте на увазі, що більшість мов є мультипарадигматикою в даний час і рідко є чистою OO чи чисто процедурною. Так важко дотримуватися себе "чистих концепцій ОО". Як приклад - ви не можете використовувати DI на своєму прикладі, оскільки ви поєднали логіку друку з підкласом. Друк не повинен бути частиною документа - PrintableDocument виводить свою частину для друку (через геттер) на PrinterService.
Т. Сар

Правильний підхід OO до цього, якби ми збиралися на "чистому OO" підході, використовували б щось, що реалізує абстрактний клас PrinterService , через повідомлення, що, пекло, слід надрукувати - використовуючи сорти GetPrintablePart. Те, що реалізує PrinterService, може бути будь-яким принтером - друк у форматі PDF, на екрані, у TXT ... Ваше рішення унеможливлює заміну логіки друку на щось інше, що закінчується більш сполученим та менш керованим. ніж ваш "неправильний" приклад.
Т. Сар

Підсумок: Геттери та сетери не є злими або не порушують ООП - люди, які не розуміють, як ними користуватися. Ви, наприклад, випадок - це приклад з підручника, коли люди пропускають всю думку про те, як працює DI. Приклад, який ви "виправили", вже був увімкнутий DI, роз'єднаний, з нього можна було легко знущатися ... Дійсно - пам’ятаєте всю річ "Віддати перевагу над спадщиною"? Один із стовпів ОО? Ви просто кинули його у вікно, коли ви відремонтували код. Це не вкладеться в жоден серйозний перегляд коду.
Т. Сар

76

Чи може хтось мені пояснити, який сенс ховати всі поля як приватні і після цього виставляти їх усі за допомогою якоїсь додаткової технології? Чому тоді ми не просто використовуємо лише загальнодоступні поля?

Сенс у тому, що ти цього не повинен робити .

Інкапсуляція означає, що ви відкриваєте лише ті поля, до яких насправді потрібні інші класи, і ви дуже обережні та уважні до цього.

Ви НЕ просто дати все поля одержувачів і установників по - замовчуванням!

Це абсолютно проти духу специфікації JavaBeans, яка іронічно звідки походить концепція громадських дітлахів та сеттерів.

Але якщо ви подивитеся на специфікацію, ви побачите, що вона передбачала створення getters і setters дуже вибірковим, і це говорить про властивості "тільки для читання" (без сеттера) та "тільки для запису" властивості (ні геттер).

Ще один фактор полягає в тому, що геттери та сетери не обов'язково мають простий доступ до приватного поля . Отримувач може обчислити значення, яке воно повертає, довільно складним способом, можливо, кешировать його. Установник може перевірити значення або сповістити слухачів.

Отже, ви є: інкапсуляція означає, що ви виставляєте лише ту функціональність, яку вам насправді потрібно викрити. Але якщо ви не замислюєтеся над тим, що вам потрібно викрити, а просто вольово-невольно розкривайте все, дотримуючись деякої синтаксичної трансформації, то, звичайно, це насправді не є інкапсуляція.


20
"[G] етери та сетери - це не обов'язково простий доступ" - я думаю, що це ключовий момент. Якщо ваш код отримує доступ до поля по імені, ви не можете змінити поведінку пізніше. Якщо ви використовуєте obj.get (), ви можете.
Дан Амброгіо

4
@jrh: Я ніколи не чув, щоб це називалося поганою практикою на Java, і це досить часто.
Майкл Боргвардт

2
@MichaelBorgwardt Цікаво; трохи поза темою, але я завжди цікавився, чому Microsoft рекомендує робити це для C #. Я думаю, що ці вказівки означають, що в C # єдине, для чого можна використовувати сеттери, - це перетворення значення, яке задається іншим значенням, яке використовується внутрішньо, таким чином, що не може відмовити або мати недійсне значення (наприклад, властивість PositionInches та властивість PositionCentimeters де він автоматично перетворюється внутрішньо в мм)? Не чудовий приклад, але це найкраще, що я можу придумати на даний момент.
jrh

2
@jrh це джерело каже, що не робити цього для Getters, на відміну від сеттерів.
Капітан Людина

3
@Gangnus, і ви дійсно бачите, що хтось ховає якусь логіку всередині геттера / сетера? Ми настільки звикли до цього, що getFieldName()стаємо для нас автоматичним контрактом. Ми не будемо сподіватися на якусь складну поведінку за цим. У більшості випадків це просто пряме порушення інкапсуляції.
Ізбассар Толеген

17

Я думаю, що суть справи пояснюється вашим коментарем:

Я повністю згоден з вашою думкою. Але десь нам доведеться завантажувати об’єкти даними. Наприклад, з XML. І нинішні платформи, що підтримують це, роблять це за допомогою геттерів / сетерів, тим самим погіршуючи якість нашого коду та наш спосіб мислення. Ломбок, справді, сам по собі не поганий, але саме його існування показує, що у нас є щось погане.

Проблема у вас полягає в тому, що ви змішуєте модуль збереження даних з активною моделлю даних.

У додатку, як правило, є кілька моделей даних:

  • Модель даних для розмови з базою даних,
  • модель даних для зчитування файлу конфігурації,
  • Модель даних для спілкування з іншою програмою,
  • ...

поверх моделю даних, який він фактично використовує для виконання своїх обчислень.

Взагалі, моделі моделей, що використовуються для зв'язку з зовнішньою стороною, повинні бути ізольованими та незалежними від внутрішньої моделі даних (модель бізнес-об'єкта, BOM), на якій проводяться обчислення:

  • незалежно : щоб ви могли додавати / видаляти властивості на BOM відповідно до ваших вимог, не змінюючи всіх клієнтів / серверів, ...
  • ізольовано : так що всі обчислення виконуються на BOM, де живуть інваріанти, і що перехід від однієї служби до іншої або модернізація однієї служби не викликає пульсації у всій базі коду.

У цьому сценарії цілком чудово, щоб об’єкти, які використовуються в комунікаційних шарах, мали всі об'єкти відкриті або відкриті геттерами / сеттерами. Це звичайні об'єкти, без яких-небудь інваріантів.

З іншого боку, ваш БОМ повинен мати інваріанти, що, як правило, не дозволяє мати багато сетерів (геттери не впливають на інваріантів, хоча вони зменшують інкапсуляцію до певної міри).


3
Я б не створював геттери та сетери для об'єктів зв'язку. Я просто оприлюднив би поля. Навіщо створювати більше роботи, ніж корисно?
іммібіс

3
@immibis: Я погоджуюсь загалом, проте, як зазначається в ОП, деякі рамки вимагають від геттерів / сеттерів. Зауважте, що ОП використовує бібліотеку, яка автоматично створює їх із застосуванням одного атрибуту, тому для нього це мало роботи.
Матьє М.

1
@Gangnus: Ідея тут полягає в тому, щоб геттери / сетери були ізольовані до граничного шару (вводу / виводу), де зазвичай забруднитися, але решта програми (чистий код) не забруднена і залишається елегантною.
Матьє М.

2
@kubanczyk: наскільки я знаю, офіційним визначенням є модель бізнес-об'єктів. Тут вона відповідає тому, що я назвав "внутрішньою моделлю даних", моделлю даних, на якій ви виконуєте логіку в "чистому" ядрі вашої програми.
Матьє М.

1
Це прагматичний підхід. Ми живемо у гібридному світі. Якби зовнішні бібліотеки могли знати, які дані передавати конструкторам BOM, ми мали б лише BOM.
Адріан Іфтоде

12

Розглянемо наступне ..

У вас є Userклас із властивістю int age:

class User {
    int age;
}

Ви хочете розширити масштаб, щоб мати Userдату народження, протилежну лише віку. Використання геттера:

class User {
    private int age;

    public int getAge() {
        return age;
    }
}

Ми можемо замінити int ageполе на більш складне LocalDate dateOfBirth:

class User {
    private LocalDate dateOfBirth;

    public int getAge() {
        LocalDate now = LocalDate.now();
        int year = ...; // calculate using dateOfBirth and now
        return year;
    }

    // other behaviors can now make use of dateOfBirth
}

Ніяких порушень контрактів, жодних розривів коду .. Не що інше, як розширення масштабів внутрішнього представництва під час підготовки до складніших форм поведінки.

Саме поле інкапсульоване.


Тепер, щоб усунути проблеми.

Ломбок @Dataанотація схожа на Котлін по класам даних .

Не всі класи представляють поведінкові об'єкти. Що стосується порушення інкапсуляції, це залежить від використання вами функції. Ви не повинні виставляти всі свої поля за допомогою геттерів.

Інкапсуляція використовується для приховування значень або стану структурованого об'єкта даних всередині класу

У більш загальному сенсі інкапсуляція - це акт приховування інформації. Якщо ви зловживаєте @Data, то легко припустити, що ви, ймовірно, порушуєте інкапсуляцію. Але це не означає, що це не має мети. Наприклад, JavaBeans деякі нахмурені. Однак він широко використовується в розвитку підприємства.

Чи могли б ви зробити висновок, що розвиток підприємства поганий через використання бобів? Звичайно, ні! Вимоги відрізняються від стандартних розробок. Чи можна зловживати квасолею? Звичайно! Їх весь час зловживають!

Lombok також підтримує @Getterта @Setterнезалежно - використовуйте те, що вимагає ваші вимоги.


2
Це не пов’язано з плесканням @Dataанотації на тип, який за задумом розкриває всі поля
Caleth

1
Ні, поля не приховані . Тому що все може підійти іsetAge(xyz)
Калет

9
@Caleth Поля , які є прихованими. Ви дієте так, ніби сеттер не може мати умови до і після, що є дуже поширеним випадком використання сетерів. Ви дієте так, ніби властивість field ==, яке навіть у таких мовах, як C #, просто не відповідає дійсності, оскільки обидві, як правило, підтримуються (як у C #). Поле можна перенести в інший клас, його можна поміняти на більш складне представлення ...
Vince Emigh

1
І що я говорю, це не важливо
Калет

2
@Caleth Як це не важливо? Це не має сенсу. Ви говорите, що інкапсуляція не застосовується, оскільки ви вважаєте, що це не важливо в цій ситуації, навіть якщо це застосовується за визначенням і чи є випадки використання?
Вінс Емі

11

Інкапсуляція підказує мені зробити всі або майже всі поля приватними та викрити їх за допомогою геттерів / сетерів.

Це не те, як визначається інкапсуляція в об'єктно-орієнтованому програмуванні. Інкапсуляція означає, що кожен об'єкт повинен бути схожим на капсулу, зовнішня оболонка якої (публічна апі) захищає та регулює доступ до її внутрішніх приміщень (приватні методи та поля) та приховує її від перегляду. Приховуючи внутрішні, абоненти не залежать від внутрішніх даних, дозволяючи змінювати внутрішні органи без зміни (або навіть перекомпіляції) абонентів. Крім того, інкапсуляція дозволяє кожному об'єкту застосовувати власні інваріанти, лише роблячи безпечні операції доступними для абонентів.

Таким чином, інкапсуляція є особливим випадком приховування інформації, в якому кожен об'єкт приховує свої внутрішні та застосовує свої інваріанти.

Генерування гетерів та сеттерів для всіх полів є досить слабкою формою інкапсуляції, оскільки структура внутрішніх даних не прихована, і інваріанти не можуть бути застосовані. Перевагою є те, що ви можете змінити спосіб зберігання даних внутрішньо (до тих пір, поки ви можете конвертувати в та зі старої структури), не змінюючи (або навіть перекомпілюючи) абонентів.

Чи може хтось мені пояснити, який сенс ховати всі поля як приватні і після цього виставляти їх усі за допомогою якоїсь додаткової технології? Чому тоді ми просто не використовуємо лише загальнодоступні поля? Я відчуваю, що ми пройшли довгий і важкий шлях лише для того, щоб повернутися до початкової точки.

Частково це пов’язано з історичною аварією. Перший удар полягає в тому, що в Java вирази виклику методу та вирази доступу до поля синтаксично відрізняються на сайті виклику, тобто заміна доступу до поля викликом Getter або Setter порушує API класу. Тому, якщо вам може знадобитися аксесуар, потрібно написати його зараз або мати можливість зламати API. Ця відсутність підтримки властивостей на рівні мови суворо контрастує з іншими сучасними мовами, особливо з C # та EcmaScript .

Страйк 2 полягає в тому, що специфікація JavaBeans визначала властивості як getters / setters, поля не були властивостями. Як результат, більшість ранніх корпоративних рамок підтримували getters / setters, але не поля. Це вже давно минуле ( Java Persistent API (JPA) , Bean Validation , Java Architecture for XML Binding (JAXB) , Джексондосі всі поля підтримки просто добре), але старі підручники та книги продовжують затримуватися, і не всі знають, що все змінилося. Відсутність підтримки властивостей мовного рівня все ще може бути болем у деяких випадках (наприклад, через те, що ледаче завантаження окремих сутностей JPA не спрацьовує, коли публічні поля читаються), але переважно публічні поля працюють просто чудово. На думку, моя компанія пише всі DTO для своїх REST API з загальнодоступними полями (зрештою, більше не публікується, що передається через Інтернет, зрештою :-).

Тим НЕ менше, Ломбок @Dataробить більше , ніж генерувати геттери / сеттери: Він також генерує toString(), hashCode()і equals(Object), що може бути дуже цінним.

І чи має інкапсуляція справді якийсь сенс у програмі реального життя?

Інкапсуляція може бути неоціненною або зовсім марною, це залежить від об'єкта, який інкапсулюється. Як правило, чим складніша логіка всередині класу, тим більша користь від інкапсуляції.

Автоматично генеровані геттери та сетери для кожного поля, як правило, надмірно використовуються, але можуть бути корисними для роботи зі застарілими рамками або використовувати випадкові функції фреймворку, які не підтримуються для полів.

Інкапсуляція може бути досягнута за допомогою геттерів та командних методів. Зазвичай сетери не є доцільними, оскільки, як очікується, вони змінять лише одне поле, тоді як підтримка інваріантів може вимагати змінити декілька полів одночасно.

Підсумок

геттери / сетери пропонують досить погану інкапсуляцію.

Поширеність геттерів / сеттерів на Java пов'язана з відсутністю підтримки властивостей на рівні мови та сумнівними варіантами дизайну в його історичній моделі компонентів, які були закріплені у багатьох навчальних матеріалах та у програмістів, які їх викладали.

Інші об'єктно-орієнтовані мови, такі як EcmaScript, підтримують властивості на мовному рівні, тому геттери можна вводити, не порушуючи API. На таких мовах введення геттерів може бути введено тоді, коли вони вам справді потрібні, а не заздалегідь, просто на всякий випадок, вам може знадобитися-це-один день, що робить набагато приємніше досвід програмування.


1
примушує своїх інваріантів? Це англійська? Чи не могли б ви пояснити це, хто не англієць? Не надто складно - деякі люди не знають англійської мови досить добре.
Gangnus

Мені подобається ваша основа, мені подобаються ваші думки (+1), але не практичний результат. Я вважаю за краще використовувати приватне поле та якусь спеціальну бібліотеку для завантаження для них даних за допомогою відображення. Я лише не розумію, чому ви ставите свою відповідь як аргумент проти мене?
Gangnus

@Gangnus Інваріант - це логічна умова, яка не змінюється (в значенні не, а значення значення змінюється / змінюється). У багатьох функціях є правила, які повинні бути істинними до та після їх виконання (називаються попередніми умовами та пост-умовами), інакше в коді є помилка. Наприклад, функція може зажадати, що її аргумент не буде null (умова) і може викинути виняток, якщо його аргумент є недійсним, оскільки цей випадок буде помилкою в коді (тобто, якщо говорити інформатики, код порушив інваріант).
Фарап

@Gangus: Не зовсім впевнений, де рефлексія входить у цю дискусію; Lombok - це процесор анотацій, тобто плагін до компілятора Java, який видає додатковий код під час компіляції. Але обов'язково, можна використовувати ломбок. Я просто кажу, що у багатьох випадках загальнодоступні поля працюють так само добре і їх простіше налаштувати (не всі компілятори автоматично визначають процесори анотацій ...).
meriton

1
@Gangnus: Інваріанти однакові в математиці та CS, але в CS є додаткова перспектива: З точки зору об'єкта, інваріанти повинні бути примусові та встановлені. З точки зору абонента цього об'єкта, інваріанти завжди правдиві.
meriton

7

Я справді поставив собі це питання.

Це не зовсім правда. Поширеність ІМО гетерів / сеттерів обумовлена ​​специфікацією Java Bean, яка цього вимагає; тож це в значній мірі особливість не об'єктно-орієнтованого програмування, а орієнтованого на Бін програмування. Різниця між ними полягає в шарі абстракції, в якому вони існують; Боби - це більше системний інтерфейс, тобто на більш високому шарі. Вони абстрагуються від основи ОО, або мають на меті принаймні - як завжди, речі ведуться занадто часто.

Я б сказав, що дещо прикро, що ця річ Bean, яка є всюдисущою в програмуванні Java, не супроводжується додаванням відповідної функції мови Java - я думаю про щось на зразок концепції Properties у C #. Для тих, хто цього не знає, мовна конструкція виглядає так:

class MyClass {
    string MyProperty { get; set; }
}

У будь-якому випадку, придатність до інкапсуляції все ще має велику користь.


Python має подібну особливість, де властивості можуть мати прозорі геттери / сетери. Я впевнений, що ще більше мов мають схожі функції.
JAB

1
"Поширеність ІМО гетерів / сеттерів обумовлена ​​специфікацією Java Bean, яка вимагає цього" Так, ви праві. І хто сказав, що це потрібно вимагати? Причину, будь ласка?
Gangnus

2
Шлях C # абсолютно такий самий, як і Ломбок. Я не бачу реальної різниці. Більш практична зручність, але очевидно погана в концепції.
Gangnus

1
@Gangnis Специфікація цього вимагає. Чому? Перевірте мою відповідь на конкретний приклад того, чому геттери не є такими, як відкриття полів - спробуйте досягти того ж без геттера.
Вінс Емі

@VinceEmigh gangnUs, будь ласка :-). (банда-ходіння, нус - горіх). А як щодо використання get / set лише там, де це нам конкретно потрібно, та масового завантаження в приватні поля шляхом відображення в інших випадках?
Gangnus

6

Чи може хтось мені пояснити, який сенс ховати всі поля як приватні і після цього виставляти їх усі за допомогою якоїсь додаткової технології? Чому тоді ми не просто використовуємо лише загальнодоступні поля? Я відчуваю, що ми пройшли довгий і важкий шлях лише для того, щоб повернутися в точку початку.

Тут проста відповідь: ви абсолютно праві. Геттери і сетери усувають більшість (але не всі) значення інкапсуляції. Це не означає, що коли-небудь у вас є метод отримання та / або встановлення, що ви порушуєте інкапсуляцію, але якщо сліпо додаєте доступу до всіх приватних членів вашого класу, ви робите це неправильно.

Так, є й інші технології, які працюють за допомогою геттерів та сетерів. І ми не можемо їх використовувати через прості загальнодоступні поля. Але ці технології з'явилися лише тому, що ми мали ті численні властивості - приватні поля за громадськими геттерами / сетерами. Якби ми не мали властивостей, ці технології розвивали б інший шлях і підтримували б публічні сфери. І все було б просто, і нам би не було потреби в Ломбоку зараз. Який сенс усього цього циклу? І чи має інкапсуляція справді якийсь сенс у програмі реального життя?

Геттери є сеттерами всюди поширеними в програмуванні Java, тому що концепція JavaBean була запропонована як спосіб динамічно прив’язувати функціональність до попередньо побудованого коду. Наприклад, у вас може бути форма аплету (хтось пам’ятає ті?), Яка б оглядала ваш об’єкт, знаходила всі властивості та відображала поля як. Тоді інтерфейс користувача може змінювати ці властивості на основі введення користувача. Ви, як розробник, тоді просто переймаєтесь написанням класу і вкладаєте туди будь-яку перевірку чи логіку бізнесу тощо.

введіть тут опис зображення

Використовуючи приклад квасолі

Це сама по собі не страшна ідея, але я ніколи не був великим шанувальником підходу в Java. Це просто йде проти зерна. Використовуйте Python, Groovy тощо. Щось більш природно підтримує такий підхід.

Річ JavaBean вийшла з-під контролю, тому що вона створила JOBOL, тобто розробників, написаних Java, які не розуміють ОО. В основному, об'єкти стали не що інше, як пакети даних, і вся логіка була написана зовні довгими методами. Оскільки це вважалося нормальним, такі люди, як ти, і я, які ставлять під сумнів це, вважалися куками. Останнім часом я бачу зрушення, і це не стільки поза стороннім становищем

Зв'язуюча річ XML - це міцна гайка. Це, мабуть, не добре поле бою для виступу проти JavaBeans. Якщо вам доведеться скласти ці JavaBeans, намагайтеся не використовувати їх у реальному коді. Обробляти їх як частину шару серіалізації.


2
Найбільш конкретні і мудрі думки. +1. Але чому б не написати групу класів, з яких кожен буде масово завантажувати / зберігати приватні поля до / від якоїсь зовнішньої структури? JSON, XML, інший об’єкт. Він може працювати з POJO або, можливо, лише з полями, позначеними анотацією до цього. Зараз я думаю про цю альтернативу.
Gangnus

@Gangnus Здогадуюсь, оскільки це означає багато додаткового написання коду. Якщо використовується рефлексія, тоді повинен бути записаний лише один грудочок коду серіалізації, який може серіалізувати будь-який клас, що відповідає певній схемі. C # підтримує це через [Serializable]атрибут та його родичів. Навіщо писати 101 конкретний метод / класи серіалізації, коли ви можете просто написати один, використовуючи роздуми?
Фарап

@Pharap Мені дуже шкода, але ваш коментар для мене занадто короткий. "використання рефлексії"? І який сенс ховати такі різні речі в один клас? Чи можете ви пояснити, що ви маєте на увазі?
Gangnus

@Gangnus Я маю на увазі рефлексію , здатність коду перевіряти власну структуру. У Java (та інших мовах) ви можете отримати список усіх функцій, які клас відкриває, а потім використовувати їх як спосіб вилучення даних із класу, необхідного для їх серіалізації, наприклад, XML / JSON. Використання рефлексії, яка була б разовою роботою, а не постійно створювати нові методи для збереження структур на основі класу. Ось простий приклад Java .
Фарап

@Pharap Це саме те, про що я говорив. Але чому ви називаєте це "користю"? І альтернатива, яку я згадував у першому коментарі, тут залишається - повинні (не) упаковані поля мати спеціальні анотації чи ні?
Gangnus

5

Скільки ми можемо зробити без геттерів? Чи можливо їх повністю видалити? Які проблеми це створює? Чи можемо ми навіть заборонити returnключове слово?

Виявляється, ви можете зробити багато, якщо хочете виконати роботу. Як тоді інформація коли-небудь виходить з цього повністю капсульованого об'єкта? Через співпрацівника.

Замість того, щоб дозволити коду задавати вам питання, які ви підказуєте, що потрібно робити. Якщо ці речі також не повертають речі, вам не доведеться нічого робити з того, що вони повертаються. Отже, коли ви думаєте досягти returnзамість цього, спробуйте знайти якийсь співпрацювач вихідного порту, який зробить все, що потрібно зробити.

Робити це таким чином має переваги та наслідки. Вам доведеться думати більше, ніж про те, що ви б повернули. Ви повинні подумати про те, як ви будете надсилати це як повідомлення об’єкту, який не вимагав цього. Можливо, ви передасте той самий об’єкт, який ви б повернули, або достатньо просто викликати метод. Робити таке мислення варто ціною.

Перевага полягає в тому, що тепер ви спілкуєтесь із інтерфейсом. Це означає, що ви отримуєте повну користь від абстракції.

Це також дає вам поліморфну ​​розсилку, оскільки, хоча ви знаєте, про що говорите, не потрібно знати, що саме ви це говорите.

Ви можете подумати, що це означає, що вам потрібно пройти лише один шлях через групу шарів, але виявляється, ви можете використовувати поліморфізм, щоб повернутися назад, не створюючи шалених циклічних залежностей.

Це може виглядати приблизно так:

Введіть тут опис зображення

class Interactor implements InputPort {
    OutputPort out;
    int y = 0;

    Interactor(OutputPort out){
        this.out = out;
    }

    void accumulate(int x) {
        y = y + x;
        out.showAsImage(y);
    }
}

Якщо ви можете кодувати так, то використання геттерів - це вибір. Не потрібне зло.


Так, такі геттери / сетери мають чудовий сенс. Але ти розумієш, що мова йде про щось інше? :-) +1 все одно.
Gangnus

1
@Gangnus Дякую Є купа речей, про які ви могли б говорити. Якщо ви хочете, щоб принаймні назвати їх, я міг би зайти в них.
candied_orange

5

Це суперечливе питання (як ви бачите), оскільки купа догм та непорозумінь змішується з розумними занепокоєннями щодо виходу геттерів та сеттерів. Але коротше кажучи, немає нічого поганого @Dataі це не порушує інкапсуляцію.

Навіщо використовувати геттери та сетери, а не публічні поля?

Тому що геттери / сетери забезпечують інкапсуляцію. Якщо ви виставляєте значення як загальнодоступне поле, а потім пізніше змінюєте обчислення значення на льоту, то вам потрібно змінити всіх клієнтів, які отримують доступ до цього поля. Ясно, що це погано. Це детальна інформація про реалізацію того, чи зберігається якесь властивість об'єкта в полі, генерується на льоту або виймається з іншого місця, тому різниці не слід піддавати клієнтам. Getter / setters setter вирішують це, оскільки вони приховують реалізацію.

Але якщо геттер / сетер просто відображають основне приватне поле, чи не так це погано?

Немає! Справа в тому, що інкапсуляція дозволяє змінити реалізацію, не зачіпаючи клієнтів. Поле все-таки може бути ідеально прекрасним способом зберігання цінності, доки клієнтам не доведеться знати або піклуватися.

Але чи не автогенерувати геттери / сетери з полів, що порушують інкапсуляцію?

Ніякої інкапсуляції все ще немає! @Dataанотації - це лише зручний спосіб написання пар getter / setter, який використовує поле під полем. Для точки зору клієнта це подібно до звичайної пари геттер / сетер. Якщо ви вирішили переписати реалізацію, ви все одно можете це зробити, не впливаючи на клієнта. Так ви отримуєте найкраще з обох світів: інкапсуляція та стислий синтаксис.

Але деякі кажуть, що геттер / сетер завжди поганий!

Існує окрема суперечка, де деякі вважають, що модель «геттер / сетер» завжди погана, незалежно від основної реалізації. Ідея полягає в тому, що ви не повинні встановлювати або отримувати значення від об'єктів, а будь-яка взаємодія між об'єктами повинна моделюватися як повідомлення, де один об’єкт просить іншого об'єкта зробити щось. Це здебільшого фрагмент догми з перших днів об’єктно-орієнтованого мислення. Зараз мислення полягає в тому, що для певних зразків (наприклад, об'єкти цінності, об'єкт передачі даних) геттери / сеттери можуть бути цілком доречними.


Інкапсуляція повинна дозволяти нам поліморфізм та безпеку. Гети / набори дозволяють ялинам, але сильно проти другого.
Gangnus

Якщо ви кажете, що я десь використовую догму, будь ласка, скажіть, що з мого тексту - догма. І не забувайте, що деякі думки, якими я користуюся, не подобаються і не погоджуються. Я використовую їх для демонстрації протиріччя.
Gangnus

1
"GangNus" :-). Тиждень тому я працював над проектом, буквально повним викликом геттерів та сетерів у декілька шарів. Це абсолютно небезпечно, а ще гірше - підтримує людей у ​​небезпечному кодуванні. Я радий, що змінив роботу досить швидко, щоб люди звикли до такої манери. Отже, я не думаю, я це бачу .
Gangnus

2
@jrh: Я не знаю, чи існує офіційне пояснення як таке, але C # має вбудовану підтримку властивостей (getters / setters з приємнішим синтаксисом) та для анонімних типів, які є типами, які мають лише властивості та не мають поведінки. Тож дизайнери C # навмисно відійшли від метафори обміну повідомленнями та принципу "скажи, не питай". Розробка C #, оскільки версія 2.0, здається, більше надихається функціональними мовами, ніж традиційною чистотою OO.
ЖакБ

1
@jrh: Зараз я здогадуюсь, але я думаю, що C # досить натхненний функціональними мовами, де дані та операції є більш окремими. У розподілених архітектурах перспектива також перейшла від орієнтованої на повідомлення (RPC, SOAP) до орієнтованої на дані (REST). І переважали бази даних, які відкривають дані та працюють з ними (а не "чорними скриньками"). Коротше кажучи, я думаю, що фокус перемістився від повідомлень між чорними скриньками до відкритих моделей даних
JacquesB

3

Інкапсуляція має мету, але її також можна зловживати або зловживати.

Розглянемо щось на зразок 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. Він здебільшого призначений для використання у класах даних, які зберігають лише дані та мають на увазі серіалізацію та десеріалізацію. Придумайте щось на зразок збереження файлу з гри. Але в інших сценаріях, як, наприклад, з елементом інтерфейсу, ви найчастіше бажаєте встановити, оскільки просто зміна значення змінної може бути недостатньою для отримання очікуваної поведінки.


Чи можете ви навести тут приклад про неправильне використання інкапсуляції? Це може бути цікаво. Ваш приклад скоріше проти відсутності інкапсуляції.
Gangnus

1
@Gangnus Я додав кілька прикладів. Марно інкапсуляція, як правило, неправильне використання, а також з мого досвіду розробники API дуже намагаються змусити вас використовувати API певним чином. Я не став прикладом для цього, тому що у мене не було легкої презентації. Якщо я знайду його, я точно додам його. Я думаю, що більшість коментарів проти інкапсуляції насправді проти синтаксису інкапсуляції, а не самої інкапсуляції.
BananyaDev

Дякуємо за редагування Я виявив незначну помилку помилки: "publice" замість "public".
jrh

2

Інкапсуляція дає вам гнучкість . Розмежовуючи структуру та інтерфейс, це дозволяє змінювати структуру, не змінюючи інтерфейс.

Наприклад, якщо ви виявите, що вам потрібно обчислити властивість на основі інших полів, а не ініціалізувати базове поле на будівництві, ви можете просто змінити геттер. Якщо ви відкрили поле безпосередньо, замість цього вам доведеться змінити інтерфейс та внести зміни на кожен сайт використання.


Хоча теоретично гарна ідея, пам’ятайте, що змушення версії 2 API викидає винятки, що версія 1 не зламає користувачів вашої бібліотеки чи класу страшенно (і, можливо, мовчки!); Я трохи скептично налаштований на те, що будь-яка значна зміна може бути внесена "поза кадром" у більшості цих класів даних.
jrh

Вибачте, це не пояснення. Те, що використання полів заборонено в інтерфейсах, походить від ідеї інкапсуляції. І зараз ми говоримо про це. Ви базуєтесь на фактах, про які йде мова. (зауважте, я не кажу про або проти ваших думок, лише про те, що вони не можуть бути використані тут як аргументи)
Gangnus

@Gangnus Слово "інтерфейс" має значення поза лише ключовим словом Java: dictionary.com/browse/interface
glennsl

@jrh Це зовсім інше питання. Ви можете використовувати його для аргументації проти інкапсуляції в певних контекстах , але це жодним чином не може визнати аргументами його недійсними.
glennsl

1
Стара інкапсуляція, без масового отримання / набору, дала нам не тільки поліморфізм, але й безпеку. І масовий набір / отримує навчити нас поганій практиці.
Gangnus

2

Я спробую проілюструвати проблемний простір інкапсуляції та дизайну класів та відповісти на ваше запитання наприкінці.

Як згадується в інших відповідях, мета інкапсуляції - приховати внутрішні деталі об'єкта за загальнодоступним API, який виконує роль контракту. Об'єкт безпечно змінювати внутрішні, оскільки він знає, що його викликають лише через загальнодоступний API.

Чи має сенс мати загальнодоступні поля, або геттери / сетери, або методи транзакцій вищого рівня або передачу повідомлень, залежить від характеру домену, який моделюється. У книзі Akka Concurrency (яку я можу порекомендувати, навіть якщо вона дещо застаріла) ви знайдете приклад, який ілюструє це, який я скорочу тут.

Розглянемо клас користувача:

public class User {
  private String first = "";
  private String last = "";

  public String getFirstName() {
    return this.first;
  }
  public void setFirstName(String s) {
    this.first = s;
  }

  public String getLastName() {
    return this.last;
  }
  public void setLastName(String s) {
    this.last = s;
  }
}

Це чудово працює в однопоточному контексті. Домен, який моделюється, - це ім'я людини, і механізми того, як це ім’я зберігається, можуть бути ідеально капсульовані сетерами.

Однак уявіть, що це повинно надаватися у багатопотоковому контексті. Припустимо, один потік періодично читає ім’я:

System.out.println(user.getFirstName() + " " + user.getLastName());

І дві інші нитки ведуть боротьбу з перетягуванням каната, встановлюючи її по черзі Гілларі Клінтон і Дональду Трампу . Кожен з них повинен викликати два методи. Здебільшого це працює добре, але раз у раз ви збираєтесь бачити Гілларі Трамп чи Дональда Клінтона .

Вирішити цю проблему неможливо, додавши блокування всередині сетерів, оскільки блокування зберігається лише на час встановлення або імені, і прізвища. Єдине рішення через блокування - додати замок навколо всього об’єкта, але це порушує інкапсуляцію, оскільки викликовий код повинен керувати замком (і може спричинити тупикові місця).

Як виявляється, чистого розчину через блокування немає. Чисте рішення полягає в повторному капсулюванні внутрішніх приміщень, роблячи їх більш грубими:

public class UserName {
   public final String first;
   public final String last;
   public UserName(String first, String last) { ... }
}

public class User
   private UserName name;
   public UserName getName() { return this.name; }
   public setName(UserName n) { this.name = n; }
}

Саме ім'я стало непорушним, і ви бачите, що його члени можуть бути загальнодоступними, оскільки це тепер чистий об'єкт даних, який не має змоги змінювати його колись створений. У свою чергу, загальнодоступний API користувача класу став більш грубим, залишився лише один сетер, так що ім'я можна змінити лише в цілому. Він інкапсулює більше свого внутрішнього стану за API.

Який сенс усього цього циклу? І чи має інкапсуляція справді якийсь сенс у програмі реального життя?

У цьому циклі ви бачите спроби застосувати рішення, які корисні для конкретного набору обставин, занадто широко. Відповідний рівень інкапсуляції вимагає розуміння модельованого домену та застосування правильного рівня інкапсуляції. Іноді це означає, що всі поля є загальнодоступними, іноді (як у програмах Akka), це означає, що у вас немає загальнодоступного API, за винятком одного способу прийому повідомлень. Однак сама концепція інкапсуляції, що означає приховування внутрішніх даних за стабільним API, є ключовою для програмного забезпечення в масштабі, особливо в багатопотокових системах.


Фантастичний. Я б сказав, що ви підняли дискусію на рівень вище. Може два. Дійсно, я думав, що я розумію інкапсуляцію, а іноді й краще, ніж стандартні книги, і дуже побоювався, що ми практично втрачаємо це завдяки деяким загальноприйнятим звичаям та технологіям, і ТОМО було справжнім предметом мого запитання (можливо, не сформульованим у хороший спосіб), але ви показали мені справді нові сторони інкапсуляції. Я, безумовно, не дуже хороший у багатозадачності, і ти показав мені, наскільки це може бути шкідливим для розуміння інших основних принципів.
Gangnus

Але я не можу відзначити свій пост в відповіді, тому що мова йде не про дані масової завантаження в / з об'єктів, проблеми з - за якої з'являються такі платформи , як боби і Ломбкі. Можливо, ви могли б викласти свої думки в цьому напрямку, будь ласка, якщо ви можете отримати час? Я сам ще повинен переосмислити наслідки, які ваша думка спричиняє до цієї проблеми. І я не впевнений, що я достатньо підходить для цього (пам’ятайте, поганий багатопотоковий фон: - [)
Gangnus

Я не використовував lombok, але, як я розумію, це спосіб реалізації певного рівня інкапсуляції (getters / setters на кожному полі) з меншим типізацією. Це не змінює ідеальну форму API для вашого проблемного домену, це лише інструмент для швидшого його написання.
Joeri Sebrechts

1

Я можу придумати один із випадків використання, коли це має сенс. У вас може бути клас, до якого ви спочатку отримуєте доступ через простий API getter / setter. Пізніше ви розширите або зміните так, що він більше не використовує ті самі поля, але все ще підтримує той самий API .

Дещо надуманий приклад: Точка, яка починається як декартова пара з p.x()та p.y(). Пізніше ви створюєте нову реалізацію або підклас, який використовує полярні координати, щоб ви могли також дзвонити p.r()і p.theta(), але ваш код клієнта, який дзвонить p.x()і p.y()залишається дійсним. Сам клас прозоро перетворюється з внутрішньої полярної форми, тобто зараз y()би return r * sin(theta);. (У цьому прикладі встановлення лише x()чи y()не має великого сенсу, але все ж можливе.)

У цьому випадку ви можете сказати: "Радий, що я переймався тим, що автоматично оголошував геттери та сетери, замість того, щоб публікувати поля, або мені довелося б там зламати свій API".


2
Це не так приємно, як ти це бачиш. Зміна внутрішнього фізичного зображення справді робить об'єкт різним. І погано, що вони виглядають однаково, бо мають різні якості. Система Декарта не має спеціальних точок, полярна система має її.
Gangnus

1
Якщо вам не подобається цей конкретний приклад, напевно ви можете подумати про інших, у кого насправді є декілька еквівалентних представлень, або більш нове представлення, яке може бути перетворене на старе і з нього.
Девіслор

Так, ви можете дуже продуктивно використовувати їх для презентації. В інтерфейсі його можна широко використовувати. Але ти не змушуєш мене відповісти на власне запитання? :-)
Gangnus

Так ефективніше, чи не так? :)
Девіслор

0

Чи може хтось мені пояснити, який сенс ховати всі поля як приватні і після цього виставляти їх усі за допомогою якоїсь додаткової технології?

Тут абсолютно немає сенсу. Однак те, що ви задаєте це запитання, свідчить про те, що ви не зрозуміли, що робить Ломбок, і що ви не розумієте, як написати код OO за допомогою інкапсуляції. Давайте трохи перемотаємось ...

Деякі дані для екземпляра класу завжди будуть внутрішніми і ніколи не повинні піддаватися впливу. Деякі дані для екземпляра класу потрібно буде встановити зовні, а деякі дані, можливо, потрібно буде передати поза екземпляром класу. Ми, можливо, захочемо змінити те, як клас заповнює речі під поверхнею, тому ми використовуємо функції, які дозволяють нам отримувати та встановлювати дані.

Деякі програми хочуть зберегти стан для екземплярів класу, тому вони можуть мати певний інтерфейс серіалізації. Ми додаємо додаткові функції, які дозволяють екземпляру класу зберігати його стан у сховищі та витягувати його стан із сховища. Це підтримує інкапсуляцію, оскільки екземпляр класу все ще контролює власні дані. Ми можемо серіалізувати приватні дані, але решта програми не має доступу до них (або, точніше, ми підтримуємо Китайську стіну , вирішивши не навмисно пошкоджувати ці приватні дані), і екземпляр класу може (і повинен) провести перевірку цілісності десеріалізації, щоб переконатися, що її дані повернулися в порядку.

Іноді для даних потрібні перевірки діапазону, перевірки цілісності тощо. Написання цих функцій самостійно дозволяє нам все це робити. У цьому випадку ми не хочемо і не потребуємо Ломбока, тому що ми все робимо самі.

Однак часто виявляється, що встановлений ззовні параметр зберігається в одній змінній. У цьому випадку вам знадобиться чотири функції, щоб отримати / встановити / серіалізувати / зняти серіалізм вміст цієї змінної. Написання цих чотирьох функцій самостійно щоразу сповільнює і схильне до помилок. Автоматизація процесу за допомогою Lombok прискорює ваш розвиток та усуває можливість помилок.

Так, цю змінну можна було б оприлюднити. У цій конкретній версії коду він був би функціонально ідентичним. Але повернімося до того, чому ми використовуємо функції: "Ми можемо захотіти змінити, як клас робить речі під поверхнею ..." Якщо ви зробите свою змінну загальнодоступною, ви обмежуєте свій код зараз і назавжди, щоб ця публічна змінна була як інтерфейс. Якщо ви використовуєте функції, або якщо ви використовуєте Lombok для автоматичного генерування цих функцій для вас, ви можете будь-коли змінити основні дані та базову реалізацію.

Це робить це зрозумілішим?


Ви говорите про питання студента з ПЗ першого курсу. Ми вже в іншому місці. Я б ніколи цього не сказав і навіть дав би вам плюс за розумні відповіді, якби ви не були такі неввічливі у формі своєї відповіді.
Gangnus

0

Я насправді не розробник Java; але наступне - це значною мірою агностик платформи.

Майже в усьому, що ми пишемо, використовуються загальнодоступні користувачі та сетери, які мають доступ до приватних змінних. Більшість геттерів та сетер тривіальні. Але коли ми вирішимо, що сеттеру потрібно щось перерахувати, або сетер робить деяку перевірку або нам потрібно переслати властивість до властивості змінної члена цього класу, це повністю не порушує весь код і є бінарним сумісним, тому ми може замінити цей модуль.

Коли ми вирішимо, що ця властивість дійсно повинна бути обчислена з ходу, весь код, який на неї дивиться, не повинен змінюватися, і лише код, який пише їй, повинен змінитися, і IDE може знайти її для нас. Коли ми вирішимо, що це обчислюване поле, що записується (тільки коли-небудь це потрібно було робити кілька разів), ми можемо це зробити і. Приємно, що деякі з цих змін є бінарними сумісними (перехід на обчислюване поле лише для читання - не в теорії, але все одно може бути на практиці).

Ми закінчили багато-багато тривіальних геттерів зі складними сетерами. Ми також закінчилися з нечисленними дітьми кешування. Кінцевим результатом є те, що вам дозволяється припускати, що геттери досить дешеві, але сетери можуть бути. З іншого боку, ми досить розумні, щоб вирішити, що сеттер не зберігається на диску.

Але мені довелося відшукати хлопця, який сліпо змінив усі змінні учасника на властивості. Він не знав, що таке атомне додавання, тому він змінив те, що дійсно потрібно було публічною змінною властивості, і тонко порушив код.


Ласкаво просимо у світ Java. (C # теж). * зітхати *. Що стосується використання get / set для приховування внутрішнього зображення, будьте обережні. Йдеться лише про зовнішній формат, це нормально. Але якщо мова йде про математику, або, що ще гірше, фізику чи іншу реальну річ, різні внутрішні уявлення мають різні якості. саме мій коментар до softwareengineering.stackexchange.com/a/358662/44104
Gangnus

@Gangnus: Цей приклад є найбільш прикрим. У нас було досить багато таких, але розрахунок завжди був точним.
Джошуа

-1

Геттери та сетери - це "на всякий випадок", доданий для того, щоб уникнути рефакторингу в майбутньому, якщо внутрішня структура або вимоги доступу змінюються в процесі розробки.

Скажімо, через кілька місяців після випуску ваш клієнт повідомляє вам, що одне з полів класу інколи встановлюється на негативні значення, хоча в такому випадку він повинен бути максимум 0. Використовуючи загальнодоступні поля, вам доведеться шукати кожне призначення цього поля у всій базі коду, щоб застосувати функцію затискання до значення, яке ви збираєтеся встановити, і пам’ятайте, що вам це доведеться завжди робити, змінюючи це поле, але це смокче. Натомість, якби ви вже використовували getters та setter, ви могли б просто змінити метод setField (), щоб забезпечити завжди це затискання.

Тепер проблема з "старовинними" мовами, такими як Java, полягає в тому, що вони заохочують використовувати методи для цієї мети, що просто робить ваш код нескінченно більш багатослівним. Боляче писати і важко читати, саме тому ми використовуємо IDE, які так чи інакше пом'якшують цю проблему. Більшість IDE автоматично генерують ваші геттери та сетери, а також приховують їх, якщо не вказано інше. Lombok піде на крок далі та просто генерує їх процедурно під час компіляції, щоб зберегти ваш код додатковим елементом. Однак інші сучасніші мови так чи інакше вирішили це питання. Наприклад, мови Scala або .NET,

Наприклад, у VB .NET або C # ви можете просто зробити так, щоб усі поля мали загальнодоступні побічні ефекти - одержувачі та налаштування просто загальнодоступні поля, а пізніше зробити їх приватними, змінити ім’я та відкрити властивість із попередньою назвою поле, де ви можете тонко налаштувати поведінку поля, якщо вам це потрібно. З Lombok, якщо вам потрібно тонко налаштувати поведінку геттера чи сеттера, ви можете просто видалити ці теги, коли це потрібно, і кодувати свої власні нові вимоги, точно знаючи, що вам не доведеться нічого переробляти в інших файлах.

В основному, спосіб вашого методу доступу до поля повинен бути прозорим і рівномірним. Сучасні мови дозволяють вам визначати "методи" з однаковим синтаксисом поля доступу / виклику поля, тому ці зміни можна робити на вимогу, не задумуючись над цим під час ранньої розробки, але Java змушує вас робити цю роботу достроково, оскільки це робить не мають цієї функції. Все, що робить Lombok, це економить ваш час, оскільки мова, якою ви користуєтеся, не хотіла економити час на метод «на всякий випадок».


У цих "сучасних" мовах API виглядає як польовий доступ, foo.barале з ним може оброблятися виклик методу. Ви стверджуєте, що це перевершує "стародавній" спосіб створення API схожим на виклики методу foo.getBar(). Ми, мабуть, погоджуємось, що публічні поля є проблематичними, але я стверджую, що "застаріла" альтернатива перевершує "сучасну", оскільки весь наш API симетричний (це все викликає методи). При "сучасному" підході ми маємо вирішити, які речі повинні бути властивостями, а які - методами, що надмірно ускладнює все (особливо якщо ми використовуємо рефлексію!).
Варбо

1
1. Приховувати фізику не завжди так добре. дивіться мій коментар на softwareengineering.stackexchange.com/a/358662/44104 . 2. Я не говорю про те, наскільки важким чи простим є створення геттера / сетера. У C # є абсолютно та сама проблема, про яку ми говоримо. Відсутність інкапсуляції через погане рішення масового завантаження інформації. 3. Так, рішення може базуватися на синтаксисі, але, будь ласка, якимись іншими способами, ніж ви згадуєте. Це погано, у нас тут велика сторінка, заповнена поясненнями. 4. Рішення не повинно базуватися на синтаксисі. Це можна зробити в межах Java.
Gangnus

-1

Чи може хтось мені пояснити, який сенс ховати всі поля як приватні і після цього виставляти їх усі за допомогою якоїсь додаткової технології? Чому тоді ми просто не використовуємо лише загальнодоступні поля? Я відчуваю, що ми пройшли довгий і важкий шлях лише для того, щоб повернутися до початкової точки.

Так, це суперечливо. Я вперше зіткнувся з властивостями в Visual Basic. До цього моменту, з мого досвіду, іншими мовами не було обгортки власності. Просто публічні, приватні та захищені поля.

Властивості були свого роду інкапсуляцією. Я зрозумів, що властивості Visual Basic є способом контролю та маніпулювання виведенням одного або декількох полів, приховуючи явне поле і навіть його тип даних, наприклад, випромінюючи дату як рядок у певному форматі. Але навіть тоді не є "приховування стану та викриття функціональності" з точки зору більшого об'єкта.

Але властивості виправдовують себе завдяки окремим особам, які отримують власність та встановлюють. Розкривати поле без властивості було все або нічого - якщо ви могли прочитати його, ви можете змінити його. Тож тепер можна виправдати слабкий дизайн класу захищеними сетерами.

То чому ми / вони просто не застосовували фактичні методи? Тому що це було Visual BasicVBScript ) (oooh! Aaah!), Що кодує маси (!), І все це було лютістю. І таким чином ідіотократія врешті-решт панувала.


Так, властивості для користувальницького інтерфейсу мають особливий сенс, вони є загальнодоступними та гарними зображеннями полів. Влучне зауваження.
Gangnus

"Так чому ми / вони просто не застосовували фактичні методи?" Технічно вони зробили у версії .Net. Властивості - це синтаксичний цукор для функцій get і set.
Фарап

"ідіотократія" ? Ви не маєте на увазі " ідіосинкразія "?
Пітер Мортенсен

Я мав у виду Idiocracy
radarbob
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.