Ekstremalna obiektowość w praktyce – część 3 – Opakowuj wszystkie prymitywy i Stringi
Z głośników spokojnie tym razem. Era. Swoją drogą przypominają mi się stare dobre czasy gdy przy „Ameno” przerąbywałem się przez kolejne poziomy w Diablo: Hellfire.
Wspomnienia, wspomnieniami czas jednak zająć się trzecią z zasad Jeff’a Bay’a.
Opakowuj wszystkie prymitywy i Stringi(w klasy o specyficznej dla zastosowania nazwie)
Rozejrzyj się wokoło. Zobacz jak zbudowany jest świat. Szybko stwierdzisz, że świat składa się z obiektów, które wchodzą w interakcje. Tak mniej więcej zaczyna się jakieś 99% kursów dotyczących programowania obiektowego. Pozostałe 1% zaczyna się mniej więcej tak: php jest językiem obiektowym.
Rzecz w tym, że w realnym świecie nie występują liczby, znaki czy stringi. Służą one do opisania jakiś konkretnych przedmiotów są ich cechami i wartościują je według określonego wzorca. W Javie liczby są przedstawione jako typu proste, a autoboxing jest tylko dodatkiem pozwalającym na łatwiejszą pracę z nimi. Swoją drogą powoduje to cholernie wiele problemów jeżeli chcemy używać typów liczbowych w metodach przyjmujących Object i przez przypadek podamy z palca prymitywa. To jednak zupełnie inny temat.
Program opisuje świat
Tak jak w realnym świecie tak i w aplikacji nie powinno używać się typów prostych i stringów. W ich miejsce należy wprowadzać niewielkie klasy reprezentujące atomową jednostkę „czegoś”. Klasa taka powinna mieć znaczenie biznesowe ponieważ reprezentuje pewną małą istotę żyjącą w ramach systemu.
Na początek pewien prosty przykład.
Listing 1. Typ prymitywny i „ciekawy błąd”
package pl.koziolekweb.eowp3;
public class MoneyExample {
public static void main(String[] args) {
Konto konto = new Konto();
print(konto.stan());
konto.uznaj(10);
print(konto.stan());
konto.obciąż(5);
print(konto.stan());
konto.uznaj(-3);
print(konto.stan());
konto.obciąż(-5);
print(konto.stan());
}
private static void print(int stan){
System.out.println("Stan konta to " + stan);
}
}
class Konto {
private int stan = 0;
public void uznaj(int wartość) {
stan += wartość;
}
public void obciąż(int wartość) {
stan -= wartość;
}
public int stan(){
return stan;
}
}
Błąd widać na pierwszy rzut oka. Prymitywne typy danych prowokują do tego typu „ekscesów”. Oczywiście można wprowadzić dodatkowy walidator i bawić się w sprawdzanie czy przekazywana wartość nie jest mniejsza od 0. Jeżeli jednak operacje na danej wartości przeprowadzamy w różnych miejscach w aplikacji warto wprowadzić klasę zamiast typu prymitywnego. Jeff Bay idzie dalej i mówi, iż skoro w świecie realnym nie istnieją typy proste jako samodzielne twory to nie należy ich używać w aplikacji.
Typy prymitywne
Oczywiście trochę inaczej należy traktować typy proste, a inaczej String. Trochę lepsze rozwiązanie:
Listing 2. Typ prymitywny i obejście
package pl.koziolekweb.eowp3;
public class MoneyExampleWithType {
public static void main(String[] args) {
Konto2 konto = new Konto2();
print(konto.stan());
konto.uznaj(new Kwota(10));
print(konto.stan());
konto.obciąż(new Kwota(5));
print(konto.stan());
konto.uznaj(new Kwota(-3));
print(konto.stan());
konto.obciąż(new Kwota(-5));
print(konto.stan());
}
private static void print(Stan stan) {
System.out.println("Stan konta to " + stan);
}
}
class Konto2 {
private Stan stan = new Stan(0);
public void uznaj(Kwota kwota) {
stan.uznaj(kwota);
}
public void obciąż(Kwota kwota) {
stan.obciąż(kwota);
}
public Stan stan() {
return stan;
}
}
class Kwota implements Wartość<Integer> {
private final int wartość;
public Kwota(int wartość) {
if (wartość
<p>Przykład działa w tym przypadku całkiem nieźle, ale... no właśnie. Jest sobie interfejs <samp>Wartość</samp>, który tan naprawdę psuje nam wszystko. Z dwóch powodów. Pierwszy to ujawnia jak trzymana jest implementacja typu prostego w klasie <samp>Kwota</samp>. Drugi nadal operujemy bezpośrednio na typie prostym. Czas wyciągnąć naszą ulubioną broń, czyli dziedziczenie.</p>
<p class="listing">Listing 3. Typ prymitywny i dobre podejście</p>java
package pl.koziolekweb.eowp3;
public class MoneyExampleWithExtends {
public static void main(String[] args) {
Konto3 konto = new Konto3();
print(konto.stan());
konto.uznaj(new KwotaBazowa(10));
print(konto.stan());
konto.obciąż(new KwotaBazowa(5));
print(konto.stan());
konto.uznaj(new KwotaBazowa(3));
print(konto.stan());
konto.obciąż(new KwotaBazowa(15));
print(konto.stan());
}
private static void print(StanKonta stan) {
System.out.println("Stan konta to " + stan);
}
}
class Konto3 {
private StanKonta stan = new StanKonta(0);
public void uznaj(KwotaBazowa kwota) {
stan = stan.uznaj(kwota);
}
public void obciąż(KwotaBazowa kwota) {
stan = stan.obciąż(kwota);
}
public StanKonta stan() {
return stan;
}
}
class KwotaBazowa {
protected final int wartość;
public KwotaBazowa(int wartość) {
if (wartość
<p>Oczywiście ten kod wymaga jednej rzeczy. Testów (oraz synchronizacji, ale to inna inszość). Tych nie prezentuję, bo są nudne, ale trzeba je napisać. OK, ale co się stało? Przede wszystkim <samp>StanKonta</samp> reprezentuje kwotę (klasa nazywa się <samp>KwotaBazowa</samp>, bo siedzi w jednym pakiecie z poprzednimi przykładami i się kompilator burzy). Rozszerzyliśmy jednak zachowanie tej klasy w ten sposób, że poza zawsze dodatnią kwotą ma jeszcze flagę <samp>Debet</samp>. Flaga ta mogła być typu <samp>Boolean</samp>, ale... nie można by było wtedy uzyskać efektu wartości oraz metody <samp>czyDebet</samp> - <samp>Boolean</samp> jest <samp>final</samp>. To rozwiązanie ma pewne wady. Przede wszystkim rozwlekło kod. Wymaga też testów, ale... pierwotne rozwiązanie nadal wymaga testów w dodatku jest znacznie bardziej podatne na błędy (spróbujcie wrzucić do stanu konta np. zrzutowany <samp>char</samp>). Z drugiej strony uzyskaliśmy kod odporny na durne błędy. Łatwy w testowaniu. Jeżeli dodatkowo odpowiednio go popaczkujemy to będzie przenośny pomiędzy projektami.</p>
<h5>Typ String</h5>
<p>Podobnie jak typy proste typ <samp>String</samp> nie występuje w naturze z jednym wyjątkiem:<br></br><figure aria-describedby="caption-attachment-2286" class="wp-caption aligncenter" id="attachment_2286" style="width: 610px"><a href="https://i0.wp.com/koziolekweb.pl/wp-content/uploads/2011/11/object.toString.jpg"><img alt="getStringFromObject" class="size-full wp-image-2286" data-recalc-dims="1" height="761" sizes="(max-width: 610px) 100vw, 610px" src="https://i0.wp.com/koziolekweb.pl/wp-content/uploads/2011/11/object.toString.jpg?resize=610%2C761" srcset="https://i0.wp.com/koziolekweb.pl/wp-content/uploads/2011/11/object.toString.jpg?w=610&ssl=1 610w, https://i0.wp.com/koziolekweb.pl/wp-content/uploads/2011/11/object.toString.jpg?resize=240%2C300&ssl=1 240w" title="object.toString" width="610"></img></a><figcaption class="wp-caption-text" id="caption-attachment-2286">getStringFromObject</figcaption></figure></p>
<p>i tego wyjątku się trzymajmy.</p>
<p>Generalnie typ znakowy jest używany dość często jako nośnik informacji. Począwszy od dupereli typu imię i nazwisko, poprzez hasła, loginy, kończąc na adresach URL czy adresach usług. Przesłanianie tego typu ma jednak dwie zalety.</p>
<p>Po pierwsze pozwala na wprowadzenie dodatkowych walidacji na etapie tworzenia obiektu. Tym samym mamy kontrolę nad tym co tworzymy i nie posypie się np. <samp>MalformedURLException</samp> czy nie otrzymamy pustego napisu.<br></br>
Po drugie znacznie łatwiej jest zarządzać kodem mając konkretny typ, a nie enigmatyczny <samp>String</samp>. Przykłady wymyślcie sobie sami.</p>
<h4>Inne typy proste</h4>
<p>Że co? Czy w Javie są jeszcze jakieś inne typy proste poza prymitywami(plus ich obiektowe wersje) oraz <samp>String</samp>? To zależy jak na to spojrzymy. Jeff Bay mówi o typach prostych, ale ja osobiści zalecam dodanie do listy typów prostych takich, które pochodzą z różnych narzędzi. Przykładowo jeżeli tworzymy XMLa i mamy schemę to warto poza walidacją ze schemą rozszerzyć klasy typu <samp>Node</samp> by uzyskać efekt wstępnej walidacji np. by nie wstawić nieprawidłowej nazw elementu.<br></br>
Podobnie ma się rzecz z usługami sieciowymi. Tu wartą rozszerzenia jest klasa <samp>QName</samp>, szczególnie jeżeli wywołujemy usługi w sieci z ograniczonym dostępem. Można wtedy wprowadzić dodatkową walidację by sprawdzać już na wstępie czy dana usługa jest osiągalna. Pozwoli to na kontrolowane wyłapanie baboli.</p>
<h4>Podsumowanie</h4>
<p>Zasada ta jest dość trudna w zastosowaniu. Wymaga umiejętnego użycia zarówno interfejsów jak i dziedziczenia. Tworząc taki kod warto zadawać sobie pytanie "Czy A to B?". Pozwoli to na uproszczenie kodu i wczesne wyłapanie pewnych abstrakcji. Rozwiązanie tego typu pociąga za sobą pewne niedogodności. Przede wszystkim uzyskujemy prawdziwą enkapsulację. Innymi słowy łatwo wyeliminować tu gettery/settery, co pociąga za sobą spore problemy natury prezentacyjnej. Ten problem poruszę jednak w ostatniej części. Na razie można używać getterów 😀</p>