JavaRush/Java блог/Архив info.javarush/Геттеры и нарушенная инкапсуляция
Vadelic
14 уровень

Геттеры и нарушенная инкапсуляция

Статья из группы Архив info.javarush
участников
Решая задачи, я столкнулся с неожиданным поведением геттеров. В здешних лекциях и даже в генерируемых IntelliJ IDEA геттерах используется простейшая схема: public final class A { private A field; public A getField() { return field; } public void setField(A field) { this.field = field; } } Если поле примитивного типа или String, которые передаются по значению, то всё в порядке, но если объект передаётся по ссылке, то получается, что через геттер можно получить полный доступ к полю, что сводит на нет установленный уровень доступа protected. Если в сеттере стоит какое-либо условие валидности, то через такой геттер его можно обойти, что нарушает инкапсуляцию и правильно было бы создавать клон объекта, что бы избежать этой неприятности. Понимаю, что не все объекты просто клонировать и если это отдаётся на ответственность программиста, то странно почему об этом нигде в лекциях не встречалось упоминаний. UPD: Геттер, как идея, конечно же, не нарушает инкапсуляцию, но написан он должен быть так, что бы не предать принципов ООП. Это значит, что если геттер выдаёт объект, то таким образом, что бы пользователь не смог его изменить никаким другим способом, который прописан в классе. В классе Collections существует замечательный метод public static Collection unmodifiableCollection(Collection c) который выдаёт read-only коллекцию. Это накладывает ряд других ограничений, например, невозможность сортировки, но на этот случай в этом классе есть: unmodifiableSortedSet unmodifiableSortedMap и ещё несколько, для большего удобства.
Комментарии (35)
  • популярные
  • новые
  • старые
Для того, чтобы оставить комментарий Вы должны авторизоваться
yag0andy
Уровень 41
21 января 2016, 15:06
Не совсем понял, в чем нарушается инкапсуляция?
Вы получаете геттером ссылку на экземпляр класса А. Класс А тоже имеет внутреннюю структуру, разные модификаторы доступа… Скажем, доступа к приватным полям класса А вы не получаете через геттер. Все норм… Геттер просто возвращает ссылку на значение в поле А… Что не так? В чем противоречие?

Если бы всё было в А скрыто — то какой толк в такой ссылке? Как с ней работать?
Vadelic
Уровень 14
21 января 2016, 15:16
В том то и дело, что объект передаётся геттером по ссылке и полученный объект можно изменить из-за чего изменится и объект(ведь эти две ссылки указывают на один и тот же объект в хиппе), на который ссылается field.
Вот например такой код:
class A{

    public static void main(String[] args) {
        System.out.println("Original: " + B.getList());

        ArrayList<String> edit = B.getList();
        edit.add("two");

        System.out.println("Changed: " + B.getList());


        edit = B.getListSafe();
        edit.add("three");
        System.out.println("Not changed(safe Getter): " + B.getList());
    }
}

class B {
    private static ArrayList<String> list = new ArrayList<>();

    static {
        list.add("one");
    }

    public static ArrayList<String> getList() {
        return list;
    }

    public static ArrayList<String> getListSafe() {
        return new ArrayList(list);
    }

}


Вывод будет таким:

Original: [one]
Changed: [one, two]
Not changed(safe Getter): [one, two]
yag0andy
Уровень 41
21 января 2016, 15:16
Мне кажется вы не совсем верно поняли суть понятия инкапсуляции.
Vadelic
Уровень 14
21 января 2016, 15:25
Прошу пояснить, для того и написал этот пост, что бы разобраться.
yag0andy
Уровень 41
21 января 2016, 16:01
Я понимаю инкапсуляцию как в первую очередь сокрытие реализации. Т.е. внешний пользователь класса (например другой программист) должен иметь доступ к тем методам и данным класса, которые можно безопасно менять, не боясь все сломать. Метод приватным объявляется не для того, что бы от кого-то что-то спрятать — а для того, что бы в первую очередь сказать — вот тебе игрушка, из неё торчат лапки, ушки и кой-чё ещё… За это можно дергать, а в кишках ковыряться не надо — можно сломать. Вдруг я решу в следующей версии избавиться от одних внутренностей и добавить новые? А публичные методы я уже менять не буду. И пользователь класса этого даже не заметит. Хотя как указал коллега выше, если сильно надо, то поковыряться в кишках можно. Но это уже на свой страх и риск с помощью спецсредств.
Vadelic
Уровень 14
21 января 2016, 16:13
Всё верно, только через такой геттер, который дарует ссылку на объект, можно сломать.
Если в классе есть какой-то список, который нельзя пользователю класса менять, но есть геттер, как на примере выше, то через полученную таким геттером ссылку можно изменить этот список.
ПС: Пример тестил?
yag0andy
Уровень 41
21 января 2016, 16:36
Что значит сломать список? Если там список «который нельзя менять», возможно вам не нужен геттер в таком виде, возможно вообще пользователю класса не нужен список, или непосредственный доступ к списку.
Короче, не вижу проблемы…
Давайте только необходимый доступ…
Vadelic
Уровень 14
21 января 2016, 16:52
Тут очень много примеров подобных:

