Exactly, in my experience if you are creating lots of classes with slight differences you are not only missing an opportunity for DRY, but you are increasing the cognitive load substantially - i.e. understanding the code base (without lots of jumping around).
I haven’t dug deep into your use case, but it sounds like an XXXErrorHandler(s) based anonymous class(es) with properly defined extensions/callbacks would allow a code reader at the site to see the specialized handling these requests take - rather than trying to name all of the scenarios and then have to understand them and remember what all of them mean. It does make testing a bit different - but I may be an outlier here - and I think more black box testing is beneficial anyway - rather than trying to test each handling implementation (if the interfaces/bases are properly designed, the extension code should be trivial and hard to mess up). > On Aug 17, 2025, at 4:46 PM, David Alayachew <[email protected]> wrote: > > Can you give an example? I used Anonymous classes in my solutions, but most > of them were just based off of the Joiner interface. > > I guess I could introduce some state into the abstract class, and then just > tweak it for my needs. And obviously, I'd need a new abstract class for each > "category" of joiner. Then from there, just implement it inline, making each > inline case cheaper. Is that what you are hinting to? > > On Sun, Aug 17, 2025, 5:28 PM robert engels <[email protected] > <mailto:[email protected]>> wrote: >> Isn’t this a perfect use of an anonymous class with a base class? >> >>> On Aug 17, 2025, at 4:00 PM, David Alayachew <[email protected] >>> <mailto:[email protected]>> wrote: >>> >>> > I will guess that #1, #2, and #5 are relatively >>> > simpler Joiner implementations, is there really >>> > any benefit to use composition or inheritance here? >>> >>> Sorry, I have been unclear. Let me clarify. >>> >>> Yes, it is not hard at all to implement #1, #2, and #5 at all. But I don't >>> have 5 joiners. I have well past 30 of them. >>> >>> Most of my joiners are very similar to each other. The 5 I showed you are >>> most of the major "categories" of joiners I would make. But each category >>> would have many derivatives of it. >>> >>> For example, #2 had a derivative that was the same, but for multiple >>> Exception types as opposed to just one. Another derivative of #2 would go >>> past a type test, and actually look at the fields of the Exception (like >>> HTTP Error Code). Yet another would go one level deep, in case the >>> exception was wrapped (like wrapping an HTTP Exception in a >>> RuntimeException). >>> >>> So I started making a whole new class each time I wanted to do almost the >>> same thing. But you start to run out of names that accurately describe what >>> you are doing. And yeah, some joiners are going to be heavily reused, so it >>> makes sense for them to have a whole type (and maybe source file). But many >>> won't either. >>> >>> Once I realized that I was making a bunch of the same thing with minor >>> variations, that's when I started thinking about inheritance and >>> composition. Sorry if I made it sound like that's what I first jumped to. >>> No, I thought about inheritance and composition because those are usually >>> the default answers to the question of "How do I do what T is doing, but >>> with a minor variation?" >>> >>> But inheritance and composition didn't get me very far for these joiners, >>> which is what I was trying to say in my original email. Inheritance with >>> state is error-prone (from my experience). And composition meant that I was >>> making my code brittle. What if those methods I am depending upon need to >>> change? >>> >>> So I went back to making each joiner be its own thing. In reality, most of >>> my custom Joiners were either a simple record implementing the Joiner, or >>> an anonymous class. Records are fine, but you start to run out of >>> reasonable names when you have 5 different records that do close to the >>> same thing. I kind of found a compromise by creating the record Joiner >>> inside the method itself that I am working in (or the class if other >>> methods need it too). That way, it's scoped off from the rest of the world. >>> But considering how many I was making, it felt like a clunky solution. I'm >>> fine peppering my code base with inlined records all over the place *as >>> long as those records don't have a body*. But once they do, it starts to >>> get annoying, and makes the code harder to read and skim. >>> >>> From there, I thought about making a factory. You know the rest of the >>> story. >>> >>> > For #4 and #5 then its surprising that there is RPC >>> > or split/join in the onComplete method. The >>> > onComplete method is called with the completed >>> > subtask and any exception/error executing >>> > onComplete isn't going to change the subtask >>> > status. Is there a reason you've chosen to put >>> > that code there rather than in the subtasks? >>> >>> Yeah. Long story short, if that RPC call or the nested scope fails, well >>> the literal goal that I created this scope to do (contruct an object) has, >>> in effect, failed. That's grounds to just throw an exception and see if >>> someone upstream can handle it. Maybe a retry or something. >>> >>> To me, it felt like I was keeping inline with what onComplete was trying to >>> do -- sort of be an AOP-like post-processing joinpoint. If the contents of >>> onComplete fails, well then the goal was unattainable anyways, so killing >>> the scope via thrown exception doesn't feel wrong. And the exception will >>> propagate, so it felt like I was following right along with how the spec >>> intended things to go. Granted, I certainly am marching on the edge here, >>> I'll concede that. >>> >>> > (for his API then the question as to "where" to >>> > put code is a good discussion as it may not be >>> > always obvious whether to code should execute in >>> > the subtask, in the Joiner handling subtask >>> > completion, or in the main task in the processing >>> > after join. >>> >>> I would love a short guide on what code to put in what place. This looks to >>> be a pretty integral API for handling a large number of tasks moving >>> forward, so I see value in it. >>> >>> >>> On Sun, Aug 17, 2025 at 1:13 PM Alan Bateman <[email protected] >>> <mailto:[email protected]>> wrote: >>>> On 16/08/2025 20:23, David Alayachew wrote: >>>>> : >>>>> >>>>> Sure. Let me highlight 5 of them. Let me know if you need more examples >>>>> -- I have about 30+ custom implementations. >>>> >>>> Thanks for sharing this selection. >>>> >>>> I will guess that #1, #2, and #5 are relatively simpler Joiner >>>> implementations, is there really any benefit to use composition or >>>> inheritance here? >>>> >>>> For #4 and #5 then its surprising that there is RPC or split/join in the >>>> onComplete method. The onComplete method is called with the completed >>>> subtask and any exception/error executing onComplete isn't going to change >>>> the subtask status. Is there a reason you've chosen to put that code there >>>> rather than in the subtasks? (for his API then the question as to "where" >>>> to put code is a good discussion as it may not be always obvious whether >>>> to code should execute in the subtask, in the Joiner handling subtask >>>> completion, or in the main task in the processing after join. >>>> >>>> -Alan >>
