Строго говоря, всегда ли инкапсуляция требует клонирования?
Нет, это не так.
Упоминаемый вами человек, вероятно, путает защиту состояния объекта с защитой деталей реализации объекта.
Запомните: инкапсуляция - это метод увеличения гибкости нашего кода. Хорошо инкапсулированный класс может изменить свою реализацию, не влияя на его клиентов . Это суть инкапсуляции.
Предположим, следующий класс:
class PayRoll {
private List<Employee> employees;
public void addEmployee(Employee employee) {
this.employees.add(employee);
}
public List<Employee> getEmployees() {
return this.employees;
}
}
Теперь этот класс имеет низкую инкапсуляцию. Можно сказать, что метод getEmployees нарушает инкапсуляцию, потому что, возвращая тип List, вы больше не можете изменить эту деталь реализации, не затрагивая клиентов класса. Я не мог изменить его, например, для коллекции Map, не влияя на код клиента.
Клонируя состояние вашего объекта, вы потенциально изменяете ожидаемое поведение клиентов. Это вредный способ интерпретации инкапсуляции.
public List<Employee> getEmployees() {
return this.employees.clone();
}
Можно сказать, что приведенный выше код улучшает инкапсуляцию в том смысле, что теперь addEmployee - единственное место, где можно изменить внутренний список. Поэтому, если у меня есть дизайнерское решение добавить новые элементы Employee в начало списка, а не в хвост. Я могу сделать эту модификацию:
public void addEmployee(Employee employee) {
this.employees.insert(employee); //note "insert" is used instead of "add"
}
Однако это небольшой шаг инкапсуляции за большую цену. У ваших клиентов создается впечатление, что у них есть доступ к сотрудникам, хотя на самом деле у них есть только копия. Поэтому, если бы я хотел обновить номер телефона сотрудника Джона Доу, я мог ошибочно получить доступ к объекту Employee, ожидая, что изменения будут отражены при следующем вызове PayRoll.getEmployees.
Реализация с более высокой инкапсуляцией будет делать что-то вроде этого:
class PayRoll {
private List<Employee> employees;
public void addEmployee(Employee employee) {
this.employees.add(employee);
}
public Employee getEmployee(int position) {
return this.employees.get(position);
}
public int count() {
return this.employees.size();
}
}
Теперь, если я хочу изменить Список для Карты, я могу сделать это свободно.
Кроме того, я не нарушаю поведение, которого, вероятно, ожидают клиенты: при изменении объекта Employee из PayRoll эти изменения не теряются.
Я не хочу расширять себя слишком сильно, но дай мне знать, ясно это или нет. Я был бы рад перейти к более подробному примеру.