Rozszczepienie obiektu

pawelwlodarski.blogspot.com 7 lat temu

Jest takie pojęcie - "myślenie lateralne" - stworzone przez E. De Bono, który jest takim specem od ogólnej nauki i przyswajania wiedzy. W tejże książce znajdują się (takie fajne) rysunkowe przykłady graficznego tworzenia jakiegoś rozwiązania gdy dysponujemy ściśle określonym kształtem figur. Zabity na pałę kształt figury symbolizuje sztywne przekonanie a problemy zbudowania rozwiązania symbolizują problemy szablonowego podejścia do rozwiązania problemu gdy mamy tylko sztywne przekonania.

Na kolejnych stronach sztywne kształty są rozbijane na nowe i w końcu budujemy rozwiązanie z nowych przekonań. Jak to ma się do programowwania?

Jest takie powiedzenie "jak programujesz młotkiem to wszystko wygląda jak gwoździe" . Osobiście na tym blogu w ostatnich latach staram się to stwierdzenie odnosić do Javy - z prostego powodu. Sam kiedyś myślałem, iż "programowanie" zamyka się w segregacji interfejsów by było bardziej "domenowo", dorzuceniu stosu annotacji by framework przyjął nasz kod i podregulowaniu JVM by było szybciej.

Dlatego też polecam by każdy podszedł do nauki kolejnego języka (nawet bez szybkiej perspektywy zastosowania go w praktyce), który zmusi go do wypróbowania zupełnie nowych podejść i sprawi, iż sztywne kształty zostaną rozbite i nowa kompozycja rozwiązania stanie się realna.

Rozszczepienie obiektu

Mamy klasy, które mają pola - pola ustawiamy seterami lub konstruktorem a pobieramy getterami. Inne nazewnictwo tego mechanizmu to "mutatory" i "akcesory" (czy "accessory"). getter są połączone z daną instancją obiektu ... ale czy muszą?

Obiekty w javie to (chyba) częściej będą jakieś bezstanowe kontrolery/repozytoria lub zwykłe DTO/rekordy danych (pole+getter) aniżeli jakaś wyrafinowana enkapsulacja zmiennego/mutowalnego stanu. Wynikać to poniekąd może z faktu, iż dane i tak trzeba zapisać w bazie toteż "życie" takiego obiektu jest bardzo krótkie. Jeden z niewielu przykładów gdzie z mojego doświadczenia takie prawo nie obowiązywało to akka-persistance gdzie aktor może kumulować prywatny stan mając zabezpieczenie w postaci "event journala" - nie wykluczone, iż podobna sytuacja występuje w domenach i narzędziach, z którymi przyjemności nie miałem pracować ale chyba w CRUDach produkowanych w Łódzkich szwalniach raczej przeważa model "wyciąg z bazy, zmien i wciśnij na powrót".

I taka "obiektowa" reprezentacja rekordu danych np "User" nam do dalszych rozważań spokojnie wystarczy.

No bo teraz zobaczmy taką konstrukcję.

class
Street(name:String)
class
Address(s:Street,city:String)
class
User(name:String, address:Address)
Mając cały czas ten pierwotny sztywny kształt "getter w instancji" próbując dostać się do nazwy ulicy otrzymamy .
street.getName
address.getStreet.getName
user.getAddress.getStreet.getName

Wiele osób sie skręci na ten widok bo tyle kropek w jednej linii uważane jest (poniekąd prawidłowo) za antywzorzec i nazwane "train wreck". Oburzenie jednocześnie byłoby i nie byłoby uzasadnione. Otóż spierdolina niewątpliwie może się pojawić gdy np. w obiekcie mamy listę produktów i staramy się pobrać najdroższy z nich

Można to zrobić źle

class
Klasa{
private
List<Product> products = ...
getProducts... 
}

klasa.getProducts.sort(desc).takeFirst

lub trochę lepiej :

class
Klasa{
private
List<Product> products = ...
findMostExpensive = products.sort(desc).takeFirst 
}
klasa.findMostExpensive

I to dla mnie wydawało się jasne i oczywiste gdy byłem w epoce javy ale nic nie stoi na przeszkodzie by cały czas mieć enkapsulację danych a wynieść metodę do innego obiektu, który może działa w zakresie prywatnym danej instancji. Brzmi trochę dziwnie ale np. scala umożliwia taką konstrukcje przy pomocy companion object

class
Klasa{
private
List<Product> products = ... 
}

object
Klasa{
def
findMostExpensive(instance:Klasa) = instance.products.sort(desc).takeFirst 
}


//in a different package
import
Klasa._
val k=new Klasa
findMostExpensive(k)
i to może zadziałać tylko jeżeli na liście będą metody, które sobie wymyśliłem. A jeżeli mamy jakieś "superSpecjalistyczneBiznesoweSortowanie" ? Wtedy można wywołać ją "explicit"
superSpecjalistyczneBiznesoweSortowanie(instance.products).takeFirst
Ale jeżeli spojrzeć na superSpecjalistyczneBiznesoweSortowanie nie jak na metodę a jak na funkcję "List[Products] => List[Products]" wtedy może mieć miejsce zgrabna kompozycja
val
findMostExpensive : Klasa =>
Product = 
getProducts andThen superSpecjalistyczneBiznesoweSortowanie andThen getFirst 

