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
>> 

Reply via email to