Sounds reasonable - and I appreciate the kindness - wasn’t sure how it would be 
received. I do a lot of Go as well, and its an area where I think Java shines. 
The ability to use anonymous abstract classes with the differentiation being 
localized - rather than polluting the namespace with lots of classes that most 
readers might not be concerned with makes the code base far more maintainable. 
Like I said though, it goes hand in hand with how you decide to test.

To be fair Go has first class function variables but I think it falls short of 
anonymous classes.

> On Aug 17, 2025, at 7:28 PM, David Alayachew <[email protected]> wrote:
> 
> Hah, so my problem isn't an API problem, it's just a basic OOP/DRY basics 
> problem.
> 
> Yeah, I think I see what you are saying. And what needs to be done.
> 
> Ever since Java 8, interfaces have become more and more powerful that I just 
> don't use abstract classes anymore. The interface alone has been good enough 
> for most of my uses cases. I haven't written an abstract class in over a year 
> now, for example.
> 
> But yeah, you are right -- I've just been using the wrong tool for the job 
> here. A couple abstract class with a few utility methods (and maybe an empty 
> list to store subtasks) would have turned this problem into a mild 
> inconvenience at best.
> 
> Lol, I'm still going to be spoiled and say I'd like the factory method 
> anyways. But yes, I think the API as is is sufficient for making the 
> necessary joiners I need.
> 
> Thanks.
> 
> 
> On Sun, Aug 17, 2025, 6:19 PM robert engels <[email protected] 
> <mailto:[email protected]>> wrote:
>> 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] 
>>> <mailto:[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