JAX-WS Hello World !

4 04 2012

Potrzebny będzie Eclipse (instalacja), JDK 6 (instalacja), Apache Tomcat 7.0.26 (instalacja), Apache Ant 1.8.3 (instalacja), JAX-WS 2.2.6 (instalacja).

W Eclipse tworzymy nowy Java project o nazwie HelloWorldWS.
W pakiecie (np. com.wordpress.rpodhajny.ws.server) tworzymy klasę HelloWorld.java która będzie bazą dla nowego webserwisu:

package com.wordpress.rpodhajny.ws.server;
import javax.jws.WebService;

@WebService
public class HelloWorld {
    public String sayHelloWorld() {
        return "Hello World !";
    }
}

Anotacja @WebService powoduje, że podczas budowania aplikacji klasa HelloWorld stanie się webservisem opisanym za pomocą standardu WSDL.

Wspomniany proces budowania aplikacji wykonany zostanie za pomocą skryptu ant:

<project name="helloWorldWS" default="generate_compile_war">
    <property name="javac_srcdir" location="src" />
    <property name="javac_destdir" location="classes" />
    <property name="hello_world_ws_class" value="com.wordpress.rpodhajny.ws.server.HelloWorld" />
    <property name="war_file" value="your_apache_tomcat_installation_directory/webapps/helloWorldWS.war" />
    <property name="war_basedir" value="web" />
    <property name="lib_location" location="web/WEB-INF/lib" />

    <target name="generate_ws_files">
        <exec executable="wsgen">
            <arg line="-cp ./bin -keep -s ./src -d ./bin ${hello_world_ws_class}" />
        </exec>
    </target>

    <target name="compile">
        <mkdir dir="${javac_destdir}" />
        <javac srcdir="${javac_srcdir}" compiler="modern" destdir="${javac_destdir}" listfiles="true">
            <include name="**/*.java" />
        </javac>
    </target>

    <target name="war">
        <war destfile="${war_file}" basedir="${war_basedir}">
            <lib dir="${lib_location}" />
            <classes dir="${javac_destdir}" />
        </war>
    </target>

    <target name="generate_compile_war" depends="generate_ws_files, compile, war" />
</project>

Uwaga. Aby program wsgen oraz wsimport (wykorzystany podczas generowania klienta) były możliwe do uruchomienia, zmienna środowiskowa JAVA_HOME musi być przypisana ścieżka do głównego katalogu wykorzystywanego JDK.

Powyższy skrypt umożliwia generowanie i kompilację wszystkich klas webserwisu oraz spakowanie całości do archiwum WAR.

Wszystkie biblioteki Ant są domyślnie wbudowane w Eclipse, tak więc aby uruchomić skrypt który przed chwilą został napisany należy kliknąć na nim prawym klawiszem myszy i wybrać polecenie Run As -> Ant Build …, lub wcisnąć kombinację kalwiszy Shift + Alt + X, a gdy pojawi się menu z dostępnymi pleceniami wcisnąć Q. Trzecim sposobem na uruchamie skryptów Anta jest wykorzystanie jednego z wielu dostępnych w Eclipse widoków (lub jeżeli ktoś woli okien czy paneli). Widok skryptów Ant można dodać klikając lewym klawiszem myszki na ikonę + znajdującą się w lewym dolnym rogu środowiska Eclipse, a następnie Others. W nowym oknie nalezy odszukać widok o nazwie Ant (najłatwiej będzie skorzystać z filtra w górnej części okna). W widoku który zostanie dodany do aktualnie używanej perspektywy klikamy w ikonę mrówki z plusem i wybieramy skrypt Ant który ma zostać dodany do listy. Target z dodanego skryptu uruchamiany jest po dwukrotnym kliknięciu na niego lewym klawiszem myszy.

Uruchamiamy target generate_compile_war aby upewnić się że proces przebiega bezbłędnie. Wyniki działania skryptu pojawią się w konsoli. Komunikat „BUILD SUCCESSFUL” informuje o poprawnym zakończeniu budowania webservisu.

