Смысл инкапсуляции не в том, что вы не должны знать или изменить состояние объекта снаружи объекта, но что вы должна иметь разумную политику для этого.
Некоторые члены данных могут быть полностью внутренними по отношению к объекту и должны не имеют ни геттеров, ни сеттеров.
Некоторые члены данных должны быть доступны только для чтения, поэтому им могут понадобиться геттеры, но не сеттеры.
Некоторые члены данных, возможно, должны быть согласованы друг с другом. В в таком случае вы бы не предоставили сеттер для каждого, а один способ их одновременной установки, чтобы можно было проверить значения согласованности.
Некоторые члены данных могут быть изменены только определенным образом, например
увеличивается или уменьшается на фиксированную величину. В этом случае вы
предоставит метод increment()
и/или decrement()
, а не
чем сеттер.
Тем не менее, другим может действительно потребоваться чтение-запись, и у них будет как геттер, так и геттер. и сеттер.
Рассмотрим пример class Person
. Допустим, у человека есть имя, номер социального страхования и возраст. Допустим, мы не позволяем людям когда-либо менять свои имена или номера социального страхования. Однако возраст человека следует увеличивать на 1 каждый год. В этом случае вы должны предоставить конструктор, который будет инициализировать имя и SSN заданными значениями, а также инициализировать возраст до 0. Вы также должны предоставить метод incrementAge()
, который увеличит возраст на 1. Вы также предоставить геттеры для всех трех. Никаких сеттеров в этом случае не требуется.
В этом дизайне вы позволяете проверять состояние объекта извне класса и разрешаете изменять его извне класса. Однако вы не позволяете произвольно изменять состояние. Существует политика, которая фактически гласит, что имя и SSN вообще нельзя изменять, а возраст можно увеличивать на 1 год за раз.
Теперь допустим у человека тоже есть зарплата. И люди могут менять работу по своему желанию, а значит, изменится и их зарплата. Чтобы смоделировать эту ситуацию, у нас нет другого пути, кроме как предоставить метод setSalary()
! Позволить себе изменять зарплату по своему усмотрению — вполне разумная политика в данном случае.
Кстати, в вашем примере я бы дал классу Fridge
методы putCheese()
и takeCheese()
вместо get_cheese()
и set_cheese()
. Тогда у вас все равно будет инкапсуляция.
public class Fridge {
private List objects;
private Date warranty;
/** How the warranty is stored internally is a detail. */
public Fridge( Date warranty ) {
// The Fridge can set its internal warranty, but it is not re-exposed.
setWarranty( warranty );
}
/** Doesn't expose how the fridge knows it is empty. */
public boolean isEmpty() {
return getObjects().isEmpty();
}
/** When the fridge has no more room... */
public boolean isFull() {
}
/** Answers whether the given object will fit. */
public boolean canStore( Object o ) {
boolean result = false;
// Clients may not ask how much room remains in the fridge.
if ( o instanceof PhysicalObject ) {
PhysicalObject po = (PhysicalObject)o;
// How the fridge determines its remaining usable volume is a detail.
// How a physical object determines whether it fits within a specified
// volume is also a detail.
result = po.isEnclosedBy( getUsableVolume() );
}
return result;
}
/** Doesn't expose how the fridge knows its warranty has expired. */
public boolean isPastWarranty() {
return getWarranty().before( new Date() );
}
/** Doesn't expose how objects are stored in the fridge. */
public synchronized void store( Object o ) {
validateExpiration( o );
// Can the object fit?
if ( canStore( o ) ) {
getObjects().add( o );
}
else {
throw FridgeFullException( o );
}
}
/** Doesn't expose how objects are removed from the fridge. */
public synchronized void remove( Object o ) {
if ( !getObjects().contains( o ) ) {
throw new ObjectNotFoundException( o );
}
getObjects().remove( o );
validateExpiration( o );
}
/** Lazily initialized list, an implementation detail. */
private synchronized List getObjects() {
if ( this.list == null ) { this.list = new List(); }
return this.list;
}
/** How object expiration is determined is also a detail. */
private void validateExpiration( Object o ) {
// Objects can answer whether they have gone past a given
// expiration date. How each object "knows" it has expired
// is a detail. The Fridge might use a scanner and
// items might have embedded RFID chips. It's a detail hidden
// by proper encapsulation.
if ( o implements Expires && ((Expires)o).expiresBefore( today ) ) {
throw new ExpiredObjectException( o );
}
}
/** This creates a copy of the warranty for immutability purposes. */
private void setWarranty( Date warranty ) {
assert warranty != null;
this.warranty = new Date( warranty.getTime() )
}
}
Рекомендую посмотреть эти видео для лучшего погружения в вопрос: