Kohezja
Klasy, które posiadają metody do różnych zadań, np odczyt/zapis do pliku + obróbka pliku + korekta błędów w tekście, to klasy o niskim współczynniki kohezji (robią wiele rzeczy). Kohezja, to inaczej specjalizacja klasy, a klasa o najwyższym współczynniku kohezji, to ta, która ma tylko jedną konkretną funkcję. Powyżej przedstawiony opis klasy, aby spełniał założenia wysokiej kohezji, musimy rozbić na większą ilość, ale za to bardziej wyspecjalizowanych klas. Powstanie więc np. osobna klasa to zapisu, osobna do odczytu, oraz osobne do obróbki pliku i sprawdzania poprawności tekstu.
Zauważyć należy, że teraz, kiedy rozbiliśmy jedną klasę na kilka mniejszych, możemy je wykorzystywać w dowolny sposób tam, gdzie jest to konieczne. Przed zmianami, stworzenie klasy, która np. potrzebowałaby jedynie odczytu z pliku, wymagałoby albo rozszerzania klasy głównej a tym samym dziedziczenie od niej wielu niepotrzebnych rzeczy, albo napisanie takiej samej metody kolejny raz. Po zmianach (rozbicie ogólnej klasy wg. zasady kohezji), daje nam możliwość skorzystania tylko z tego, co jest potrzebne, czyli klasy, która umożliwia odczyt danych z pliku.
Luźne powiązania między klasami (coupling)
class A {
protected int x;
public int getX() {
return x;
}
public void setX( int x ) {
if( x > 0 ) {
this.x = x;
}
}
}
class B extends A {
public int getBadMultiplyX( int factor ) { // metoda bezpośrednio odwołuje
// się do pola klasy nadrzędnej (źle)
return x * factor; // bezpośredni dostęp do pola klasy nadrzędnej (żle)
}
public int getGoodMultiplyX( int factor ) { // metoda uzyskuje dostęp do
// pola klasy nadrzędnej za
// pośrednictwem metody (gettera) (dobrze)
return getX() * factor; // pośredni dostęp do pola instancji (dobrze)
}
}
public class Main {
public static void main( String[] args ) {
A a = new A();
B b = new B();
a.x = 2; // bezpośrednie ustawienie wartości pola klasy (źle)
a.setX( 2 ); // pośrednie ustawianie wartości pola klasy, sprawdzana jest
// również poprawność wprowadzanych danych (dobrze)
System.out.println( "a.x: " + a.x ); // bezpośredni dostęp do pola
// instancji (źle)
System.out.println( "a.getX(): " + a.getX() ); // pośredni dostęp do pola
// instancji (dobrze)
System.out.println( "b.x: " + b.x ); // bezpośredni dostęp do pola
// klasy nadrzędnej (źle)
System.out.println( "b.getX(): " + b.getX() ); // pośredni dostęp do pola klasy
// nadrzędnej (dobrze)
System.out.println( "b.getBadMultiplyX( 2 ): " + b.getBadMultiplyX( 2 ) );
System.out.println( "b.getGoodMultiplyX( 3 ): " + b.getGoodMultiplyX( 3 ) );
}
}
Po przejrzeniu powyższego kodu (oraz przeczytaniu komentarzy), widać, co nie jest dobrze napisane. Powiązania między klasami powinny być tak “luźne”, jak się tylko da. Oczywiście nikt nikomu nie może zabronić, aby klasy pochodne miały bezpośredni dostęp do pól klasy nadrzędnej ( tak jak w naszym przypadku), lecz jest to zła praktyka. Mając bezpośredni dostęp do pola klasy, możemy przypisać mu dowolną wartość, programista musi więc sam zadbać o to, aby dane wprowadzane, były poprawne. Tą samą czynność muszą zrobić wszyscy, którzy będą chcieli z danej klasy skorzystać. A można przecież wszystkie te powtarzające się problemy załatwić dopisując metody, która będą umożliwiać pobieranie oraz poprawne ustawianie wartości pola klasy (getter, setter). Metody takie to interfejs (API – Application Program Interface), sposób komunikacji naszej klasy z otoczeniem (innymi klasami). Dodatkowo istnieje poważne zagrożenie, że w przyszłości, kiedy klasa ulegnie zmianom, np. zmieni się typ pola “x” klasy “A”, wszystkie klasy dziedziczące po niej, i korzystające z bezpośredniego dostępu jej do pola, po aktualizacji, przestaną działać ze względu na konflikt typów. Natomiast, gdyby klasa A posiadła pole x jako prywatne, i udostępniała jedynie swój API do edycji i pobierania wartości, nic złego by się nie stało. Po zmianach do klasy dopisane byłyby odpowiednie wersje getterów i setterów, lub zmodyfikowane zostały by tylko te, które już są, klasy pochodne nie odczułyby tych zmian.
Aby pisać kod zgodnie z zasadą luźnych powiązań między klasami, należy dla każdej z klas napisać odpowiednie API. Nawet, jeżeli getter/setter nie realizuje dodatkowych czynności, oprócz zwrócenia/ustawienia wartości pola klasy, to i tak warto go napisać. W przyszłości może się bowiem okazać koniecznym dopisanie np. walidacji w setterze, która nie będzie problemem, odkąd nasza klasa porozumiewa się z innymi za pomocą swojego API.
Idea luźnych powiązań jest taka, aby dawać innym tylko to, co chcą zobaczyć, a całą resztę trzymać za zamkniętymi drzwiami.