Чому приватний член доступний статичним методом?


25

Далі йде псевдо-код, я спробував його в Java та PHP і обидва працювали:

class Test { 

    private int a = 5;

    public static function do_test(){
        var t = new Test();
        t.a = 1;
        print t.a // 1
    }

}

Test::do_test();

Чому ви можете це зробити в парадигмі OOP і в чому її користь?


6
Чому б цього не було? У Java приватні члени не є приватними, наприклад, приватними для вихідного файлу . Перше, що приходить на думку, - equalsце перевірка приватних полів іншого екземпляра. (Публікуємо як коментар, оскільки це коротко, і нічого про OOP-ності такого підходу)
Звичайний

2
Зауважте, що у статичних методів немає a this, тому єдиними об'єктами власного класу, до яких вони можуть потрапити, є ті, які вони створюють самі (або передаються як параметр). Тож якщо ви вважаєте це порушенням капсулювання або захисною дірою, це не так, як якщо б воно не було великим, і, можливо, не варто заїжджати.
Кіліан Фот

4
Не впевнений, чому це було знято. Питання може бути тривіальним (ish), але ОП пережило проблеми перевірки поведінки на двох мовах, перш ніж задавати питання. Це набагато більше зусиль, ніж ми зазвичай бачимо у новачків.
янніс

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

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

Відповіді:


17

У Java приватні змінні видимі для всього класу. До них можна отримати статичні методи та інші екземпляри того ж класу.

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

Однак, коли ви хочете приховати деталі реалізації класу навіть від статичних методів або від інших екземплярів цього класу, ви можете слідувати шаблону даних приватного класу . Помістіть усі приватні змінні класу в приватний внутрішній клас і делегуйте будь-які геттери або сеттери гетерам і сетерам цього внутрішнього класу.

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

interface ITest {
     public int getA();
}

class Test implements ITest { 

    private int a = 5;

    public int getA() { return a; } // implementation of method declared in interface

    public static void main(){
        ITest t = new Test();
        t.a = 1; // syntax error: Interface ITest has no "a"
        System.out.println(t.getA()); // calls Test.getA, visible because ITest declares it
    }

}

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

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

3

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

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

Перевагою заборони такого доступу (як це було у звичайній об'єктній моделі Microsoft (COM)) є те, що він дозволяє зовнішньому коду трактувати класи як інтерфейси. Якщо клас ImmutableMatrixмістить приватне або захищене double[][]резервне поле і якщо код у класі вивчає резервний масив інших екземплярів, то неможливо визначити клас ZeroMatrix, IdentityMatrixякий не підтримується масивом (наприклад , ), який зовнішній код може використовувати як an Immutable2dMatrix, без цього класу не потрібно включати резервне поле. Якщо нічого в межах не Immutable2dMatrixвикористовується приватних членів будь-якого іншого, крім іншого this, можна було б перейменувати клас у ImmutableArrayBackedMatrixта визначити новий абстрактний ImmutableMatrixклас, який міг би мати ImmutableArrayBackedMatrixяк і вищезазначені класи, не підтримувані масивом, як підтипи.

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


2

Java - не суто об'єктно-орієнтована мова, а мова на основі класу - клас визначає операції та доступ до поведінки, а не екземпляр.

Тому не варто надто дивуватися, що це дозволяє робити речі, не суто орієнтовані на об'єкти.

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

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

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