4

In some high level package (which I don't own) I have a Route interface.

Now this Route interface contains the detail for a specific rout within my system. The high level package does not contain a method to add get parameters to the route, like /customers?a=b.

class CustomerRoute extends BaseRoute implements RouteInterface 
{
    public function __construct()
    {
        $this->setController(CustomerController::class);
        $this->setAction('index');
        $this->setUrl('/customers');
        $this->setType('get');
    }
 }

I want to add an extra method; setGetParameters like for example in a new base class:

public function setGetParameters(array $parameters)
{
    $this->parameters = $parameters;
    $this->setUrl($this->getUrl() . $this->buildQueryString($parameters));
}

protected function buildQueryString(array $parameters)
{
    return '?' . http_build_query($parameters);
}

But this would violate the Liskov Substitution Principle. If I create a dependency to RouteInterface I may not access setGetParameters.

Like:

$route->setGetParameters([
    'a'=>'b'
]);
$route->getUrl();

What is the best way to go?

I have considered:

Adapter pattern (object)

Like so?

class RouteAdapter implements Contracts\RouteAdapter
{
    protected $route;

    public function __construct(RouteInterface $route)
    {
        $this->route = $route;
    }

    public function setGetParameters(array $parameters)
    {
        $this->parameters = $parameters;
    }

    //the route methods
    public function getUrl()
    {
        $this->route->getUrl() . '?' . http_build_query($this->parameters);
    }

    //... other methods from route 
}

Decorator pattern

But with decorator (inheritance) the interface of the route is not allowed to change.

Vistor

But visitor is better used for different types of classes.

What would you do if you wouldn't own the high level package and what would you do if you did?

Would you change the base class to add the methods and also change the RouteInterface?

Update 1

Is extending the interface a solution?

This might not be a specific design pattern.

Like so:

class Route extends NewAbstractRoute
{
   //...
}

abstract class RouteInterface //the original interface
{
    //....
}

abstract class NewAbstractRoute extends RouteInterface 
{
    public function setGetParameters(array $parameters);
}

class Implementation extends NewAbstractRoute
{/*..*/}

In this way It would not violate the Liskov Substition Principle for the package and I can make a dependency to the new interface.

But this solution might not be good since it is best not to use inheritance to add methods, since it might creates too many subsystems.

  • 3
    To get an incorrect notion out of the way: Adding functionality to a class that is not accessible from the base class/interface is not a violation of the Liskov Substitution Principle. It would only be a violation if the derived class changes the contract specified by the base class/interface, not it it adds something to it. – Bart van Ingen Schenau Mar 10 '17 at 11:27
  • Yes. But consider this, what the implementation would be in my code: controller.index(RouteInterface $route) { $route->setGetParameters(['a'=>'b']); } This would a violation of LSP because the concrete implementation cannot be replaced by the abstraction. This is also true for a factory method with a return type: function factoryMethodExample() : RouteInterface { return new ConcreteRoute; }. And my code would implement it as such: controller.index() { $this->factory->factoryMethodExample()->setGetParameters(['a'=>'b']); }. – Stefan Mar 10 '17 at 11:43
  • If you only know about a more restricted interface than the full capabilities that a concrete class offers, then it is not a LSP violation that you can't access the extended capabilities of the concrete class. It would be a violation if ConcreteRoute::getUrl returns something other than a URL, because then you can't use a ConcreteRoute object where a RouteInterface is expected. – Bart van Ingen Schenau Mar 10 '17 at 12:59
1

The point of saying that a bit of code follows a pattern is to communicate the intent of the code. The intent of an adapter is to do the same thing in a different way to make the incompatible compatible. An adapter sits between two objects that need to communicate and allows them to work without either having to change the way they communicate.

Exposing a new method that adds a new feature doesn't do any of that.

Extending the interface is a better way to describe what you're doing. Nothing was previously demanding this extended interface. You're now free to create things that can use it.

The difference here has nothing to do with whether what you've done was a good solution. Patterns should not dictate your design. They are simply a way to communicate what you are doing.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.