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).takeFirstAle 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 getNameno 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 getStreetNo 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 pseudokodI 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