private ArrayList<Integer> field;

ArrayList<Integer> getField(){
return field;
}

void setField(Integer ext){
//тут условие валидности
if (ext!=0)
field.add(ext);
}


Но при таком геттере вполне возможно:

ArrayListtest = detField();
test.add(0);

И в конце поста я предполагаю, что достаточно написать более безопасный геттер, но меня изумляет, что тут в примерах методично используют вот такие небезопасные геттеры и ни разу эта ситуация не комментировалась в лекциях
yag0andy
Уровень 41
21 января 2016, 17:05
Так можно сломать… Согласен. Но! Я бы «setField» тогда переименовал бы в «add» (а то сбивает с толку), и задался бы вопросом — а зачем пользователю мой список? Что он будет с ним делать? Если всё что угодно, и это может сломать мой класс — то возможно надо не ArrayList? А применить паттерн Декоратор и нарисовать свой класс MyArrayLisr и в переопределенный метод add (и остальные нужные методы) напихать своих проверок, а ненужные методы закрыть или не переопределять? И внутренний ArrayList сделать приватным? На эту тему кстати есть урок, если ещё не дошли

То что указано в примерах — это ж примеры, они для простоты, особенно на старте… Не будьте так уж строги
Vadelic
Уровень 14
21 января 2016, 17:19
что бы обезопасить данный пример, достаточно написать
return new ArrayList(Field)

Тут очень хорошие примеры, странно что к такому важному моменту не уделено должного внимания.
Возможно что на практике всё не так как в учебке, но это ещё только предстоит узнать.
К размышлению подтолкнула одна задача, в которой надо было изменить поле через полученную ссылку геттером, что, как мне кажется, не совсем адекватно.
Fry
Уровень 41
21 января 2016, 17:22
если ты пишешь геттер к полю, значит ты уже отрываешь доступ к полю сам. Значит в твоей реализации предположено менять список. Если менять его не хочешь то не пишешь гетер и поле остается закрытое. Без инкапсуляции как ты закроешь поле? Вот если нет модификаторов доступа (нет реализации в ЯП), как тогда?
yag0andy
Уровень 41
21 января 2016, 17:28
Это не всегда прокатит. А что если у вас в вашем списке миллион объектов, и не int, а что-нить потяжелее? Будете копировать? А оно надо?
Vadelic
Уровень 14
21 января 2016, 17:28
В примерах геттеры в 100% случаев (минус одна задача) используется исключительно для того, что бы прочесть данные, а доступ к полю предоставляется полный, в том числе и для редактирования.
Такое впечатление, что это получается как побочный результат, побочный и вредный.
yag0andy
Уровень 41
21 января 2016, 17:30
удалено
yag0andy
Уровень 41
21 января 2016, 17:30
Вот умеют же люди кратко излагать! Я как начну сопли по столу размазывать!
+1 короче
Vadelic
Уровень 14
21 января 2016, 17:33
Думаю, что правильный вариант — написать такую реализацию геттера, что бы и память не сожрать и не предать принципов ООП. А в таком геттере я вообще не вижу особого смысла, можно просто пабликом объявит
yag0andy
Уровень 41
21 января 2016, 17:37
Можно… Просто вы поймите… У вас список. Ну закрыли вы его… Даже скопировали! Но в списке объекты… Возможно какие-то классы… Их тоже будете копировать? А там могут быть свои ссылки! Выходит вообще утопия, потому что по факту так никто не делает! Значит надо проектировать класс без геттера, а что бы он давал те интрументы по работе с вашим приватным списком, которые нужны. Надеюсь, тема закрыта.
Fry
Уровень 41
21 января 2016, 17:40
Если ты про 5 — ый уровень, то тут рассказывают о том что такое геттер и сеттер, и как им пользоваться. О инкапсуляции расскажут тебе в 11 — ом уровне более детально. Информацию надо подавать порционно.
Vadelic
Уровень 14
21 января 2016, 17:54
Нет, не про 5й. Такие штуки и на 27м встречаются и мне кажется, что так писать и использовать геттеры не верно, вот и хотел бы узнать мнения знатоков.
Vadelic
Уровень 14
14 февраля 2016, 00:46
Вот теперь тема закрыта. В классе Collections существует замечательный метод
<code><span class="kwd">public</span><span class="pln"> </span><span class="kwd">static</span><span class="pln"> </span><span class="pun"><</span><span class="pln">T</span><span class="pun">></span><span class="pln"> </span><span class="typ">Collection</span><span class="pun"><</span><span class="pln">T</span><span class="pun">></span><span class="pln"> unmodifiableCollection</span><span class="pun">(</span><span class="typ">Collection</span><span class="pun"><?</span><span class="pln"> </span><span class="kwd">extends</span><span class="pln"> T</span><span class="pun">></span><span class="pln"> c</span><span class="pun">)</span></code>
который действует как в Си final — выдаёт read-only коллекцию. Это накладывает ряд других ограничений, например( на этот случай есть unmodifiableSortedSet unmodifiableSortedMap), невозможность сортировки, но полностью сохраняет инкапсуляцию класса.
Fry
Уровень 41
21 января 2016, 14:48
Открою тебе маленький секрет, даже к приватным полям можно получить доступ, и поменять значение, без геттеров и сеттеров, и даже можно поменять адрес в памяти.
Vadelic
Уровень 14
21 января 2016, 14:55
Эти способы доступа тоже нарушают инкапсуляцию класса?
Fry
Уровень 41
21 января 2016, 15:04
нет
Fry
Уровень 41
21 января 2016, 15:04
и то что ты описал, тоже не нарушает инкапсуляцию класса
Vadelic
Уровень 14
21 января 2016, 15:07
Не мог бы ты более аргументировано развернуто каментить?))
Почему ты считаешь, что нет? С таким успехом можно оставить поле public и не создавать геттеры и сеттеры.
Fry
Уровень 41
21 января 2016, 15:15
Инкапсуляция означает, что данные объекта недоступны его клиентам непосредственно. Вместо этого они инкапсулируются — скрываются от прямого доступа извне. Инкапсуляция предохраняет данные объекта от нежелательного доступа, позволяя объекту самому управлять доступом к своим данным.

