1-2-3, OCD!
Announcer: Now, back to San Diego’s native code agitator, MoffDub!
Well well well, just when I thought I’d have to wing this, a late, breaking design problem has arrived, and I have it right here in my formerly delusion-stained neurons.
Once upon a time, there was a data structure called Person. Person had two sub-classes: Crony and Activist. The stored procedure that returned this data returned Cronys in result set #1 and Activists in result set #2. There was a service down in that data structure layer like so:
interface PersonService
{
Collection< Person> retrieveByCustomer(Long id);
}
Crony and Activist differ in data attributes only, and the attributes they share are inherited from Person. This service was happily consumed by your application layer to populate a drop-down list in the UI. Super. Then a new requirement came up: the UI will need to show an association of which Activists go with each Rally, another domain object in your application.
The stored procedure was updated to return that information, and so was the PersonService, but now you have a need to get at only those elements in Collection<Person> that are instances of Activist. Gulp.
class ActivistRepository
{
Collection< Activist> retrieveByCustomer(Long id)
{
Collection< Activist> results = ...;
Collection< Person> peopleData = this.personSvc.retrieveByCustomer(id);
// now what?
return results;
}
}
So it begins: the obsessing, the compulsing, and finally the obsessive-compulsion. There are five thus-far-identified options, and we enumerate them now, just for old time’s sake.
Downcasting
Heh, yeah, I still list this as an option:
Collection< ActivistDomainObject> retrieveByCustomer(Long id)
{
Collection< ActivistDomainObject> results = ...;
Collection< Person> peopleData = this.personSvc.retrieveByCustomer(id);
for(Person p : peopleData)
if(p instanceof Activist)
results.add(this.activistFactory.create((Activist)p));
return results;
}
Do I need to say it? Nope? Good. Moving on.
Flag in Person
This assumes you have the ability to change the data structure service.
Collection< ActivistDomainObject> retrieveByCustomer(Long id)
{
Collection< ActivistDomainObject> results = ...;
Collection< Person> peopleData = this.personSvc.retrieveByCustomer(id);
for(Person p : peopleData)
if(p.isActivist())
results.add(this.activistFactory.create((Activist)p));
return results;
}
This is only slightly better than downcasting. Actually, it is downcasting. What was I thinking here? It doesn’t matter, really, because it sucks just as hard.
Flatten the type hierarchy
Now I think if you flatten the types and add the flag, then you’d have more of a solution:
Collection< Activist> retrieveByCustomer(Long id)
{
Collection< ActivistDomainObject> results = ...;
Collection< Person> peopleData = this.personSvc.retrieveByCustomer(id);
for(Person p : peopleData)
if(p.isActivist())
results.add(this.activistFactory.create(p));
return results;
}
Not shown is how Person now includes all data attributes applicable to cronies and activists. This is the strongest solution yet, if you can rationalize that breaking the Single Responsibility Principle is ok for mere data structures.
Add a method in PersonService
This is another solution that requires write-access to the data structure layer code.
interface PersonService
{
Collection< Person> retrieveByCustomer(Long id);
Collection< Activist> retrieveActivistsByCustomer(Long id);
}
Collection< Activist> retrieveByCustomer(Long id)
{
return this.personSvc.retrieveActivistsByCustomer(id);
}
Oh, awesome. It’s a one-liner. But what you don’t see is implied state. PersonService is now a stateful service, or at least it has to be to avoid duplicate retrievals if you decide to call both methods on the same instance. But at least you can confine this state at an instance level. At an instance level, I think a little caching won’t hurt anyone. This is probably the best solution yet.
Another object instead of Collection
I’m now thinking this is more of an incarnation of the previous solution:
interface PersonService
{
PersonCollection retrieveByCustomer(Long id);
}
interface PersonCollection
{
Collection< Person> asPersonList();
Collection< Activist> asActivistList();
}
Yeah, sort of, except the service is back to being stateless, so I think this is even better than the previous solution.
So, what have we learned? The best solutions appear to require write-access to the data structure layer code, and this implies that PersonService and its cohorts are the core of this problem, not the calling code (ActivistRepository).
Fortunately, I have access to the data structure layer code, because I wrote it. But I can’t help but wonder what I’d do if I didn’t. And that is why I am an Odorless Code Dreamer.
![]() |
Announcer: You’re reading the EIP web-ring. |
