Порушення інкапсуляції: Як змінювані об’єкти можуть зламати ваш код

pic

Поламана капсула

Щоб повністю зрозуміти контекст, давайте спершу розберемося, що таке Інкапсуляція!

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

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

Деяким з вас це може бути не зовсім зрозуміло, тому давайте подивимося на цей клас Employee.

Примітка: Клас java.util.Date, що використовується в цьому прикладі, вважається застарілим і подано тут лише для демонстраційних цілей, щоб показати концепцію змінюваності та її вплив на інкапсуляцію.

import java.util.Date;  

public class Employee {  
 private String name;  
 private double salary;  
 private Date joinedDate; // Змінюваний об'єкт  

 public Employee(String name, double salary, Date joinedDate) {  
 this.name = name;  
 this.salary = salary;  
 this.joinedDate = joinedDate;  
 }  

 public String getName() {  
 return name;  
 }  

 public void setName(String name) {  
 this.name = name;  
 }  

 public double getSalary() {  
 return salary;  
 }  

 public void setSalary(double salary) {  
 this.salary = salary;  
 }  

 public Date getJoinedDate() {  
 return joinedDate;  
 }  

 public void setJoinedDate(Date joinedDate) {  
 this.joinedDate = joinedDate;  
 }  
}

1. Приватні поля:

  • Поля name, salary і joinedDate позначені як private. Це означає, що ці поля не можна безпосередньо отримати чи змінити ззовні класу Employee.
Employee emp = new Employee("Alice", 50000, new Date());  
// Прямий доступ, наприклад, emp.name = "Bob"; не дозволяється.

2. Публічні методи отримання та встановлення значень:

  • Публічні методи (getName, setName, getSalary, setSalary, getJoinedDate, setJoinedDate) надаються для читання або зміни приватних полів. Ці методи виступають як контрольовані точки доступу.
emp.setName("Bob"); // Дозволено  
System.out.println(emp.getName()); // Дозволено

3. Контрольований доступ

  • Використовуючи методи отримання та встановлення значень, можна накладати обмеження або додавати перевірки при доступі або зміні полів.
  • Це є перевагою інкапсуляції. Вона дозволяє змінювати внутрішню реалізацію без впливу на будь-який інший код, крім конкретного методу.
  • Наприклад, можна змінити метод setSalary, щоб заборонити встановлення негативних зарплат.
public void setSalary(double salary) {  
 if (salary >= 0) {   
 this.salary = salary;  
 } else {  
 throw new IllegalArgumentException("Зарплата повинна бути позитивною");  
 }  
}

Проблема: Змінювані об'єкти руйнують інкапсуляцію

Змінюваний означає здатний змінювати свій стан.

Змінюваний об'єкт може змінювати свій стан після створення. Клас Date в Java є яскравим прикладом.

Давайте зрозуміємо, як реалізація класу Employee вищезазначена була порушена.

public class Main {  
 public static void main(String[] args) {  
 // Створюємо об'єкт Employee  
 Date date = new Date();  
 Employee employee = new Employee("Alice", 50000, date);  

 // Отримуємо joinedDate  
 Date culpritDateObject = employee.getJoinedDate();  
 culpritDateObject.setTime(0); // Модифікує дату на "Thu Jan 01 00:00:00 GMT 1970"  

 // Перевіряємо joinedDate Employee  
 System.out.println("Дата приєднання працівника: " + employee.getJoinedDate());  
 }  
}  

Вивід: Дата приєднання працівника: Thu Jan 01 00:00:00 GMT 1970

Зверніть увагу, що ви змогли змінити приватну властивість joinedDate без використання методу setJoinedDate(“..дата..”), просто використовуючи метод setTime() на змінюваному об'єкті culpritDateObject.

Хтось може запитати: "Як зміна culpritDateObject може вплинути на значення приватного joinedDate?"

Коли ви працюєте з об'єктами, ви працюєте з посиланнями на ці об'єкти. Ці посилання вказують на місце в пам'яті, де зберігається сам об'єкт.

Дозвольте пояснити вам,
1.
Прості типи: Для змінних простих типів (як-от int, float тощо) змінна безпосередньо містить значення.

int a = 10; // `a` містить значення 10

2. Типи за посиланням (Об'єкти): Для змінних об'єктних типів змінна містить посилання на місце в пам'яті, де зберігається об'єкт.

Date date = new Date(); // `date` містить посилання на місце в пам'яті об'єкта Date

Отже, в прикладі з класом Employee, коли ви використовуєте метод отримання значення, ви фактично повертаєте посилання на об'єкт дати, а не саме значення дати.

Внаслідок цього culpritDateObject отримує доступ до того ж місця в пам'яті, що й joinedDate, дозволяючи його змінювати.

Що сталося?

  • Метод отримання getJoinedDate() повернув посилання на внутрішній об'єкт joinedDate.
  • Зміна отриманого об'єкта Date безпосередньо вплинула на внутрішній стан об'єкта Employee.
  • Інкапсуляція порушена, оскільки зовнішній код змінив внутрішній стан.

Рішення: Оборонне копіювання

1. Що таке оборонне копіювання?

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

Змініть клас Employee наступним чином:

public class Employee {  
 private String name;  
 private double salary;  
 private Date joinedDate;  

 public Employee(String name, double salary, Date joinedDate) {  
 this.name = name;  
 this.salary = salary;  
 this.joinedDate = new Date(joinedDate.getTime()); // Копія при призначенні  
 }  

 public Date getJoinedDate() {  
 return new Date(joinedDate.getTime()); // Повертаємо оборонну копію  
 }  

 public void setJoinedDate(Date joinedDate) {  
 this.joinedDate = new Date(joinedDate.getTime()); // Копія при встановленні  
 }  
}

2. Використовуйте незмінювані об'єкти

Замість змінюваних класів, таких як Date, використовуйте незмінювані альтернативи, наприклад, LocalDatejava.time)

private LocalDate joinedDate;

Висновок

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

Перекладено з: Breaking Encapsulation: How Mutable Objects Can Compromise Your Code

Leave a Reply

Your email address will not be published. Required fields are marked *