суббота, 16 февраля 2019 г.

7 Что такое инкапсуляция. Геттеры и сеттеры (Getters and setters)

   Здесь вы узнаете об инкапсуляции в Java.


   Инкапсуляция (от лат. in capsula; «в коробочке») - это размещение в оболочке, изоляция, закрытие чего-либо инородного с целью исключения влияния на окружающее.
   К примеру, поместить радиоактивные отходы в капсулу, закрыть кожухом механизм, убрать мешающее в шкаф.

   В общем случае в языках программирования термин «инкапсуляция» относится к одному или двум утверждениям сразу:
   - это механизм языка, позволяющий ограничить доступ одних компонентов программы или пользователей к другим;
   - это языковая конструкция, позволяющая связать данные с методами, предназначенными для обработки этих данных.

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

   //не делайте так :)

   Инкапсуляция защищает переменные экземпляра (требуемые поля) от передачи им (получения ими) недопустимых или нежелательных значений, позволяя внести изменения администратором спустя некоторое время.
   Например, инкапсуляция может понадобится, если переменные экземпляра могут принимать только определённый диапазон значений (так возраст не может быть отрицательным).

   Таким образом, инкапсуляция нужна, чтобы обеспечивать корректное состояние объекта и скрывать переменные от прямого (непосредственного) доступа извне.


   Инкапсуляцию в Java реализуют (обеспечивают) при помощи служебных слов-модификаторов доступа (private, protected, package default, public), явно задающие область видимости каждого поля и метода класса (которые позволяют ограничивать доступ к элементам данных ("прятать" данные)).

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

   Общедоступные методы-геттеры и методы-сеттеры (getters and setters) используют для того, чтобы предоставить опосредованный (через использование методов) контролируемый доступ к важным переменным и их обновление.

   Механизм действия инкапсуляции таков: для того, чтобы защитить объекты от нежелательных и недопустимых изменений она вынуждает остальной код обращаться к этим объектам не напрямую, а опосредованно, с помощью методов-сеттеров, в которые добавляют условия, гарантирующие допустимые значения полей.
 
   Переменная, скрытая модификатором с ограниченным доступом (например, private) и доступна только через геттер и сеттер, инкапсулирована.
 
   Некоторые фреймворки такие, как Hibernate, Spring, Struts и т. д. могут проверять информацию или внедрять свой служебный код через геттер и сеттер, поэтому при интеграции кода с ними необходимо иметь эти методы.

   Также целью инкапсуляции является разрыв зависимости публичных методов класса (то, что могут использовать другие классы) от внутренних методов (скрытой части), реализующих функциональность, которая не должна быть видна другим классам. Это означает, что детали реализации скрыты от клиентского кода. Один из аспектов, касающихся реализации, которые нужно помнить, заключается в следующем: изменения в реализации не должны требовать внесения изменений в пользовательский код. Если публичные методы спроектированы надлежащим образом, то изменения в реализации не должны требовать внесения изменений в пользовательский код.

    Это сделано для того чтобы малейшее изменение в классе (например изменение реализации способа хранения данных или их обработки и тд) не влекло за собой изменения в работе класса-клиента. Т.е. пользователи класса не должны страдать от его внутренних изменений.

 

Достоинства:

  • сокрытие реализации позволяет сократить временные затраты на поиск ошибок: если мы контролируем доступ к атрибуту, то при возникновении проблемы нам не придется беспокоиться об отслеживании каждого фрагмента кода, который мог бы изменить значение соответствующего атрибута — оно может быть изменено только в одном месте (с помощью сеттера)

  • также инкапсуляция заставляет пользователя играть по правилам, показывая ему только необходимые методы и скрывая не нужные

  • инкапсуляция отделяет изменяемую часть класса от постоянной. Данное свойство полезно, т.к. позволяет безболезненно для пользователя менять код скрытых методов без каких-либо для него проблем, т.к. сигнатуры публичных методов, которые ему доступны, не меняются, а изменяются только скрытые (приватные) методы

Недостатки:

  • необходимо писать дополнительный код в виде геттеров и сеттеров, которые зачастую захламляют код

 


   О служебных словах-модификаторах доступа я написал здесь: https://javaika.blogspot.com/2018/12/3-java.html

   Метод-геттер (Getter - с англ. получатель) - это метод, с помощью которого получают (считывают) значение переменной, доступ к которой напрямую ограничен.
    Геттер иногда называют accessor.
   
  
   Метод-сеттер (Setter, от англ.set - устанавливать) - это метод, с помощью которого задают или изменяют значение переменной (присваивают какое-либо значение инкапсулированному полю), например, обработав при этом недопустимые присваивания.
   Сеттер иногда называют mutator.

   Они могут предоставлять дополнительные функции: проверка корректности значения перед его присваиванием полю или обработка ошибок.
   Внутри метода-сеттера можно делать что угодно: например, отвергнуть недопустимое значение, сгенерировать исключение, округлить параметр до минимально допустимого значения и т.п.
   Таким образом, мы можем добавляется условная логику и обеспечить поведение в соответствии с потребностями (если сеттер не имеет подобной логики, а лишь присваивает полю какое-то значение, то его наличие не обеспечивает инкапсуляцию, а его присутствие становится фиктивным).
   При реализации только геттера (без сеттера) можно достичь неизменяемости объекта.

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

 
   Пример метода-геттера и метода-сеттера:

