On Tue, Jun 6, 2017 at 5:11 AM, Stephane Ducasse <stepharo.s...@gmail.com> wrote:
> Tx ben. > When I see all this complexity for something that looks not that complex: > I prefer to pass the class and get done. > May be I missed something obvious... but when I see something too complex > I start to get worried. > > I think that thinking about the contract between classes at runtime is > important and to me a MovieLister should be using at runtime and instance > of the *Finder* > Now needing an extra class just to set this should really be evaluated > with the tradeoff: "flexibility win" (our simple solution is still really > flexible - I decide when I want to pass the correct finder) vs. the code > and conceptual bloat. > > > I'm happy not to face the hyper super over engineering of Java "solutions". > > I like the "Of course this just shifts the burden a tad, we still have to > get the locator into the lister" > but the solution is super simple let us use another "Singleton and a > Factory...." :) > > Stef > > On Mon, Jun 5, 2017 at 8:43 PM, Ben Coman <b...@openinworld.com> wrote: > >> >> >> On Tue, Jun 6, 2017 at 1:26 AM, Vitor Medina Cruz <vitormc...@gmail.com> >> wrote: >> >>> Thanks for the answer Ben and Stephane. >>> >>> I already read A Mentoring Course on Smalltalk, Valloud, there is >>> nothing there I could use in this case :( . I will look after for The >>> Design Patterns Smalltalk Companion. Most of the sources provided I already >>> know of or went in the same lines lines of what I have already found. >>> >>> About TDD, I am experienced with the discipline and have tested it on >>> Pharo living system already, but I could not understand how this is related >>> with object wiring, DI and service locator. >>> >> >> I guess I don't properly understand your need and those topics. That was >> my quick pass. Now if I take the time to actually read Fowler's long >> article >> >> "the inversion is about how they lookup a plugin implementation ... to >> ensure that any user of a plugin follows some convention that allows a >> separate assembler module to inject the implementation into the lister." >> >> "The basic idea of the Dependency Injection is to have a separate object, >> an assembler, that populates a field in the lister class with an >> appropriate implementation for the finder interface. There are three main >> styles of dependency injection. The names I'm using for them are >> Constructor Injection, Setter Injection, and Interface Injection." >> >> Now there was too much syntactical noise in those Java examples for me to >> think clearly, so I converted them all to Smalltalk. >> >> >> ##CONSTRUCTOR INJECTION >> >> Object subclass: MovieLister >> instanceVariables: 'finder' >> >> >> MovieLister class >> newWith: aFinder >> ^ self basicNew initializeWith: aFinder >> >> MovieLister >> initializeWith: aFinder >> finder := aFinder >> >> >> ColonMovieFinder class >> newWith: aFilename >> ^ self basicNew initializeWith: aFilename >> >> ColonMovieFinder >> initializeWith: aFilename >> filename := aFilename. >> >> >> ConstructorInjectionContainer >> new >> container := DefaultContainer new. "the article doesn't specify >> where this comes from" >> finderParams := ConstantParameter newWith: 'movies1.txt'. >> container registerComponentInterface: MovieFinderInterface >> implementation: ColonMovieFinder >> params: finderParams. >> container registerComponentImplementation: MovieLister >> ^container >> >> to be used like this... >> ConstructorInjectionTest >> testWithContainer >> container := ConstructorInjectionContainer new. >> lister := container getComponentInstance( MovieLister ). >> movies = lister moviesDirectedBy: 'Sergio Leone'. >> self assert: (movies includes: 'Once Upon a Time in the West') >> >> The article poorly defines registerComponentXXX: or getComponentInstance: >> methods, so I don't dwell on them. I presume its little relevant to the >> main theme. >> >> >> ##SETTER INJECTION >> >> MovieLister >> setFinder: aFinder >> finder := aFinder. >> >> ColonMovieFinder >> setFilename: aFilename >> filename := aFilename. >> >> SetterInjectionTest >> testWithConfigurationFile >> ctx := SomeXmlApplicationConfiguration on: 'config.xml'. >> lister := ctx getConfigOf: 'MovieLister'. >> movies = lister moviesDirectedBy: 'Sergio Leone'. >> self assert: (movies includes: 'Once Upon a Time in the West') >> >> >> ##INTERFACE INJECTION >> >> MovieLister >> injectFinder: aFinder >> finder := aFinder >> >> ColonMovieFinder >> injectFilename: aFilename >> filename := aFilename >> >> InterfaceInjectionTest >> configureContainer >> container := InterfaceInjectionContainer new. >> self registerComponents. >> self registerInjectors. >> container start. >> >> InterfaceInjectionTest >> registerComponents >> container registerComponent: 'MovieLister' with: MovieLister. >> container registerComponent: 'MovieFinder' with: ColonMovieFinder. >> >> InterfaceInjectionTest >> registerInjectors >> container registerInjector: Injector with: (container lookup: >> 'MovieFinder'). >> container registerInjector: InjectorFinderFilename with: >> FinderFilenameInjector new. >> >> ColonMovieFinder >> inject: anObject >> anObject injectFinder: self. >> >> FinderFilenameInjector >> inject: anObject >> anObject injectFilename: 'movies1.txt'. >> >> InterfaceInjectionTester >> testInterface >> self configureContainer. >> lister := container lookup: 'MovieLister'. >> movies = lister moviesDirectedBy: 'Sergio Leone'. >> self assert: (movies includes: 'Once Upon a Time in the West') >> >> The article doesn't define InterfaceInjectionContainer, but I guess it >> could look like this... >> >> InterfaceInjectionContainer >> registerComponent: componentName with: >> aComponent >> container ifNil: [ container := Dictionary new]. >> container at: componentName put: aComponent >> >> InterfaceInjectionContainer >> lookup: componentName >> ^ container at: componentName >> >> >> ##SERVICE LOCATOR >> >> "The basic idea behind a service locator is to have an object that knows >> how to get hold of all of the services that an application might need. So a >> service locator for this application would have a method that returns a >> movie finder when one is needed. Of course this just shifts the burden a >> tad, we still have to get the locator into the lister" >> >> MovieLister >> initialize >> finder := ServiceLocator movieFinder. >> >> >> Object subclass: ServiceLocator >> instanceVariable: 'movieFinder' >> classVariable: 'SoleInstance' >> >> ServiceLocator class >> load: aServiceLocator >> SoleInstance := aServiceLocator >> >> ServiceLocator class >> soleInstance >> ^ SoleInstance >> >> ServiceLocator class >> movieFinder >> ^ self soleInstance movieFinder >> >> ServiceLocator >> movieFinder >> ^movieFinder >> >> >> ServiceLocatorTest >> configure >> ServiceLocator load: (ServiceLocator newWith: (ColonMovieFinder >> newWith: 'movies1.txt')) >> >> >> ServiceLocator class >> newWith: aMovieFinder >> ^ self basicNew initializeWithFinder: aMovieFinder >> >> ServiceLocator >> initializeWithFinder: aMovieFinder >> movieFinder := aMovieFinder >> >> >> ServiceLocatorTest >> testSimple >> self configure. >> lister := MovieLister new. >> movies = lister moviesDirectedBy: 'Sergio Leone'. >> self assert: (movies includes: 'Once Upon a Time in the West') >> >> So it seems that a service locator is just a Singleton pattern having a >> class variable for each service of interest ?? >> > It was late and I mispoke here, of course this should have said... * a Singleton pattern having an instance variable for each service of interest * a Singleton pattern having a getter method (with hidden implementation) for each service of interest > So in good faith** I ask... "Is it any more complicated than that?" >> >> **Since its taken me a couple of hours to convert the Java to this point >> so I stopped reading to seek your feedback. >> > but I'll add, it was so much simpler to understand without all that Java typing boiler plate. cheers -ben > Is that enough insight to adapt to your needs, or is there something else >> further down the article that invalidates my analysis? >> >> >> >> >> >>> >>> From ben: >>> >>> "I'm not really familiar with IoC or DI patterns, so just taking your >>>> example at face value, in Pharo I'd do... >>>> >>>> MovieLister>>moviesDirectedBy: director >>>> allMovies := finder allMovies. >>>> ^ allMovies select: [ :movie | movie getDirector = director ]. >>>> "although typically #getDirector would be renamed #director" >>>> >>>> MovieLister>>finder: movieFinder >>>> finder := movieFinder. >>>> >>>> to be used like this... >>>> lister := MovieLister new finder: (ColonDelimitedMovieFinder on: >>>> 'movies1.txt'). >>>> movies := lister moviesDirectedBy: 'Tarantino'." >>> >>> >> >> So per Fowler, the above is equivalent to "Setter Injection with Spring" >> >> >>> >>> and Stephane: >>> >>> Why don't you simply pass the class and use that class in your >>>> MovieLister? >>>> >>>> MovieLister new >>>> finderClass: MySuperCoolFinderClass >>>> >>>> ... >>>> MovieLister finder >>>> finderClass new ..... >>>> >>>> What is wrong with that. >>> >>> >>> That was what I meant when I said: "I know that in Smalltalk I can make >>> MovieLister to receive, upon construction, a class representing MovieFinder >>> and call it construction message.". The code I had in mind is a bit of >>> mix from the one provided by you both: >>> >>> MovieLister>>moviesDirectedBy: director >>> allMovies := finder allMovies. >>> ^ allMovies select: [ :movie | movie getDirector = director ]. >>> "although typically #getDirector would be renamed #director" >>> >>> MovieLister>>finder: aMovieFinderBuilder >>> finder := aMovieFinderClass new. >>> >>> to be used like this... >>> lister := MovieLister new finder: (ColonDelimitedMovieFinder >>> builderOn: 'movies1.txt'). >>> movies := lister moviesDirectedBy: 'Tarantino'." >>> >>> But that means I will have to wire dependencies by hand whenever I >>> create a MovieLister and seek through code when and if those dependencies >>> change. When there are lot's of dependencies it's is a considerable and >>> tedious work. Let's see an image from Fowlers article: >>> >>> [image: Inline image 1] >>> >>> In this case, the service locator provides me with an instance and I >>> configure the instance in the assembler, the scheme is alike for an IoC, >>> and that would mean my implementation could be like this: >>> >>> >>> MovieLister>>moviesDirectedBy: director >>> allMovies := finder allMovies. >>> ^ allMovies select: [ :movie | movie getDirector = director ]. >>> "although typically #getDirector would be renamed #director" >>> >>> MovieLister>>initialize >>> finder := ServiceLocator locate: FinderClass <--- This would >>> bring the instance of finder class configured by the assembler >>> >> >> No, like this... >> finder := ServiceLocation movieFinder. >> >> Now if you want to store a class rather than an instance as I did higher >> up, you just do this... >> >> Object subclass: ServiceLocator >> instanceVariable: 'movieFinderClass' >> classVariable: 'SoleInstance' >> >> ServiceLocator class >> movieFinder >> ^ self soleInstance movieFinderClass new >> >> >> >>> >>> >>> to be used like this... >>> lister := MovieLister new. >>> movies := lister moviesDirectedBy: 'Tarantino'." >>> >>> and the assembler: >>> >>> Assember class>>configure: >>> aMap put: (ColonDelimitedMovieFinder builderOn: 'movies1.txt') at: >>> FinderClass >>> >> >> Assembler class>>configure >> ServiceLocator load: >> (ServiceLocator new >> movieFinder: (ColonMovieFinder newWith: 'movies1.txt') >> otherService: MyCustomService new) >> >> >>> >>> My assembler and service locator could be even more elaborated, and >>> provide a different MovieFinder in test scope, for different classes or >>> wharever. >>> >> >> Really, the test should not be updating the class variable global like >> this... >> ServiceLocatorTest >> configure >> ServiceLocator load: (ServiceLocator newWith: (ColonMovieFinder >> newWith: 'movies1.txt')) >> >> you probably want something like (its rough but shows the point...) >> Object subclass: #MovieLister >> instanceVariables: 'serviceLocator' >> >> MovieLister >> initialize >> serviceLocator ifNil: [ serviceLocator := ServiceLocator soleInstance >> ]. >> finder := serviceLocator movieFinder. >> >> MovieLister class >> newWithServiceLocator: aServiceLocator >> ^ (self basicNew initializeWithServiceLocator: aServiceLocator) >> initialize. >> >> MovieLister >> initializeWithServiceLocator: aServiceLocator >> serviceLocator := aServiceLocator >> >> ServiceLocatorTest >> testSimple2 >> lister := MovieLister newWithServiceLocator: (ServiceLocator newWith: >> (ColonMovieFinder newWith: 'movies1.txt')). >> movies = lister moviesDirectedBy: 'Sergio Leone'. >> self assert: (movies includes: 'Once Upon a Time in the West') >> >> >> cheers -ben >> >> >>> >>> It is a little convenience for Smalltalk, I will give that, but I was >>> wandering if there was something alike in Pharo, by your answers I assuming >>> there is nothing like that. >>> >>> >>> >>> On Mon, Jun 5, 2017 at 6:41 AM, Stephane Ducasse < >>> stepharo.s...@gmail.com> wrote: >>> >>>> Why don't you simply pass the class and use that class in your >>>> MovieLister? >>>> >>>> MovieLister new >>>> finderClass: MySuperCoolFinderClass >>>> >>>> ... >>>> MovieLister finder >>>> finderClass new ..... >>>> >>>> What is wrong with that. >>>> >>>> If you do not want to have a reference at runtime to a Finder then you >>>> need to use announcement and registration. >>>> >>>> Stef >>>> >>>> >>>> >>>> On Sun, Jun 4, 2017 at 11:17 PM, Vitor Medina Cruz < >>>> vitormc...@gmail.com> wrote: >>>> > Hello, >>>> > >>>> > I would like to know how people in Pharo ecosystem do to deal with >>>> object >>>> > wiring, as described by Marting Fowler in >>>> > https://martinfowler.com/articles/injection.html#FormsOfDepe >>>> ndencyInjection: >>>> > >>>> > "A common issue to deal with is how to wire together different >>>> elements: how >>>> > do you fit together this web controller architecture with that >>>> database >>>> > interface backing when they were built by different teams with little >>>> > knowledge of each other." >>>> > >>>> > He gives an example, I will leave it in java as it is simple enough to >>>> > understand: >>>> > >>>> > "class MovieLister... >>>> > >>>> > public Movie[] moviesDirectedBy(String arg) { >>>> > List allMovies = finder.findAll(); >>>> > for (Iterator it = allMovies.iterator(); it.hasNext();) { >>>> > Movie movie = (Movie) it.next(); >>>> > if (!movie.getDirector().equals(arg)) it.remove(); >>>> > } >>>> > return (Movie[]) allMovies.toArray(new Movie[allMovies.size()]); >>>> > >>>> > }" >>>> > >>>> > The question is how to provide the finder object in a decoupled >>>> matter, a >>>> > naive approach would be: >>>> > >>>> > " private MovieFinder finder; >>>> > >>>> > public MovieLister() { >>>> > finder = new ColonDelimitedMovieFinder("movies1.txt"); >>>> > >>>> > }" >>>> > >>>> > Which couples the MovieLister to the specific >>>> ColonDelimitedMovieFinder >>>> > class. >>>> > >>>> > Fowler explains how to decouple using an IoC framework or a Service >>>> Locator. >>>> > In Java and .Net IoC is used most of the time. I Googled how this >>>> problem is >>>> > approached in Smalltalk/Pharo, and I generally I found answers "that >>>> is easy >>>> > to do in Smalltalk, so there is no need of a framework", what I miss >>>> is a >>>> > description on *how* to do that: >>>> > >>>> > https://stackoverflow.com/questions/243905/smalltalk-and-ioc >>>> > https://stackoverflow.com/questions/2684326/is-there-a-depen >>>> dency-injection-framework-for-smalltalk >>>> > https://stackoverflow.com/questions/243905/smalltalk-and-ioc >>>> /347477#347477 >>>> > >>>> > I know that in Smalltalk I can make MovieLister to receive, upon >>>> > construction, a class representing MovieFinder and call it >>>> construction >>>> > message. As long an object that responds to this message is provided, >>>> I can >>>> > create as many derivations I want and the MovieLister will be >>>> decoupled from >>>> > the MovieFinder. That way, however, I still have to wire things by >>>> hand, and >>>> > I am not sure if this is what I am supposed to do in order to solve >>>> the >>>> > decouple problem. >>>> > >>>> > Can you explain me how this is done in Pharo? It's is usually wiring >>>> by >>>> > hand? Is there a simple construction that deals with the wiring >>>> problem that >>>> > I cannot foresee? >>>> > >>>> > Thanks in advance, >>>> > Vitor >>>> > >>>> > >>>> > >>>> >>>> >>> >> >