Ну если ты создал геттеры и сеттеры то ты даешь доступ сам к полям (косвенный), не напрямую. и в этих методах можешь сделать себе нужные проверки. Да и методы можешь сделать не только публичные.
Vadelic
Уровень 14
21 января 2016, 15:27
Так в данном случае не происходит никакого предохранения. Если в сеттере могу быть проверки, то через полученную ссылку на объект геттером его можно менять в обход проверок установленных в сеттере.

Геттер, который передаёт объект по значению не представляет опасности для инкапсуляции, но геттер передающий объект по ссылке — это же нарушение её.
Fry
Уровень 41
21 января 2016, 15:35
Что тебе мешает написать проверку в геттере? Что тебе мешает совсем не писать геттер? Что тебе мешаешь сделать геттер protected или package?

Нужен или нет, ты сам решаешь, точно также реализацию. Главное чтобы не было прямого доступа к полям. А что открывать через методы, решаешь уже ты.
Vadelic
Уровень 14
21 января 2016, 15:42
В джаве по значению передаются только примитивные типы и String.

Мне интересно что думают на этот счёт знатоки, учитывая что тут в задачах очень часто используются геттеры, которые выдают ссылки на приватные поля. У меня есть предположение, почему ИДЕА генерирует такие геттеры (в конце поста написал) но удивляет, что в здешних лекциях про это ни слова не было.
Fry
Уровень 41
21 января 2016, 15:43
в джаве всегда передается по значению.
Fry
Уровень 41
21 января 2016, 15:56
Повторяю тебе ещё раз, в джаве всегда передается по значению. Почитай спеку по JVM, если мне не веришь. Или хотя бы то что я тебе скинул. Если ты спрашиваешь у других, и при этом не принимаешь информацию, тогда зачем создавать тему?
Vadelic
Уровень 14
21 января 2016, 16:31
Ссылки, указатели, значения — терминология в джаве несколько нарушена от других языков и я использую терминологию Си.
Не хотелось бы спорить по терминам.
«По ссылке» я подразумеваю передачу указателя на объект в хиппе:

public class A {
    private static ArrayList<Integer> field = new ArrayList<>();

    public static void main(String[] args) {
        ArrayList<Integer> test = getField();
        test.add(1);
        System.out.println(field);
    }
   static ArrayList<Integer> getField(){
       return field;
   }
}


В джаве:
MyClass myClass;
тоже самое что в СИ:
MyClass *myClass;
Fry
Уровень 41
21 января 2016, 16:37
Указателей в джаве нет. Таких как в Си
Ссылки в джаве в каком то частном случае являюся указателями (Как в С). Но в си указатели всегда занимают 4 байта, и по сути это всегда инт, и в своей середине имеет адрес памяти куда ссылается.

В джаве там совсем по другому.

В джаве при передаче обьекта в метод как параметр, создается новая ссылка что указывает на тот же обьект, таким образом происходит передача по значению обьектов. Ссылки разные, обьект один и тот же.
Vadelic
Уровень 14
21 января 2016, 16:59
С терминами вроде как разобрались.
Вернёмся к сабжу:
private ArrayList<Integer> field;

ArrayList<Integer> getField(){
return field;
}

void setField(Integer ext){
if (ext!=0)
field.add(ext);
}

void editField(){
ArrayListtest = detField();
test.add(0);
}


В примере метод editField() добавляет в список field неверный элемент «0».