Czym jest getProducts? To getter w postaci funkcji, która przyjmuje daną instancje. Normalnie jak mamy "getCos" w klasie to tak "na pierwszy rzut oka" nie ma parametrów ale na rzut oka drugi w bajtkodzie będzie przekazany parametr "this" czyli mamy taki "coupling" gettera do danej konkretnej instancji. Możemy zrobić "decoupling" oraz odpowiednio sterować zakresem dostępu by getter wyciągał bebech w określonych miejscach.

ok, rozbijając pierwotny kształt dochodzimy do trochę innego sposobu budowania rozwiązania -> podążajmy dalej tym tropem

Optyka

Jeśli już mamy getName, getStreet i getAddress wtedy bez problemu możemy całość skomponować w jednego gettera

val
getStreetName : User =>
String =  getAddress andThen getStreet andThen getName
no fajnie ale co nam to daje? Jest to klasyczny przyklad user, ktory ma adres. Firma może mieć adres i zakup moze miec adres. no i można getter "reużyć" w kompozycji.
val
getStreet : Address =>
String = getStreet andThen getName
val
companyStreet: Company =>
String = companyAddress andThen getStreet
val
purchaseStreet: Purchase =>
String = purchaseAddress andThen getStreet
No i jest rejuse poprzez ładną kompozycję. A to dopiero gettery. Magia zaczyna się gdy poskładamy tak sobie settery i może skromna wizualizacja poniżej.

Wspomniany mechanizm nie został odkryty tu i teraz. To jest popularne podejście FP zwane Lens i realizowany przez biblioteki Monocle(https://github.com/julien-truffaut/Monocle) lub rodzime Quicklens(https://github.com/adamw/quicklens). A monocle dlatego, iż lens jest częścią ogólniejszego mechanizmu zwanego optics i jest tam kilka innych ciekawych rzeczy.

Iso

Co jeżeli w jednym kontekście numer ulicy to String ale gdzie indziej operujemy "bardziej domenowym" StreetNumber. Można łatwo dokomponować do gettera/settera konwersję "w tę i na zad".

Optional

Nulle to chuje. Kosztują wiele błędów, czasu i pieniędzy. Dlatego brak wartości należy modelować przy pomocy Option/Optional/Maybe . No i możemy Optional wkomponować w nasze gettery tak, iż dalsze soczewki będa komponowane w kontekście potencjalnego braku wartości i na koniec dostajemy np. Option[StreetNumber]

List

Jeśli Option to zero lub jeden element - tak lista to zero lub n elementów. No i teraz taka opcja. Powiedzmy, iż mamy jebany javowy moloch wygenerowany z SOAP.

getUser composeTraversal getPurchases.map(_.price) composeFold reduce  // to pseudokod
I to może nam dać szybki raport o sumie zakupów

Functor

No to jak jest Option i jest List to pewnie będzie i generalizacja w postaci funktora. I np. w monocle mamy:

  /** modify polymorphically the target of a [[PLens]] using Functor function */
def
modifyF[F[_]: Functor](f: A =>
F[B])(s: S): F[T]

Pryzmat

Ta koncepcja raczej będzie opcja w świecie JAvy. Pryzmat jest w stanie rozszczepić abstrakcyjny typ ADT do konkretnego podtypu. Klasycznym przykladem jest

sealed
trait
Json
case
class
JStr(s:String) extends
Json
case
class
JNum(i:Int) extends
Json
//i to tutaj skopiowane prost z dokumentacji
val
jStr = Prism[Json, String]{
  case
JStr(v) =>
Some(v)
  case _       =>
None
}(JStr)

Warsztaty

Temat poruszony w tym artykule został zainspirowany książką "[TYTUL]" która stanowi kręgosłup nowego cyklu warsztatów na łódzkim JUGu (JUG - Just User Group) "Modelowanie Domeny z FP". Pierwsze spotkanie już się odbyło i kilkanaście osób na tak wyspecjalizowany temat interpretuję jako zainteresowanie duże.

Na

pierwszym spotkaniu uczyliśmy się jak wykorzystać monady do modelowania efektów (tak padło słowo monada i nie rozumiem tego podniecenia tu i tam - "nie wypowiem słowa na M"- po to kurwa ma ta konstrukcja nazwę by jej używać) oraz jak komponować operacje biznesowe będące czystymi funkcjami z efektami. Kolejna część po wakacjach bo jak jest lato i 30 stopni to nikomu się nie chce przychodzić i jest dla mnie strasznie frustrujące jak zapisuje się 30 osób a przychodzi 5. Także wrzesień - manipulacja złożonymi typami przy pomocy "optyki" a później pewnie użycie Monad Reader jako repozytoriów.

No a artykuł był o tym by zburzyć dogmaty na których zbudowane są nasze przekonania - w tym przypadku przekonanie, iż zawsze getter musi być związany z dana instancją - a jak już to się stanie to możemy budować rozwiązania z nowych "kształtów"

Post SKRYPTum

I tutaj jeszcze link z mojej próby podejścia do tego tematu dwa lata temu : http://pawelwlodarski.blogspot.com/2015/08/soczewkiobiektywylenses.html

Idź do oryginalnego materiału