Ten artykuł zaczyna się serio oczywistych stwierdzeń o geterach i seterach i o tym, ze pojawiła się masa artykułów odnośnie tego, że są złe i też pojawiła się seria artykułów o tym, ze są dobre i ze jedni i drudzy się hejtowali.
A skoro mamy to za sobą to z getterami było tak:
Z enkapsulacją jakoś tak:Czy brak/obecność getterów i setterów to pełne spektrum możliwości? Otóż co ciekawe nie. Poniżej zupełnie inna technika, która wychodzi poza pudełko obiektowych dysput. Idea jest taka by każdy sobie obejrzał i sam zdecydował czy to ma dla niego sens...
Soczewki
Zaczynamy od czegoś co obija się o profanacje czyli damy pola bez modyfikatora private :
public class User { String name; String email; }
Ale co ciekawe nie ma tez modyfikatora public. Ciekawe jak to jest z edukacją wśród młodzieży i czy to część wiedzy powszechnej, że brak modyfikatora w javie to równeiż modyfiaktor. pakietowy.
I na przykład z byle jakiego miejsca się tutaj nie dostanę.
public class OuterClass { public static void main(String args[]) { User user=new User(); //user.name="Stefan"; //Oj nie nie nie , Co to to nie! } }
Osiągnęliśmy więc pewien poziom enkapsulacji - może nie obiektowej ale "paczkowej". Ale jak ustawić to pole bez gettera jeśli go nie ma? Otóż on jest ale gdzieś indziej.
Out of the box
No własnie gdzie może być ten getter. Na pewnie w tej samej paczce ale w zupełnie innej klasie :
Taka sytuacja
public class UserModule { public static Function<User,String> getName=u->u.name; public static BiConsumer<User,String> setName= (u,n)->u.name=n; //ciekawiej public static Function<User,Consumer<String>> setNameCurrying=u->n->u.name=n; }
Powyższe funkcje są na razie jedynym sposobem dostępu do naszego obiektu :
import static blog.UserModule.*; public class OuterClass { public static void main(String args[]) { User user=new User(); setName.accept(user,"Stefan"); System.out.println(getName.apply(user)); setNameCurrying.apply(user).accept("Zofia"); System.out.println(getName.apply(user)); } }
No i fajnie ale poza aspektem naukowo-artystycznym - oddzielenie funkcji od obiektu ma jeden ciekawy aspekt praktyczny - jedno i drugie można niezależnie komponować.
Trochę inna kompozycja
Weźmy coś takiego - to jest taki niby agregat ale aby nikt mnie nie łapał za słówka nie będę tego nazywał agregatem:
public class User { String name; Email email; } class Email{ String address; }
O ile jeszcze pamiętam Jave to klasa Email tez jest w zakresie pakietowym i nie widać jej na zewnątrz.
I dalej
//package setters static Function<User,Consumer<Email>> setEmail=user->newEmail->user.email=newEmail; static Function<Email,Consumer<String>> setAddress=email->newAddress->email.address=newAddress; //package getters static Function<User,Email> getEmail=u->u.email; static Function<Email,String> getAddress=e->e.address;
I teraz, i teraz to się stanie, teraz to będzie...teraz to będzie... ściana
public class Kombinatoryka { public static Function<User,Consumer<String>> composeSet(Function<User,Consumer<Email>> f1,Function<Email,Consumer<String>> f2){ //return ... I co teraz? ściana? To jest przykład na to, że wszelkie wywołanai bez typu zwracanego, czyli wszelkie konsumery //komponują się chu***owo //mozna niby tak ale to jest bez sensu bo możemy zapomnieć o wszelkiej generalizacji biblioteki return u->{ Email email=UserModule.getEmail.apply(u); return f2.apply(email); }; } }
Podejście numer dwa bez konsumerów
Tym razem zwracamy wynik naszego działania :static BiFunction<User,Email,User> setEmail=(user,newEmail)->{user.email=newEmail; return user;}; static BiFunction<Email,String,Email> setAddress=(email,newAddress)->{email.address=newAddress;return email;};
Czy tym raazem się uda? Nie:)
public class Kombinatoryka { public static BiFunction<User,String,User> composeSet(BiFunction<User,Email,User> outer,BiFunction<Email,String,Email> inner){ (user,newAddress)->outer.apply(u,inner.apply(//i znowu ściana, jak wyłuskac z usera email?)) }
Aby myśleć o kompozycji musimy dorzucić przynajmniej jeden getter.
public class Kombinatoryka { public static BiFunction<User,String,User> composeSet(BiFunction<User,Email,User> outer, BiFunction<Email,String,Email> inner, Function<User,Email> outerGet){ return (user,newAddress)->outer.apply(user,inner.apply(outerGet.apply(user),newAddress)); } }
Tak jest to trochę zamotane. Co możemy zrobić?Najpierw tak
interface GenericLens<O,V>{ BiFunction<O,V,O> setter(); Function<O,V> getter(); }
Później tak
static class UserLens implements GenericLens<User,Email>{ @Override public BiFunction<User, Email, User> setter() { return (user,newEmail)->{user.email=newEmail; return user;}; } @Override public Function<User, Email> getter() { return u->u.email; } } static class EmailLens implements GenericLens<Email,String>{ @Override public BiFunction<Email, String, Email> setter() { return (email,newAddress)->{email.address=newAddress;return email;}; } @Override public Function<Email, String> getter() { return e->e.address; } }
Następnie tak :
public static <Outer,Inner,Value> GenericLens<Outer,Value> composeLens( GenericLens<Outer,Inner> outerLens, GenericLens<Inner,Value> innerLens ){ return new GenericLens<Outer, Value>() { @Override public BiFunction<Outer, Value, Outer> setter() { return (outer,value)->{ Inner inner = outerLens.getter().apply(outer); Inner newInner = innerLens.setter().apply(inner, value); return outerLens.setter().apply(outer,newInner); }; } @Override public Function<Outer, Value> getter() { return outer->{ Function<Outer, Inner> outerGetter = outerLens.getter(); Function<Inner, Value> innerGetter = innerLens.getter(); Inner inner = outerGetter.apply(outer); return innerGetter.apply(inner); }; } }; }
I na końcu tak :
static GenericLens<User,String> userEmailLens=Kombinatoryka.composeLens(new UserLens(),new EmailLens());
Co uzyskaliśmy
Teoretycznie mamy enkapsulację obiektów i znowu teoretycznie generyczny mechanizm do kompozycji działań na tych obiektach. Ale wygląda to przerażająco. Niby można sobie założyć, że część z tego kodu byłaby zamknięta przed oczami świata gdzieś w bibliotece...
Czy są inne opcje?
Inne Opcje
Generlanie w Javie nie znalazłem, żadnej biblioteki, która implementuje opisany mechanizm. W scali do dyspozycji jest całkiem ciekawa biblioteczka https://github.com/julien-truffaut/Monocle
libraryDependencies ++= Seq( "com.github.julien-truffaut" %% "monocle-generic" % "1.1.1", "com.github.julien-truffaut" %% "monocle-generic" % "1.1.1", "com.github.julien-truffaut" %% "monocle-macro" % "1.1.1" )
import monocle.macros.GenLens case class Email(address:String) case class User(name:String,email:Email) val userEmailLens=GenLens[User](_.email) val emailAddressLens=GenLens[Email](_.address) val u=User("Stefan",Email("stefan@wp.pl")) val userAddressLens=userEmailLens composeLens emailAddressLens val nowyUser=userAddressLens.set("stefan@gmail.com")(u) nowyUser // User(Stefan,Email(stefan@gmail.com))
I to w zasadzie to tyle kodu potrzeba...
Polecam quicklens Adama W. Małe, sprytne, nieskomplikowane
OdpowiedzUsuńNo proszę, "cudze chwalicie swego nie znacie" , dzięki :D
OdpowiedzUsuńDCI? https://www.google.pl/webhp?sourceid=chrome-instant&ion=1&espv=2&ie=UTF-8#q=data%20context%20interactions
OdpowiedzUsuń