0

I am challenging my OO design skills and started an ambitious project that is supposed to be highly reusable and extensible. It is supposed to be kind of a framework for evolutionary algorithms. Maybe there is somthing out there on GitHub, but my research didn't come up with anything I'm looking for and anyway, I'd like to see if I could develop it myself and improve my skills.

The very problem I'm facing is the multitude of potential interfaces I can imagine I or some potential user might need, because implementations of the objects are so different they even have different interfaces. That might sound strange or like bad design, but I think it's due to the meta level I'm thinking at. Let me give you an abstract example, maybe you spot the right pattern I'm missing. The current pattern is Strategy Pattern, but the interface will vary according to different DataInterfaces:

class Client {
 /** EvaluatorInterface */
 private $evaluatorStrategy;
 public function getData(): DataInterface {
 //return data...
 }
 public function doEvaluation()
 {
 $value = $this->evaluatorStrategy->evaluate($this->getData());
 //...
 }
}

The problem is, that in this client code, I do not exactly know, what the DataInterface and thus the EvaluatorInterface must look like, since I want to be able to both change the type the data is stored in and the strategy the evaluator works with. What I do know, is that the both need to match. E.g. When I decide to add a new fancy data structure with a new interface, I need to adjust the injected EvaluationStrategy accordingly.

So, since this is PHP I could just don't specify any types and just hope we have data and evaluator strategies that can work together, or otherwise we get a runtime error but that's not what I want. The design should be clean, and I think it must work somehow.

Another thing I tried was Visitor Pattern. At first it sounded promising, but then I realized, that the EvaluatorInterface would need grow each time I'd add a new data type, something a user of my package couldn't even do.

Do you have any ideas where I'm barking up the wrong tree? Or what pattern could help me?

asked Aug 27, 2021 at 16:06
7
  • 2
    "both need to match" Are you saying that each instantiation of EvaluatorInterface and DataInterface should have one single data type that they operate on, and those should be the same type, i.e. you would prefer if php had generics? Commented Aug 27, 2021 at 16:15
  • Yes, kind of. I think Generics would be a solution. So i could write a Client<SpecialDataInterface><SpecialEvaluatorInterface>. But I thought there might be a way without "fancy" language features and just simple, well considered OO Design ;) Commented Aug 27, 2021 at 23:50
  • No, Client<T> holds a DataInterface<T> and a EvaluatorInterface<T>. Can't mismatch DataInterface<Foo> with EvaluatorInterface<Bar>, it doesn't typecheck. Commented Aug 28, 2021 at 8:16
  • I agree. Since I do not have Generics in PHP, I'd still like to find a way around. But maybe it's best for me to just "simulate" Generics using php docs (which support Generics with @template) to document the use properly. Commented Aug 28, 2021 at 10:10
  • Update: Well, Generics support in PHPDoc and related tools seems to be still kind of young and underdeveloped. Now I understand why Java people make fun of PHP :( ;) Commented Aug 28, 2021 at 11:10

3 Answers 3

1

With @caleth pointing me in the right direction and some more research, I think I can answer my own question like this:

I think in general, the problem seems to be made for generics. Since PHP doesn't support generics, I'll need to leave out real generic TypeHints. Instead I'll document the right types using the @template T (with @var T and @return T and so on...) annotations. Moreover I'm considering integrating some kind of static analysis tool like PHPStan to provide some ability to check the correct types as in JAVA a compiler would do.

So, my code looks like this now:

/**
* @template T of DataInterface
*/
class Client {
 /** @var EvaluatorInterface<T> */
 private $evaluatorStrategy;
 /** @return T */
 public function getData() {
 //return data...
 }
 public function doEvaluation()
 {
 $value = $this->evaluatorStrategy->evaluate($this->getData());
 //...
 }
}
answered Aug 31, 2021 at 8:43
2
  • Generics are well supported in PHP static analysers PHPStan and Psalm, and somewhat supported in PhpStorm. If you can make sure your code is never run in production without having passed static analysis check first you may find that is enough and you don't need generics support in the actual language. Commented Jan 28, 2022 at 13:26
  • There's even github.com/Roave/you-are-using-it-wrong in case you want to make sure that downstream consumers of your library respect your generic types. Commented Jan 28, 2022 at 13:27
0

The whole idea of an Interface is that it is a contract which tells the caller what is expected. Allowing any interface is usually consider a bad idea, because:

  1. If any object type to allowed to be passed in, reflection is required to find the methods and properties, but even if that was done, foreknowledge of how to use them is required.
  2. Weakly typed languages like JavaScript, do this all the time. If they want to append something to an object they just do it. We are able to use this pattern in Strongly typed languages but it does not diminish the foreknowledge factor.
  3. The newer patterns for Strongly typed languages bring the Generic factor.
// Allows any type to be passed in as a parm
public method<ofType>(ofType parm1){
 //still requires knowledge of how to use. 
}
  1. The traditional pattern is to protect our code as follows. In this pattern we are saying if you want to use this method there's only these interfaces that we support.
//Only allow known interface implementations
public method(InnterfaceTypeA NameA){ //we know this interface }
public method(InnterfaceTypeB NameB){ //we know this interface }
public method(InnterfaceTypeC NameC){ //we know this interface }
  1. This pattern uses the keyword Dynamic (which C# has)
 //Still requires discovery of the type and how to use it.
 public method(Dynamic Parma){}
answered Aug 28, 2021 at 9:16
1
  • I agree with you in terms of "the implementation of the method must know the interface", but what your examples do not consider, is that I have TWO interfaces. When these two match in terms of "know each other" or, concretely, in my example EvaluatorInterface<SomeDataInterface> knows how to deal with SomeDataInterface, there is enough knowledge for it to work. I think Caleths comment(s) to my question hit(s) the point. Commented Aug 28, 2021 at 10:03
0

I am a bit out-of-touch with PHP, so I'll give it for java. It is reminiscent of OpenGL/DirectX discovery of capabilities; which was not done systematically.

My Solution

What you want, is the detection of (yet unknown) capabilities.

Every capability is an interface.

public class Client {
 public <T> Optional<T> lookup(Class<T> capability) { ... }
}
public interface Flying {
 /* @return average speed */
 int flyDistance(int distance);
}
public interface Swimming {
 /* @return average speed */
 double swimDistance(double weight);
}
Client client = new BirdClient();
client.lookup(Flying.class)
 .ifPresent(flying -> System.out.printf("Avg speed: %f%n",
 flying.flyDistance(50)));

In PHP instead of Optional null etcetera.

Reason

The evaluation needs structured data, specific to the evaluation class. The classes are open ended.

If you would have the evaluation class in the client as a very general class, you still would not have the data interface appliable on the world data.

So you must start with the specific class (Flying/Swimming), and then ask the client whether it can do that.

You might also be more open, have 2 evaluation strategies for a client, old and new, or on for other aspects, time and money.

And the other reason: this is evaluation centric; you start with the processing: the evaluation: as interface. And then you tailor your client as such. It is much less obvious if you have a "data object" and therein have to store the data object for this evaluation strategy, and so on, swimming in muddy waters.

answered Jan 28, 2022 at 13:11

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.