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