Почему закрытый член доступен в статическом методе?

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();

Почему вы можете сделать это в парадигме ООП и какая от этого польза?

Бен
источник
6
Почему бы не быть? В Java закрытые члены не являются частными для экземпляра, а являются частными для исходного файла . Первое, что приходит на ум equals, это проверка личных полей другого экземпляра. (Публикация в качестве комментария, так как это коротко, и ничего об ООП-
простоте
2
Обратите внимание, что статические методы не имеют this, поэтому единственные объекты их собственного класса, к которым они могут получить, это те, которые они создают сами (или которые передаются в качестве параметра). Так что, если вы считаете это нарушением инкапсуляции или дырой в безопасности, это не так, как если бы она была очень большой, и, возможно, ее не стоит включать.
Килиан Фот
4
Не уверен, почему за это проголосовали. Вопрос может быть тривиальным (иш), но у ОП возникла проблема с тестированием поведения на двух языках, прежде чем задавать вопрос. Это гораздо больше усилий, чем мы обычно видим от новичков.
Яннис
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
    }

}
Philipp
источник
Можете ли вы вспомнить ситуацию, когда шаблон данных частного класса полезен? Лично я использую внутренние классы только в графическом интерфейсе, такие как настройки (Swing и т. Д.) Или статические внутренние классы в упражнениях по кодированию, поскольку я не хочу, чтобы упражнение охватывало несколько исходных файлов.
InformedA
1
Скрытие внутренних элементов класса от других экземпляров лишает одно из преимуществ классов над интерфейсами, не возвращая ничего взамен. Более простое и гибкое решение - просто использовать интерфейс.
Довал
3

Некоторые языки и среды выполнения (например, Java, .NET) предполагают, что любому, кто компилирует код для определенного класса, можно доверять, чтобы он не использовал закрытые члены любого экземпляра этого класса способами, которые могли бы нанести ущерб его правильному операция. Другие языки и структуры являются более строгими в этом отношении и не разрешают доступ к закрытым членам экземпляра, за исключением кода, выполняемого в этом экземпляре . Обе конструкции имеют свои преимущества и недостатки.

Самое большое преимущество предоставления любому коду в классе доступа к закрытым членам любого экземпляра состоит в том, что существуют случаи, когда этот уровень доступа является подходящим, и такая privateработа устраняет необходимость либо иметь другой квалификатор доступа, доступный для этой цели, либо иначе заставьте код выставлять членов более широко, чем было бы в идеале.

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

Обратите внимание, что такой рефакторинг не мог бы быть предотвращен, если бы язык «позволял» ImmutableMatrixпроверять массив резервных копий для других thisслучаев, кроме случаев, когда язык использовал эту возможность и фактически проверял внешние экземпляры. Основной эффект ограничения языка таким использованием заключается в том, что компилятор немедленно закричит при любой попытке написать код, который не поддается такому рефакторингу.

Supercat
источник
2

Java не является строго объектно-ориентированным языком, но языком, основанным на классе - класс определяет операции и доступ к поведению, а не экземпляр.

Так что не удивляйтесь, что это позволяет вам делать вещи, которые не являются строго объектно-ориентированными.

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

Это унаследовано от C ++, где это полезно для создания конструкторов копирования и перемещения. Это также полезно для сравнения или объединения двух объектов, где их значение зависит от членов, которые не являются общедоступными эффективным способом (например, получатель для массива в Java должен копировать массив, чтобы клиентский код не мог его изменить, изменяя внутреннее состояние объекта, но не нужно копировать массивы для сравнения равенства объектов)

Пит Киркхэм
источник