0

I have googled a lot on that topic, but I only found this Stackoverflow post. Assuming I have a simple domain model hierarchy as such:

class Furniture{}
class Table extends Furniture{}
class Sideboard extends Furniture{}

How can I implement a service method called position that is called for the corresponding object type without using instanceof or an if-statement with .class.name while still maintaining separated service classes for the various domain classes?

I really like this answer, but here all methods are packed in one service. I think the service class may grow too large depending on the number of operations to be performed or the depth of the class hierarchy (I know, the latter should be avoided anyway, but still).

I can think of two ways of achieving that by myself, but they both seem broken and hackish.

Option 1: Accessing Application Context

class FurnitureService{
    def grailsApplication
    void position(Furniture furniture){
       grailsApplication.getMainContext().getBean(Introspector.decapitalize(furniture.class.simpleName) +  'Service').position(furniture)
    }
}
class TableService{
    void position(Table table){
        println "table positioned"
    }
}
class SideboardService{
    void position(Sideboard sideboard){
        println "sideboard positioned"
    }
}

I really hate that solution, because it does not make use of DI at all.

Option 2: Use reflection to get the right injected service class

class FurnitureService{
    def tableService
    def sideboardService
    void position(Furniture furniture){   
        furniture.class.getDeclaredField(Introspector.decapitalize(furniture.class.simpleName) + 'Service').get(this).position(furniture)
    }
}
class TableService{
    void position(Table table){
        println "table positioned"
    }
}
class SideboardService{
    void position(Sideboard table){
        println "sideboard positioned"
    }
}

No idea if the first option is better or if this one is just more terrible. I don't like to use reflection. In a traditional OO manner, I would just override an abstract method. There must be a best-practice convention to handle that.

I guess I am making my life way too hard right now with those approaches. Can anybody give me a clean, concise "business standard" solution? I will not be offended if redirected to the grails documentation or a tutorial, if one thinks that is necessary.

Community
  • 1
  • 1
nst1nctz
  • 333
  • 3
  • 23

1 Answers1

1

If you have all the given methods from the different services, the incoming type of your furnture could decide, which one to use. There are different ways to mixin all your services into one.

You could use a @Delegate and let it all decide on passed in type. E.g:

class Furniture{}
class Table extends Furniture{}
class Sideboard extends Furniture{}
class FurnitureService{
    @Delegate TableService tableService
    @Delegate SideboardService sideboardService
}
class TableService{
    String position(Table table){
        return 'table'
    }
}
class SideboardService{
    String position(Sideboard sideboard){
        return 'sideboard'
    }
}

def s = new FurnitureService()
assert s.position(new Table())=='table'
assert s.position(new Sideboard())=='sideboard'

Basically the same, if your groovy/grails version is new enough, can be done with traits:

class Furniture{}
class Table extends Furniture{}
class Sideboard extends Furniture{}
class FurnitureService implements TableService, SideboardService {}
trait TableService{
    String position(Table table){
        return 'table'
    }
}
trait SideboardService{
    String position(Sideboard sideboard){
        return 'sideboard'
    }
}

def s = new FurnitureService()
assert s.position(new Table())=='table'
assert s.position(new Sideboard())=='sideboard'

This of course is all groovy magic to just compose the generic service from the special ones. The simple fact, that the passed in param will decide, which specialized method will be called is the key here.

cfrick
  • 35,203
  • 6
  • 56
  • 68
  • Thanks! Haven't tried in grails yet, but it works in groovyconsole and is a nice alternative. What do you mean by "just for testing, should be autowired"? Do you mean it should be autowired by grails/spring? How would that look like in grails, like `@Delegate def tableService` ? – nst1nctz Oct 13 '14 at 15:32
  • @nst1nctz Yes like this. Just leave the `= new ...` part out. But you have to use a type or else `@Delegate` won't work! It _should_ work in general, as the delegate just generates methods matching the ones from the services and calls the original methods within on the object the delegate is done to. It should not matter, if the var is injected in the first place... but beside groovy magic also grails magic takes place there, so nobody knows ;) – cfrick Oct 13 '14 at 15:35
  • Yes, sometimes grails' magic is like a bunch of evil spells for me, even though I love the conventions and ease of use. But anyway, I will mark your answer as correct if it works. If not, I will open another question for groovy where you can answer, so I can mark it as correct there. Thanks a lot ;) – nst1nctz Oct 13 '14 at 15:39
  • @nst1nctz no hastle. just let me know, if it does not work in grails and i'll try it out there – cfrick Oct 13 '14 at 15:41
  • hmm, the delegate annotation does not work on java.lang.Object or groovy.lang.Object. And I think I have to use def in grails. I'm going to try the traits now, since I use groovy 2.3. – nst1nctz Oct 13 '14 at 15:54
  • @nst1nctz You have to use `@Delegate ExactType theService` -- like in my example (without the `new`), and you may use an exact type with grails – cfrick Oct 13 '14 at 15:56
  • ok, didn't know that you can use the type. Lifelong learning :) – nst1nctz Oct 13 '14 at 15:58
  • Perfect! I am going to try the second one later, but first I have to tell you that the @Delegate variation works as advertised for grails 2.4.2! Exactly what I was looking for. Which of those options do you prefer? Is there any difference? – nst1nctz Oct 13 '14 at 16:04
  • On level of generated bytecode, i would assume, that the delegate variant is slightly less overhead (gut feeling, no evidence). And it also has the advantage, that the services again are regulary injected. If in any case you need your specialized service you can simply use it. Also the delegate will work with older grails versions. For the same reason i would expect delegate to have better IDE support than the newer traits. And of course there is the philosophical question: are these actually traits? – cfrick Oct 13 '14 at 16:27
  • Well, having thought through both approaches for some time, I come to the conclusion that the delegate option suits better to the running application, since services are usually named and put in a directory by grails convention. I think traits would not be actual services for grails, and I guess I would have to put them into `src/groovy`. With delegate, other developers can just use the services as they are used to it; they don't even have to know that they are used as delegates elsewhere. But good to know that there are traits though ;) – nst1nctz Oct 14 '14 at 07:15