Metoda zwracająca typ zamiast Voida, nawet jeśli miałoby być to tylko ułatwieniem na testy.

0

No własnie, mamy metody, których wynik często i tak jest dla nas bezużyteczny. Ja jestem zwolennikiem zawsze zwracania czegoś chociażby po to, że łatwiej się to testuje potem, a nic nie tracimy przecież, a dodatkowo przecież każda operacja ma wynik z matematycznego punktu widzenia (często uboczny w programowaniu).

Widziałem gdzieś odpowiedź @jarekr000000 na podobny temat, że pozwala to też zachować kolejność wołania dwóch metod. Tzn. jeśli mamy jakieś dwie metody voidowe, ale logicznie trzeba je wykonać w ścisłej kolejności to nadanie im typów pozwala to zrobić w łatwy i jednoznaczny sposób. Mógłbyś podać przykłąd?

1

Wystarczy, że do wywołania drugiej potrzebny jest wynik wywołania pierwszej. Można też iść w buildera jak poprzednik napisał - kolejne metody zwracają inny stan, na którym możesz wywołać tylko konkretne metody. Przydaje się w builderach i fluent asercjach.

Z drugiej strony - idąc w voida tracisz możliwość informowania o niepowodzeniu operacji inaczej niż poprzez wyjątki.

1

Kropkowy zapis, który nie kontroluje kolejności, to zwracanie this'a

Taki który ja znam i wymusza prawidłowe użycie, jest nieco pracochłonny, ale daje (dość) dobre wyrażenie o co w API caman.

class MainObiecjt {
  static class IntermediateState1{
           IntermediateState2 method2();
  }
  static class IntermediateState2{
           AnyFinalType metchod3();
  }
  IntermediateState1 methhod1();
}


ps... właśnie myślę, ale z klasami niestatycznymi może by było jeszcze lepiej ... muszę to ogarnąć wyobraźnią.
Oczywiście niepubliczne konstruktory.

1
Bambo napisał(a):

Widziałem gdzieś odpowiedź @jarekr000000 na podobny temat, że pozwala to też zachować kolejność wołania dwóch metod. Tzn. jeśli mamy jakieś dwie metody voidowe, ale logicznie trzeba je wykonać w ścisłej kolejności to nadanie im typów pozwala to zrobić w łatwy i jednoznaczny sposób. Mógłbyś podać przykłąd?

data A
data B
data C

f1 :: A -> B
f1 = undefined
f2 :: B -> C
f2 = undefined

Kolejność musi być zachowana, bo f1 . f2 się nie typuje.

7

Klasyka to taki przykład:

class DoorOpened {
   DoorClosed close() { ...}

}
class DoorClosed {
   DoorOpened open() { ...}

}

a) nie da się tego źle użyć.
b) nie ma potrzeby bawić się w wyjątki,
c) nie ma potrzeby testować pewnych przypadków
d) i nawet jeśli te metody open i close robią jakieś efekty uboczne i logicznie wstawilibyśmy tam void... to nadal można to zapisać w postaci jak wyżej (sztucznie wprowadzając takie typy).

0

No nie wiem czy to takie idioto-odporne:

var openDoor2 = closedDoor.open();
var closedDoor2 = openDoor2.close();

openDoor2.close();
openDoor2.close();
openDoor2.close();
closedDoor2.open();

Ja dla mnie jak są dwie metody jedna musi być po drugiej to jest na to kilka wzorców. Najlepszy to wzorzec kanapka (tutaj impl za pomocą try-with-resources):

try (ResponseBody body = response.body()) {
  // read
}

Wtedy body() na początku a close() na końcu.
W innych językach może to być jako:

"file.txt" open { lines =>
   blah(lines)
}

tutaj ukryte jest open() i close().

Jeżeli jedną z metod można wykonać wielokrotnie to można też użyć "handla" (nie pamiętam jak się ten pattern nazywa):

var h = class.startOp();
var h2 = class.startOp();

h.finish();
h2.finish();

Typ zwracany przez startOp ma tylko jedną metodę finish() którą można wywołać wielokrotnie (tylko pierwsze wywołanie się liczy).

3

@0xmarcin:

var openDoor2 = closedDoor.open();
var closedDoor2 = openDoor2.close();
openDoor2.close();
openDoor2.close();
openDoor2.close();
closedDoor2.open();

Tu będzie problem tylko jeśli faktycznie są używane efekty uboczne i radzę sobie na kilka sposobów:
a) przede wszystkim nie mam efektów ubocznych (wiadomo, że do pewnego miejsca - w końcu następują). Monady (np: reactive streamy) - dają radę. Kod jak wyżej po prostu nic nie robi.
b) dodatkowo nie używam nigdy takiego zapisu (instrukcje) - w moim kodzie na takie coś nie ma miejsca
(dlatego też nie piszę w javie -tylko w kotlinie lub scali, w Scali takie zapis - (wywołanie funkcji bez użycia wartości) chwyta jeden z popularnych linterów),

0

@jarekr000000: A pokazałbyś praktyczny przykład z zastosowaniem monad?

4

Nie mam prostego izolowanego przykładu w głowie, ale zmieńmy ten kod z użyciem Reactora

class DoorOpened {
   Mono<DoorClosed> close() {
    Mono.fromCallable( () ->  {
        hardwareCloseDoor();//to jakiś syf IO/hardwarowy,który daje void - chcemy to opakować
        return new DoodClosed();
       }
   )
}

class DoorClosed {
   Mono<DoorOpened> open() { ...//podobnie jak wyżej}

}


void main(...) {
    Mono<DoorOpened> opened = .... ; //pewnie gdzieś jest jakaś funkcja zwracająca stan początkowy
    opened
      .flatMap( x -> x.close()) 
      .flatMap( x -> x.open())
      .subscribe(...) ; //bez tego subscribe nic się nie wykona
  
}

Teraz jest łatwiej, bo wywołanie openDoor2.close(); po prostu nic nie robi. (nie wywoła też kodu w hardware).
Teraz wystarczy tylko pilnować, aby w kodzie było jak najmniej subscribe() - idealnie to powinno być jedno wywołanie subscribe - w main. W praktyce różnie bywa. Jeśli robisz REST na jakimś WebFlux to możesz nie mieć nawet żadnego (bo to robi framework).

1 użytkowników online, w tym zalogowanych: 0, gości: 1