❔ Creating a "Lazy" Factory with a Fluent API
I'm looking to create a kind of factory, which creates commands instead of the actual results. The results are then conditionally returned upon a call to the factory's
Run()
or Execute()
method. The reason for the "laziness" of the factory is that executing one of these commands may fail (TryExecute()
). This command is then skipped (the failure may be logged) and tried again later, at which point it may no longer fail.
Assume these result objects;
These objects are all "created" differently (as in, they have different constructor parameters), meaning the factory must also have MakeFoo<T>
, MakeBar<T>
, and MakeQux
.
But unfortunately, that's not it. All implementations of FooBase
take an instance of IFoo
as a sort-of parent. This can be nested infinitely. Let's assume something like this;
This means that the factory needs some way to create a parent which then has at least 1 or more children.
I also want to modify some of the objects' properties fluently;
Potential implementations I've considered use a setup such as this;
46 Replies
The API could look something like this;
But this quickly runs into issues with "un-nesting". That is, if you want to "go up a parent". Perhaps a
WithChildren(/* */)
method on IMakeFooCommand
could solve this...?can you try this?
Couldn't you do
.MakeFoo<T>(foo => foo.Prop1(/* */))
where each level of "nesting" would just be a lambda?how do you delete someone else's message
i cam
n
Mh, how do you mean?
MakeFoo<T>
still needs constructor argumentshmm, true
also was this written by ChatGPT
and very specifically the
MakeX
methods take a params int[]
arg at the endAnother idea Each builder would have a
Back
property which is the previous builder.the "previous builder"? there's only one builder
Was this issue resolved? If so, run
/close
- otherwise I will mark this as stale and this post will be archived until there is new activity.i still need some advice on this
i'll elaborate a bit. assume this setup;
the resolvers are there to simply attempt and resolve this critical value in
FooBase
. without that resolved value, no instances of FooBase
can function.
the resolving is happening when the factory gets "activated"
if the resolving fails, the command is skipped and attempted again later and the desired objects (Foo<T>
, Bar<T>
, Qux
) are not created
the part i'm stuck on is choosing whether to create these types of creation commands, which hold both the ctor arguments of the FooBase
implementations, and the resolver, or to create the FooBase
implementations directly, and set that value which must be resolved from outside the classes (which feels very dirty)
that's one of the things i'm stuck on. the other is how to handle potentially infinite nesting of parents;
this has been making me lose my mind for the past week or so
i just need some tips on good and proper design hereWas this issue resolved? If so, run
/close
- otherwise I will mark this as stale and this post will be archived until there is new activity.should i elaborate on anything?
pardon my naivety, but can't you just keep track of parents and
pop
the top most parent when you need to escape
?well that's the easy part
absolutely no issues there
Right
So what's the problem with the current proposed API 😅 ?
well, for one, i'm not exactly sure how to even do the parents thing
like, do i have an
IMakeFooCommand _parent;
within each IMakeFooCommand
?
and even then, how do i get the result of that? how do i make sure the children are only ever evaluated when the parent was successful?
how do i evaluate the children at all?Hmm basically you want
Is my understanding correct?
ah, no.
MakeFoo<T>
and MakeParent
are separate things
MakeFoo<T>
would create a MakeFooCommand<T>
and MakeParent
would create any IMakeFooCommand
. doesn't really matter which (i do have a concrete type in mind, but it doesn't really matter too much)Ah okay, and all subsequent calls after
MakeParent
shouldn't get evaluated unless their parent's command was successfulyeah
I assume
IMakeFooCommand
shouldn't have reference to any of it's children's commands, right ?that'd feel like bad practice, i think
yup, I feel the same
here's the actual real world example, by the way; https://paste.mod.gg/gniwudkzsulo (don't mind the lousy naming)
Yeah that helps illustrate the problem, lemme try something on a smaller scale and see if works
the final classes (
Pointer<T>
, SpanPointer<T>
, StringPointer
, SizedStringPointer
) basically have the same ctor signature as the overloads with nint baseAddress
in the factory
the reason being that the other overloads rely on finding a processmodule in the target process
this obviously can't be found if the process isn't even running yet
and the module may not be found on the first pass through
or the module may not be found at all
because the user misspelled something, or what have you
this is why the factory is "lazy" and only returns what actually succeededOkay, I'm a bit confused now
Would the offsets be known before hand? I don't think so, right?
They would, yes
Oh okay, that should make it a tiny bit easier
This is a different story when we get to Unity where it'll take a bunch of strings which correspond to field names, and we infer the type and offsets just from that via mono API calls...
I think something like this could work, but unnesting feels a bit dirty with the
Top
and Previous
props
https://paste.mod.gg/hjknydjgepys/0BlazeBin - hjknydjgepys
A tool for sharing your source code with the world!
thanks, i'll check it out tomorrow
you do still have empty
MakeParent
calls which aren't really valid, but i assume that's just for the sake of simplicity for the sample?Yeah pretty much
I think the general design idea might work, but unnesting is kinda bad, it's brittle
Dunno if it helps but I just made little test and ended up with this API
And this only builds the nested builders once
Build()
is called.i'm not sure that's right
The thing I mostly just wanted to make a prototype of is the "go up a parent" thing you mentioned way back
i don't really think it makes sense like this though
maybe take a look at this
ILazyPointerFactory
and IChildPointerFactory
are different types
so you can't just "go uppies" onethat's quite the interface
because if you're in the "first" parent (which is already a
IChildPointerFactory
), then "going up one" would return an ILazyPointerFactory
but if you're in the second or later parents, then going up one would return an IChildPointerFactory
again
which is what kouhai meant with the unnesting being annoyingSo child builders can only be built "downwards"
not sure what you mean by that
nvm
again not sure if this is what you want, just a thought
that's what i was thinking of, yeah
the other option is
Was this issue resolved? If so, run
/close
- otherwise I will mark this as stale and this post will be archived until there is new activity.