W wyniku działania skryptu w projekcie powstały dwie nowe klasy (F5 na projekcie odświerzy jego zawartość) SayHelloWorld.java oraz SayHelloWorldResponse.java.

Opublikowanie webservisu sprowadza się do umieszczenia WARa w katalogu webapps serwera Apache Tomcat (w tym przypadku wersja 7.0.26) i odczekanie chwili aż wszystko zostanie rozpakowane i uruchomione. Aby sprawdzić czy webservice działa, należy wpisać w przeglądarce internetowej adres http://localhost:8080/helloWorldWS/helloWorld. Na ekranie powinna pokazać się tabela z informacjami na temat webserwisu.

Aby sprawdzić czy webservice rzeczywiście mówi „Hello World !”, trzeba napisać klienta, który będzie się z nim komunikował.
Tworzenie klienta rozpoczynamy od utworzenia nowego Java Project o nazwie HelloWorldWSClient do którego dodajemy plik build.xml, który będzie generował klasy klienta na podstawie wcześniej wspomnianego WSDLa:

<project name="helloWorldWSClient" default="webserviceImport">
    <property name="package_name_for_generated_files" value="com.wordpress.rpodhajny.ws.client" />
    <property name="wsdl_url" value="http://localhost:8080/helloWorldWS/helloWorld?wsdl"/>

    <target name="webserviceImport">
        <exec executable="wsimport">
            <arg line="-keep -s ./src -p ${package_name_for_generated_files} -d ./bin ${wsdl_url}" />
        </exec>
    </target>
</project>

Przed uruchomieniem targetu webserviceImport należy opublikować utworzony wcześniej webserwice. Komunikat „BUILD SUCCESSFUL” informuje o poprawnym zakończeniu generowania klas klienta.

W projekcie, po jego odświerzeniu, powinien pojawić sie pakiet com.wordpress.rpodhajny.ws.client wraz zawartością:

  • HelloWorld.java
  • HelloWorldService.java
  • OjectFactory.java
  • package-info.java
  • SayHelloWorld.java
  • SayHelloWorldResponse.java

Powyższy zestaw klas posłuży do komunikacji z helloWorldWS. Aby tego dokonać tworzymy nową klasę:

package com.wordpress.rpodhajny.ws.client;
public class HelloWorldWSClient {
    public static void main(String[] args) {
        HelloWorldService helloWorldService = new HelloWorldService();
        HelloWorld helloWorld = helloWorldService.getHelloWorldPort();
        String message = helloWorld.sayHelloWorld();
        System.out.println(message);
    }
}

Klasa HelloWorldService służy do nawiązania połączenia z webserwisem. Udostępnia ona również interfejs wygenerowany dla napisanej na początku tego tutoriala klasy HelloWorld. Dzięki temu wiemy jakie metody są dostępne do wykorzystania po stronie klienta. Uruchamiając powyższy kod na ekranie konsoli powininiśmy zobaczyć napis: „Hello World !”.





Porządki w HTML przy użyciu HTMLCleaner

28 02 2010

Czasami zachodzi potrzeba sięgnięcia do źródła danych jakim są strony internetowe. Niestety często zdarza się tak, że kod HTML nie jest dobrze sformatowany i nie da się go przejrzeć za pomocą narzędzi do XML.

import java.io.IOException;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.w3c.dom.Document;
import org.xml.sax.SAXException;

