A valóságban tényleg nem lesz új pénztárcád és nem terem elő egy másik automata, de ha teremne egy az előzővel teljesen megegyező és az előző eltűnne, akkor a végeredményt tekintve mi változna?
Semmi, hiszen azt hinnéd, hogy ez a régi pénztárca/automata, csak közben megváltozott.
Nem tudod felismerni, hogy nem ugyanaz, mert még a karc, amit belevéstél az is ott van.
Miért is jobb ez a modell nekünk, mint a régi?
Azért, mert így immutable objektumaink lesznek a mutable helyett.
Az immutable objektum azért jó, mert az csak egy érték, olyan, mint a 15, vagy true/false. Mindig annyi, sosem változik meg, nincsenek állapotai.
Miért rossz, ha van állapota?
Azért, mert bármivel kapcsolatba kerül azokat is "megrontja" (elbonyolítja). Ha egy függvénynél minél több ilyen van, annál nagyobb lesz az állapottér. Mit csinál ez a függvény? Attól függ, hogy ennek, ennek meg ennek éppen mi az állapota.
Nézzünk egy példát:
public class Number {
  private int a;
  public Number(int n) { a = n; }
  public void mul(int b) { a = a * b; }
  public int get() { return a; }
}
public class Service1 {
  public static void calc(Number x, Number y) {
    Service2.anotherCalc(x, y);
    x.mul(y.get() + 1);
  }
}
public class Service2 {
  public static void anotherCalc(Number x, Number y) {
    x.mul(y.get() + 1);
  }
}
Nagyon egyszerű a példa: egy objektum, egy állapot, mégis nagyon nehéz dolgunk van, hogy megmondjuk, hogy a Service1.calc metódusa mit is csinál. El kell mennünk a Service2-be meg kell értenünk az anotherCalc-ot, azt, hogyan változtatja az állapotteret, onnan továbbmenni a Number.mul-ba, mert az is állapotváltozást csinál. Minél több ilyen állapotunk van és minél hosszabb útvonalat kell végignézzünk, annál nehezebb dolgunk van.
A tesztelésnél le kellene szeparálnunk a Service2-őt és a Number-t, ezekhez jó Stubokat készíteni megint nem egyszerű.
Nézzük meg ugyanezt a példát leegyszerűsítve, immutable objektummal:
public class Number {
  private int a;
  public Number(int n) { a = n; }
  public Number mul(int b) { return new Number(a * b); }
  public int get() { return a; }
}
public class Service1 {
  public static Number calc(Number x, Number y) {
    Number result = Service2.anotherCalc(x, y);
    return result.mul(y.get() + 1);
  }
}
public class Service2 {
  public static Number anotherCalc(Number x, Number y) {
    return x.mul(y.get() + 1);
  }
}
Itt nincsenek állapotok, a Number az egy érték, emiatt könnyen érthetővé válik a Service2 és a Service1.
Mit csinál a Service2? x * (y + 1)
Mit csinál a Service1? Service2.anotherCalc(x,y)*(y+1).
Nem kell megértenünk a Service2.anotherCalc-ot, elegendő, ha tudjuk, hogy mit csinál, nem kell tudnunk, hogy hogyan csinálja, hiszen nem változtatja meg az állapotteret.
Mivel csak pure function-ök vannak, csak az inputtól függ az outputuk és nincs mellékhatás, így a Service1.calc-ba akár behelyettesíthetnénk a meghívott függvények belét, akkor is ekvivalens függvényt kapnánk, itt { return x*(y+1)*(y+1) }-et, emaitt nem kell leválasztanunk, Stub-olnunk!
Tesztelése minden függvénynek roppant egyszerű, csak meghívom a függvényeket adott inputokkal és ellenőrzöm, hogy a kívánt outputokat adják.