сlass Telefon {
    private int telefon_nomer;
    private int telefon_balance;
    // getBalance () – это метод getter, который считывает значение переменной account_balance:
    public int getBalance () {
       return this.telefon_balance;
    }
    // setNomer () – это метод-сеттер, который устанавливает или обновляет значение переменной telefon_nomer:
    public void setNomer (int num) {
       this.telefon_nomer = num;
    }
}
 



   Правила именования геттеров и сеттеров


   Принцип именования геттеров и сеттеров должен соответствовать конвенции Java об именовании:
   Геттеры именуются так:
      "get" + имя переменной, для которой реализуется этот метод с большой буквы (например, getMoney, getCake).
   Если переменная имеет тип boolean, то геттер будет содержать префикс is:
      "is" + имя переменной, для которой реализуется этот метод с большой буквы (например, isMoney, isCake)
 
   Сеттеры именуются так:
      "set" + имя переменной, для которой реализуется этот метод с большой буквы (например, setMoney, setCake).

   Метод геттер не имеет параметров (т.е. в скобках ничего не пишется) и возвращает значение одной переменной (одного поля).
   Метод сеттер всегда имеет модификатор void и только один параметр, для изменения значения одного поля.

   *Вместо прописывания геттеров и сеттеров вручную можно генерировать их с помощью IDE и различных пакетов.
 
 
 
   Использование геттеров и сеттеров для public-полей делает их бесполезными.
   Если в сеттер передаётся ссылка на объект, то нужно не копировать её во внутреннюю переменную напрямую, а, вместо этого, делать её копию (например, используя метод copyOf или copyOfRange) и только тогда присваивать её полю.
   Возврат геттером ссылки на объект: не возвращайте ссылку на исходный объект в геттере, а возвращайте копию.
 
 
 
   Реализация геттеров и сеттеров для:
 
    - переменных примитивных типов

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

    Как и для примитивных типов, можно безопасно реализовать геттер и сеттер для переменной String.
    (String - это immutable-тип - после создания объекта этого типа, его значение нельзя изменить, и любые изменения будут приводить к созданию нового объекта String)

    Для объектов типа Date внешние классы не должны иметь доступ к их оригиналам, т.к. объекты класса java.util.Date являются изменяемыми.
   Данный класс реализует метод clone() из класса Object, который возвращает копию объекта, но использовать его для этих целей не стоит.
    Поскольку Date не является окончательным классом, нет га­рантии, что метод clone() возвратит объект, класс которого именно java.util.Date: он может вернуть экземпляр ненадежного подкласса, созданного специально для нанесения ущерба. Такой подкласс может, например, записы­вать ссылку на каждый экземпляр в момент создания последнего в закрытый статический список, а затем предоставить злоумышленнику доступ к этому списку. В результате злоумышленник получит полный контроль над всеми эк­земплярами копий. Чтобы предотвратить атаки такого рода, не используйте метод clone() для создания копии параметра, тип которого позволяет нена­дежным сторонам создавать подклассы.
    Таким образом для объектов типа Date нужно создавать каждый раз новый экземпляр и работать с ним.
 
    - коллекций
 
     Для коллекций из элементов типа String не требуется специальной реализации, так как объекты этого типа неизменяемы (immutable).
     Для коллекции из String'ов одним из решений проблемы, при которой коллекция может быть изменена из кода, находящегося за пределами геттера и сеттера, является использование конструктора, который принимает другую коллекцию в качестве аргумента.
   При создании новой коллекции на основе имеющейся, объекты, размещенные в оригинальной коллекции, не будут скопированы. Вместо этого в новую коллекцию будут скопированы только ссылки на эти объекты. И хоть в итоге две коллекции и различны, но содержат одни и те же объекты.
 
    Для коллекций пользовательских типов данных необходимо учитывать следующие ключевые моменты:
        -  самостоятельно реализовать метод clone()
        - в сеттере добавить клонирование элементов из исходной коллекции в конечную
        - в геттере создать новую возвращаемую коллекцию, используя клонирование элементов из исходной коллекции в новую
        
   Так, например, ArrayList, HashMap, HashSet и т. д. реализуют свои собственные методы clone(). Эти методы якобы возвращают копии, но на самом деле копируют лишь ссылки (происходит неглубокое копирование). Об этом прямо написано в Javadoc метода clone() класса ArrayList:
   "возвращает копию этого экземпляра ArrayList (сами элементы не копируются)".
    Таким образом, нельзя использовать метод clone() этих классов коллекций и необходимо самостоятельно реализовать метод clone().
 

    -  собственных классов

    Если вы создаете объект своего пользовательского класса, вам следует для него реализовать метод clone().
    Правила реализации геттера и сеттера для собственного класса:
        -  Реализуйте метод clone() самостоятельно
        - Возвращайте клонированный объект из геттера
        - Используйте клонированный объект в сеттере
 
 
   Подробнее с данной темой можно ознакомиться здесь: https://topjava.ru/blog/gettery-i-settery-v-java

Комментариев нет:

Отправить комментарий

Наследование в Java

   «родительское/дочернее»     Наследование (inheritance) - свойство системы, позволяющее описать (создать) новый класс на основе уже су...