public class Moja {
    public static void main(String[] args) {
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        factory.setNamespaceAware(true);

        try {
            DocumentBuilder builder = factory.newDocumentBuilder();
            Document doc = builder.parse("index.htm"); // błąd podczas konwersji dokumentu na XML

        } catch (ParserConfigurationException e) {
            e.printStackTrace();
        } catch (SAXException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

Wykonanie tego kodu może spowodować pojawienie się w zależności od zawartości dokumentu index.htm różnych błędów np.:

  • Server returned HTTP response code: 503
  • The entity „nbsp” was referenced, but not declared.

Powodami najczęściej są brakujące domknięcia tagów, czy niezrozumiałe dla parsera XML znaki twardych spacji „&nbsp;”. Można taki kod poprawić ręcznie, można napisać własne narzędzie do normalizacji dokumentów HTML, ale na pewno szybciej i prościej będzie skorzystać z gotowego rozwiązania.

W tym artykule postaram się przybliżyć bibliotekę HTMLCleaner która udostępniana jest na licencji BSD, a więc każdy może ją bez przeszkód używać, modyfikować i rozpowszechniać.

Instalacja

Biblioteka jest dostępna do pobrania pod adresem http://htmlcleaner.sourceforge.net/download.php. Po pobraniu rozpakowujemy archiwum i dodajemy plik htmlcleaner.jar do naszego projektu.

Aby dodać do projektu bibliotekę HTMLCleaner w środowisku Eclipse Galileo należy:

  • Kliknąć prawym klawiszem myszy na projekcie i wybrać Build Path -> Configure Build Path…
  • W nowym oknie przechodzimy do zakładki Libraries i klikamy na przycisk Add External JARs…
  • Wybieramy plik htmlcleaner.jar i zatwierdzamy zmiany klikając na przycisk OK.

Podstawy

import java.io.File;
import java.io.IOException;
import org.htmlcleaner.HtmlCleaner;
import org.htmlcleaner.TagNode;

public class Moja {
    public static void main(String[] args) {
        HtmlCleaner cleaner = new HtmlCleaner();
        try {
            TagNode rodzic = cleaner.clean(new File("index.htm"));
            TagNode[] dzieci = rodzic.getAllElements(false); // true/false - wywołanie rekurencyjne/bez rekurencji  			

            for (TagNode dz : dzieci) {
                System.out.println(dz);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

Linia 9:
Wykorzystanie obiektu stworzonego w linii 7 do wyczyszczenia pliku index.html (plik nie jest modyfikowany, jego zawartość przechowywana jest w pamięci). Czyszczenie kodu to nic innego jak poprawienie istniejącej struktury tak aby spełniała standardy XML (w przypadku braku tagu zamykającego, jest on dopisywany, itp.). Metoda clear() ma wiele wersji przystosowanych między innymi do pozyskiwania kody HTML z plików (File), serwerów (URL) czy w postaci łańcuchów tekstowych (String), my chcemy wczytać dane z pliku zapisanego w katalogu z którego uruchamiana jest aplikacja więc wykorzystaliśmy wersję pobierającą w parametrze obiekt typu File. Klasa TagNode wykorzystywana jest do przechowywania informacji o pojedynczym węźe, umożliwia dostęp do atrybutów węzła oraz jego dzieci za pośrednictwem między innymi takich metod:

  • TagNode[] getAllElements(boolean arg), List getAllElementsList(boolean arg)
Zwracają wszystkie dzieci danego węzła, arg true włącza wywołanie rekurencyjne (zostaną pobrane nie tylko dzieci danego węzła, ale także dzieci dzieci itd. innymi słowy wszystkie węzły które leżą niżej w hierarhii od rodzica), arg false spowoduje wykonanie nie rekurencyjnej wersji tej metody (zwróci wszystkie dzieci danego elementu ale tylko te które leżą o 1 stopień niżej w hierarchii niż rodzic).
  • String getAttributeByName(String name)

Zwraca wartość atrybutu danego węzła.

  • Map getAttributes()

Zwraca mapę atrybutów gdzie kluczem jest nazwa atrybutu.

  • TagNode getParent()

Zwraca węzeł który jest rodzicem danego węzła.

  • String getText()

Zwraca tekst jaki zawiera dany element, trzeba jednak zaznaczyć że funkcja ta działa rekurencyjnie i zwraca również tekst który umieszczony został w wewnętrznych węzłach (dzieciach).

Linia 10:
Pobranie wszystkich elementów z głównego węzła (html). Ustawienie parametru na false zwróci dwa węzły: head i body (bo są dziećmi węzła głównego: html). Ustawienie parametru na true rekurencyjnie zwróci węzły z całego dokumentu HTML.

Linia 12:
Wypisanie nazw wszystkich elementów pozyskanych w linii 10. Klasa TagNode ma przeciążoną metodą toString() w taki sposób aby zwracała nazwę węzła.





Java vs. PHP

11 02 2010
Java PHP
Skalowalność (1-10) 7 10
Szybkość budowania
aplikacji (1-10)
5 7
Wsparcie
narzędziowe (1-10)
10 5
Późniejsza pielęgnacja
kodu (1-10)
7 5
Typowane zmienne tak nie
Łatwość nauki trudniejsza niż PHP łatwiejsza niż Javy
Popularność:
prywatne strony WWW
mniejsza niż PHP większa niż Javy
Popularność: zastosowania
komercyjne
większa niż PHP mniejsza niż Java
Debugowanie kompilator, podłączenie pod
serwer lub ręcznie
dodatkowa biblioteka lub
ręcznie
Hosting aplikacji
(ilość serwerów)
mała ilość duża ilość
Cena hostingu aplikacji większa niż hosting PHP mniejsza niż hosting Java

O wiele łatwiej jest dokonać zmian w kodzie aplikacji napisanej w PHP niż tej napisanej w Javie. PHP jest w końcu językiem skryptowym w całości interpretowanym. Java choć interpretowana to jest jednak wcześniej kompilowana do byte kodu, który nie nadaje się już do obróbki, jakiekolwiek zmiany w kodzie muszą być zatem poprzedzone przebudowaniem projektu.

Java ze względu na mnogość narzędzi deweloperskich oraz łatwość późniejszej pielęgnacji kodu jest lepszym wyborem dla dużych internetowych aplikacji. PHP wygrywa jednak jeżeli chodzi o strony prywatne lub mniejsze projekty.

Ponadto wśród programistów krąży opinia że kod pisany w PHP znacznie łatwiej jest zepsuć (bałagan w kodzie) niż kod Javy.





JBoss Seam zmienne kontekstowe

18 11 2009

W zasisęgu kontekst możemy przechowywać dowolne wartości, jednak zazwyczaj
przechowywane są tam stany (wartości) różnego rodzaju komponentów.

Stany obiektów dostępne w ramach danego kontekstu przechowywane są jako
para [klucz, wartość].

Możemy w łatwy sposób uzyskać wartość obiektu z kontekstu:

User user = (User) Contexts.getSessionContext().get("user");

Równie łatwo można zmienić wartość obiektu:

Contexts.getSessionContext().set("user", user);

Zazwyczaj jednak do uzyskiwania dostępu do zmiennych kontekstowych
wykorzystywany jest mechanizm wstrzykiwania (injection) a do zapisu
zmiennej w ramach kontekstu outjection.

Aby pobrać lub zapisać wartość do zmiennej kontekstowej, musi być ona
uprzednio odszukana. Wyszukiwanie odbywa się według określonej
hierarchii (od najbardziej szczegółowego kontekstu do najbardziej ogólnego):

  1. kontekst zdarzenia (request)
  2. kontekst strony
  3. kontekst konwersacji
  4. kontekst sesji
  5. kontekst biznesowy
  6. kontekst aplikacji

Więcej o kontekstach na stronie: https://rpodhajny.wordpress.com/2009/11/18/jboss-seam-kontekst-komponentu/





JBOSS Seam kontekst komponentu

18 11 2009

Konteks bezstanowy

W kontekście tym umieszczane są komponenty bezstanowe. Cechą tych komponentów jest to że są one tworzone od nowa przy każdym użyciu.

Konteks zdarzeniowy (request[żądanie])

Jest to najbardziej szczegółowy kontekst. Komponenty powiązane z nim
są niszczone tuż po zakończeniu zdarzenia (requestu), ale ich stan
dostępny jest conajmniej tak długo jak trwa żądanie.

Konteks strony

Stany obiektów (mozna w skrócie powiedzieć obiekty) umieszczone w ramach
kontekstu strony są powiązanie z konkretną instancją(egzemplarzem)
tejże strony. Obiekty zachowane w kontekście danej strony, mogą być
wykorzystane w eventach z niej wygenerowanych. Jest on szczególnie
przydatny przy funkcjonalnościach np. typu aktywnych list, gdzie
każde klikniecie na wiersz z listy powoduje zaczynanie danych z serwera
np. szczegółów zamówienia. Stan jest przypisywany do konkretnej instancji
strony u użytkownika, umożliwia swobodne korzystanie z przycisku wstecz,
strona załadowana z historii, będzie posiadać swój oryginalny stan
(stan w jakim zostala opuszczona).

Konteks konwersacji

Jest głównym kontekstem Seamowym. Może obejmować kilka interakcji
z użytkownikiem, requestów oraz kilka odwołań do bazy danych, wszystko
co jest konieczne aby wykonać daną funkcjonalność.

Użytkownik może mieć rozpoczętych kilka konwersacji, zazwyczaj w osobnych oknach,żadna z nich
nie ma jednak możliwości narobienia bałaganu w drugiej
(konwersacje nie widzą siebie nawzajem).

Konwersacje mogą być zagnieżdżane.

Konwersacje mogą wygasać (timeout), jest to parametr który można
konfirugować, zabezpiecza przed niebezpecznym rozrostem konwersacji
np. gdy użytkownik opuści stronę bez wylogowania się.

Można tak skonfigurować Seama, aby konwersacje trzymane były
w przeglądarce użytkownika.

Kontekst sesji

Kontekst sesji przechowuje obiekty powiązane z użytkownikiem po
jego zalogowaniu się. Zazwyczaj nie jest stosowany do przechowywania
globalnych informacji, wyjątkiem może być przechowywanie informacji
wykorzystywanych przez kilka różnych konwersacji w ramach danej sesji.

Kontekst procesu biznesowego

Kontekst biznesowy przechowuje obiekty powiązane z długo trwającym procesem
biznesowym. Jest zarządzamy poprzez JBoss jBPM. Proces biznesowy obejmuje
interakcje pomiędzy wieloma użytkownikami.

Kontekst aplikacji

Kontekst aplikacji jest najszerszym kontekstem i jest wykorzystywany
przeważnie do przechowywania statycznych danych np. dotyczących
konfiguracji.

Artykuł napisany w oparciu o dokument Seam reference.




Widening, autoboxing, var-args i Java 5.0

2 04 2009

Widening – cecha Javy poniżej 5.0, jest to po prostu poszerzanie zakresu argumentu podanego metodzie. Załóżmy, że metoda przyjmuje argumenty typu int, a my podajemy jej byte, w tym momencie kompilator automatycznie poszerza byte, konwertując go do int, aby sprostać wymaganiom metody. Możliwe jest to ze względu na to, iż byte jest pod przedziałem zakresu liczbowego int, w drugą stronę (int na byte) automatyczna konwersja nie będzie mieć miejsca, a jeżeli ją wymusimy (poprzez rzutowanie), stracimy część danych, bo jak wiadomo int zapisywany jest na 4 bajtach, byte na jednym, rzutując int na byte odcinane są 3 najbardziej znaczące bajty i w efekcie zakres liczb ograniczony jest do min -128, max 127:

class Main {
    static void test(int a) {
        System.out.println("int");
    }
    public static void main( String[] args ) {
        byte b = 12;
        test(b); // na ekranie zostanie wypisane: "int"
    }
}

A co w przypadku, kiedy będziemy mieć do czynienia z tzw wrapperem (ang. wrap – opakować), czyli typem prostym opakowanym w obiekt? Jak zachowa się kompilator widząc coś takiego?:

class Main {
    static void test(Byte a) {
        System.out.println("Byte");
    }
    static void test(int a) {
        System.out.println("int");
    }
    public static void main( String[] args ) {
        byte b = 12;
        test(b); // na ekranie zostanie wypisane: "int"
    }
}

Dlaczego tak się dzieje, skoro mamy dostępną metodę, która jako argument przyjmuje opakowany typ byte? Otóż programiści implementujący nowe funkcjonalności w Javie 5.0, przyjęli za zasadę, że kod napisany w poprzednich wersjach języka, powinien działać tak, jak w pierwotnych założeniach (kiedy nie było jeszcze mechanizmów typu autoboxing i autounboxing). Tak więc kompilator najpierw próbuje poszerzyć zakres liczby (szuka odpowiedniej metody), a gdy jej nie znajdzie skorzysta z metody, która jako argument przyjmuje obudowany typ prosty (w tym konkretnym przypadku Byte, aby to sprawdzić, wystarczy za komentować przeciążoną metodę static void test(int a)).

Dodać trzeba, że widening wygrywa nie tylko z autoboxingiem, ale też z var-arg, ale czy var-arg wygra z autoboxingiem?

class Main {
    static void test(Byte a) {
        System.out.println("Byte");
    }
    static void test(int... a) {
        System.out.println("int...");
    }
    public static void main( String[] args ) {
        byte b = 12;
        test(b);        // na ekranie zostanie wypisane: "Byte"
        test(b, b);     // na ekranie zostanie wypisane: "int..."
    }
}

Jak widać, podczas, gdy jako argument podajemy jedną zmienną typu byte, autoboxing wygrywa. Natomiast, gdy podajemy do metody test dwa argumenty typu byte (lub więcej), kompilator szuka metod gdzie mógłby zastosować widening, potem autoboxing, a na końcu var-arg (w takiej kolejności), dopiero ostatnia spełnia wymagania.

Referencje

W przypadku referencji sprawa jest prosta, jeżeli metoda test oczekiwałaby na argument typu Owoc (Owoc jest klasą), a dostałaby argument typu Jabłko (Jabłko jest klasą dziedziczącą po klasie Owoc), to jeżeli zachodzi relacja IS-A (a w tym przypadku zachodzi), kompilator wykona automatycznie widening i wykonana zosanie metora test(Owoc 0).

Różne kombinacje

Nie można przekazać argumentu typu byte metodzie, która oczekuje argumentu typu Integer, gdyż, co prawda kompilator poprawnie przekonwertowałby go na typ Byte (autoboxing), ale widening na Integer nie jest już możliwi, z tego prostego powodu, że typu Integer, Byte, Short, itp., nie są między sobą w relacji IS-A, są że się tak wyrażę w jednym szeregu. Kompilator wyrzuci zatem wyjątek w miejscu, gdzie metoda test(Integer i) dostaje jako argument typ prosty byte.

Aczkolwiek, jezeli naszą metodę test(Integer i) zmienimy lekko na test(Object o), wszystko byłoby w porządku, wiemy przecież, że w Javie wszystkie klasy dziedziczą po głównej klasie Object, kompilator wykona autoboxing na typ Byte, który jest w relacji IS-A z typem Object, kompilacja zostanie przeprowadzona pomyślnie i metoda będzie mogła być wywoływana bez problemów.

Zasady dla metod przeciążonych, używających wideningu, boxingu i var-arg:

  • Widening w przypadku typów prostych, zawsze poszerza do najmniejszego możliwego typu prostego,
  • Nie można używać wideningu, pomiędzy dwoma typami opakowującymi typy proste (nie są w relacji IS-A),
  • Nie można rozszerzać, a potem opakowywać, (int nie może stać się Long`iem),
  • Można za to opakowywać, a następnie rozszerzać (int -> Integer -> Object),
  • W przypadku metod przeciążonych boxing i var-arg, używane osobno są poprawne,
  • Var-arg działa zarówno z boxingiem, jak i z wideningiem.




Java kohezja i luźne powiązania między klasami (coupling)

19 03 2009

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.