Java Language
Kapsułkowanie
Szukaj…
Wprowadzenie
Wyobraź sobie, że masz klasę z kilkoma bardzo ważnymi zmiennymi i zostały one ustawione (przez innych programistów z ich kodu) na niedopuszczalne wartości. Ich kod spowodował błędy w kodzie. Jako rozwiązanie, w OOP zezwalasz na modyfikację stanu obiektu (przechowywanego w jego zmiennych) tylko metodami. Ukrywanie stanu obiektu i zapewnianie wszelkiej interakcji za pomocą metod obiektów nazywane jest enkapsulacją danych.
Uwagi
Znacznie łatwiej jest zacząć od oznaczenia zmiennej private
i ujawnić ją, jeśli to konieczne, niż ukryć już public
zmienną.
Istnieje jeden wyjątek, w którym enkapsulacja może nie być korzystna: „głupie” struktury danych (klasy, których jedynym celem jest przechowywanie zmiennych).
public class DumbData {
public String name;
public int timeStamp;
public int value;
}
W tym przypadku interfejsem klasy są przechowywane przez nią dane.
Należy pamiętać, że zmienne oznaczone jako final
można oznaczyć jako public
bez naruszania enkapsulacji, ponieważ nie można ich zmienić po ustawieniu.
Kapsułkowanie w celu utrzymania niezmienników
Istnieją dwie części klasy: interfejs i implementacja.
Interfejs to udostępniona funkcjonalność klasy. Jego publiczne metody i zmienne są częścią interfejsu.
Implementacja jest wewnętrznym działaniem klasy. Inne klasy nie powinny wiedzieć o implementacji klasy.
Hermetyzacja odnosi się do praktyki ukrywania implementacji klasy przed użytkownikami tej klasy. To pozwala klasie przyjmować założenia dotyczące jej stanu wewnętrznego.
Weźmy na przykład tę klasę reprezentującą kąt:
public class Angle {
private double angleInDegrees;
private double angleInRadians;
public static Angle angleFromDegrees(double degrees){
Angle a = new Angle();
a.angleInDegrees = degrees;
a.angleInRadians = Math.PI*degrees/180;
return a;
}
public static Angle angleFromRadians(double radians){
Angle a = new Angle();
a.angleInRadians = radians;
a.angleInDegrees = radians*180/Math.PI;
return a;
}
public double getDegrees(){
return angleInDegrees;
}
public double getRadians(){
return angleInRadians;
}
public void setDegrees(double degrees){
this.angleInDegrees = degrees;
this.angleInRadians = Math.PI*degrees/180;
}
public void setRadians(double radians){
this.angleInRadians = radians;
this.angleInDegrees = radians*180/Math.PI;
}
private Angle(){}
}
Ta klasa opiera się na podstawowym założeniu (lub niezmienniku ): angleInDegrees i angleInRadians są zawsze zsynchronizowane . Gdyby członkowie klasy byli publiczni, nie byłoby gwarancji, że te dwie reprezentacje kątów są skorelowane.
Kapsułkowanie w celu zmniejszenia sprzężenia
Hermetyzacja umożliwia wprowadzanie wewnętrznych zmian w klasie bez wpływu na kod wywołujący klasę. Zmniejsza to sprzężenie lub to, jak bardzo dana klasa opiera się na implementacji innej klasy.
Na przykład zmieńmy implementację klasy Angle z poprzedniego przykładu:
public class Angle {
private double angleInDegrees;
public static Angle angleFromDegrees(double degrees){
Angle a = new Angle();
a.angleInDegrees = degrees;
return a;
}
public static Angle angleFromRadians(double radians){
Angle a = new Angle();
a.angleInDegrees = radians*180/Math.PI;
return a;
}
public double getDegrees(){
return angleInDegrees;
}
public double getRadians(){
return angleInDegrees*Math.PI / 180;
}
public void setDegrees(double degrees){
this.angleInDegrees = degrees;
}
public void setRadians(double radians){
this.angleInDegrees = radians*180/Math.PI;
}
private Angle(){}
}
Implementacja tej klasy uległa zmianie, dlatego przechowuje tylko jedną reprezentację kąta i oblicza drugi kąt, gdy jest to potrzebne.
Jednak implementacja uległa zmianie, ale interfejs nie . Jeśli klasa wywołująca polegała na dostępie do metody angleInRadians, należałoby ją zmienić, aby użyć nowej wersji Angle
. Wywoływanie zajęć nie powinno obchodzić wewnętrznej reprezentacji klasy.