Ten wpis będzie należał do serii tych krótszych. Chciałem w nim powiedzieć/przypomnieć o czymś takim jak statyczne klasy w Javie. Jeśli pamiętasz, że można z czegoś takiego korzystać to być może ten wpis nie jest dla Ciebie. Natomiast jeśli czujesz potrzebę odświeżenia sobie pamięci to plus dla Ciebie.

Moim zdaniem warto czytać wpisy na tematy, które znamy już od podszewki. Być może znajdziemy w nich coś co otworzy nam oczy na nowe aspekty, o których nie przyszło nam pomyśleć. Możemy znaleźć w nich to jedno zdanie, które wywróci nasze myślenie o 180 stopni. Przejdźmy zatem do krótkiego przypomnienia o klasach wewnętrznych.

Konstrukcja klas wewnętrznych

W Javie możemy tworzyć klasy wewnętrzne. Mogą być one statyczne oraz niestatyczne. Czym to się różni? Prosty przykład. Mamy dwie klasy, A oraz B, gdzie B znajduje się w A.

1
2
3
4
5
6
7
8
9
class A {
  class B {

  }
}

private void usage() {
  A.B b = new A().new B();
}
1
2
3
4
5
6
7
8
9
class A {
  static class B {

  }
}

private void usage() {
  A.B b = new A.B();
}

Jedno słowo kluczowe, a zmienia naprawdę dużo. W pierwszym przykładzie, aby utworzyć obiekt klasy B musimy utworzyć obiekt klasy A co prowadzi do naprawdę niecodziennego zapisu. Natomiast w drugim przypadku wygląda to nieco bardziej normalnie. Nie mamy dwóch użyć słowa kluczowego new. Rozdzielamy jedynie nazwy klas kropką.

Dobra, tyle nam wystarczy na ten moment. Jeśli chcesz dowiedzieć się czegoś więcej to serdecznie zapraszam do zapoznania się z artykułem, który napisałem już dawno, dawno temu… Przejdźmy teraz do sedna. Skupmy się na statycznych klasach wewnętrznych w kontekście DTO.

Przykład z życia wzięty

Załóżmy, że chcielibyśmy dostarczyć strukturę danych dotyczącą wszystkich zamówień wybranej firmy na frontend. Najczęstszym rozwiązaniem z jakim się spotykałem było stworzenie wielu klas, każda per plik - CompanyDto, CompanyClientDto, CompanyClientOrderDto, CompanyClientOrderProductDto.

1
2
3
4
5
public class CompanyDto {
  public final String name;
  public final Set<CompanyClientDto> clients;
  //...
}
1
2
3
4
5
public class CompanyClientDto {
  public final String fullName;
  public final Set<CompanyClientOrderDto> orders;
  //...
}
1
2
3
4
5
public class CompanyClientOrderDto {
  public final String orderCode;
  public final Set<CompanyClientOrderProductDto> products;
  //...
}
1
2
3
4
5
public class CompanyClientOrderProductDto {
  public final String name;
  public final Money price;
  //...
}

Czyli mamy zdefiniowane aż 4 pliki z rozszerzeniem .java. Każdy ma przedrostek zawierający nazwę agregującej go klasy. Gdy w jednym pakiecie mamy tylko te klasy to nie jest to jeszcze takie uciążliwe. Jednak w przypadku posiadania ich większej ilość można mieć przeświadczenie, że w projekcie robi się bałagan. Można z tego wybrnąć poprzez zastosowanie wcześniej wspomnianej klasy statycznej.

Możliwe rozwiązanie

Przepisem jest po prostu umieszczenie “dziecka” w “rodzicu” i usunięcie zbędnego prefiksu. Oczywiście trzeba również skorzystać ze słowa kluczowego static. Finalnie wszystko znajduje się w jednym pliku o nazwie CompanyDto.java.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class CompanyDto {
  public final String name;
  public final Set<ClientDto> clients;
  //...

  public static class ClientDto {
    public final String fullName;
    public final Set<OrderDto> orders;
    //...

    public static class OrderDto {
      public final String orderCode;
      public final Set<ProductDto> products;
      //...

      public static class ProductDto {
        public final String name;
        public final Money price;
        //...
      }
    }
  }
}

Mała pierdoła a cieszy oko. Dodatkowo Jackson sobie z takim podejściem na spokojnie radzi. Zobaczmy jak wygląda tworzenie obiektów klasy reprezentującej produkt w nowym i starym podejściu.

1
2
3
4
5
6
// wcześniej
CompanyClientOrderProductDto product = 
  new CompanyClientOrderProductDto("chleb", Money.ofPLN(2.80));
// teraz
CompanyDto.ClientDto.OrderDto.ProductDto product = 
  new CompanyDto.ClientDto.OrderDto.ProductDto("chleb", Money.ofPLN(2.80));

Niewiele się od siebie różnią. Dla kogoś nowe podejście może wydawać się dziwne i mało intuicyjne przez konieczność korzystania z kropek. Jednak od Javy 9 wyglądałoby to znacznie prościej dzięki słowu kluczowemu var, przynajmniej przy deklaracji zmiennej.

Podsumowanie

Chciałem się w tym krótkim wpisie podzielić czymś co może wydawać się dla niektórych oczywiste. Jednak ktoś mógł się z takim podejściem nigdy nie spotkać, a przynajmniej w tej postaci i być może z tego skorzysta. Z tego powodu warto czasami mówić o rzeczach, które dla nas są znane od dawna, a dla kogoś mogą być game changer’em. Znasz jakiś skrót w IntelliJ, z którego korzystasz codziennie i myśli, że skoro Ty go znasz to każdy go zna? Może warto o tym wspomnieć komuś przy kawie w pracy. Jeśli zna ten skrót to tylko przytaknie i zmienicie temat. Jeśli nie, to na pewno Ci podziękuje!

Inną rzeczą jest, że zapis tworzenia obiektów klas wewnętrznych nie do końca może komuś odpowiadać. Zgadzam się. Nie jest on tak oczywisty jak w przypadku podejścia klasa per plik. Jednak oszczędzamy “miejsce” w postaci ilości plików oraz nie tworzymy redundancji w nazwie klas.

To podejście było inspirowane wykładem Kevlina Henneya dotyczącego testowania. Jaki masz stosunek do takiego tworzenia klas? Daj znać w komentarzu!