I have a situation where I have a "Task" class that can "progress"
However each Task has different required param to know how to progress, and therefore the "progress" should receive different parameters.
What I did was making sure each "Task" implements an "ITask" interface which forces the implementaion of "progress" method. The parameter for the "prgoress" method is "ITaskProgress" interface, which will allow me to put the right task progress for each task.
The problem is I need to reflect the taskProgress inside the Task when I use it to progress, to know which members/method exists inside the "taskProgress" so I can use it to progress.
I don't feel this is the right way, how would you put the pieces together the right way?
Assume that because Task and Task Progress have to be saved in different places they cannot live under the same class.
interface ITaskProgress {};
interface ITask
{
function progress(ITaskProgress $taskProgress);
}
class DoThisTaskPrgoress implements ITaskProgress
{
public $needThisToProgress;
}
class DoThatTaskProgress implements ITaskProgress
{
public $needThatToProgress;
public $alsoNeeded;
}
class DoThisTask implements ITask
{
function progress(ITaskProgress $taskProgress)
{
if ($taskProgress instanceof DoThisTaskPrgoress)
{
$taskProgress->needThisToProgress++;
}
}
}
class DoThatTask implements ITask
{
function progress(ITaskProgress $taskProgress)
{
if ($taskProgress instanceof DoThatTaskProgress)
{
if ($taskProgress->needThatToProgress > $taskProgress->alsoNeeded)
{
$taskProgress->needThatToProgress = 0;
}
}
}
}
I know it's a bit opinionated as an answer, but as far as you're interested in my opinion, I think you can trust your feelings and should lean stronger on them:
I don't feel this is the right way, how would you put the pieces together the right way?
Also the question title makes it clear that you dislike to use reflections. So getting rid of reflections is probably a good way to put things into the right way.
So right now you need to analyse an object so that you can use it. This normally is pretty cumbersome with objects, your case is just another example of that.
But why is it so cumbersome? Well to benefit of objects they should be more abstract. However here in your case (please don't see it as an insult, just as another example) you need to write very specialized code only to use the object (Tell Don't Ask).
This is sort of as if it turned inside-out. Instead of that the progress of a task is an implementation detail, the task itself needs to deal with different kind of progresses.
So how to resolve this? First of all don't do what you don't want to do (and only do what you must do, according to your question, that is if I understood you right all the things to deal with the obstacle of "polymorphism" [the way you name it]), so remove the if
s:
class DoThisTask implements ITask
{
function progress(DoThisTaskPrgoress $taskProgress)
{
$taskProgress->needThisToProgress++;
}
}
class DoThatTask implements ITask
{
function progress(DoThatTaskProgress $taskProgress)
{
if ($taskProgress->needThatToProgress > $taskProgress->alsoNeeded)
{
$taskProgress->needThatToProgress = 0;
}
}
}
So now this feels better as the reflection (type checks) are moved out of the progress implementation.
The specific contract is now only between the caller of some task's progress method needing to take care to provide the correct concrete task progress parameter.
As PHP has no concept of "friends" in visibility, type-hinting the more specific task progress type (in comparison to the task progress interface) is crucial here so that it is clear which specific type the progress expects to work on (Don't Look for Things).
And this brings us closer to inversion of control which we can experience often in object oriented programming. Now it is clear that telling a task to progress needs a specific task progress, an object representing state injected as method parameter.
If this is the foremost design you choose to solve your overall problem, it should also guide you in finding the parenting pattern for your object oriented design. E.g. it looks like you need (at least a very small) abstract factory so whatever is progressing is from a family of classes (progress and task) so a central factory can provide this family. And the factory can be injected (inversion of control) so that this family of classes can be "one" in your overall application.
Which task then needs which progress then can be resolved by the factory and the rest of your code should just work(tm).