MVC: ¿Cómo puedo reducir la cantidad de métodos polimórficos?

I am working on an MVC framework in PHP but I work with java primarily and I am looking for a solution to this problem which adheres to OOP principles and is transferable to other languages.

I have a model abstract class that interacts with some data (database, xml, whatever). Inheriting classes have to implement these methods:

abstract class Model {

abstract public function nextItem();

abstract public function insert(Map $item);

abstract public function update(Map $item);

abstract public function delete(Map $item);

abstract public function exists(Map $item);

abstract public function countItems();

abstract public function allItems();

The controller passes a Map object which holds information about an item that is to be inserted, updated, deleted etc. This means that the model and controller are decoupled and any Model can be injected into a controller provided there is an implementation of these methods.

When using this class in practice I have found that there have been instances where the operation needed by a controller is highly unique such as re-ordering data in a specific way. This is a BAD solution:

abstract public function reorder(Map $item);

This solution means that EVERY Model must implement this method which isn't necessary. Also Imagine if I needed other methods, the amount of abstract methods would simply grow and grow, each needing an implementation.

Otra solución sería esta:

abstract public function action(Map $item, $action)

The $action variable would be a string that defines an action. So you could implement different methods but only call them with the polymorphic action() method:

if ($action === "reorder") { $this->reorder($item); }

The only problem with this solution is that the correct commands are not obvious from the method signature. For example the $action string could be anything and another developer would have to examine the method body (implementation) to find the admissible strings. Simply stating them in the documentation seems a flimsy solution. Also, what if a model is injected into a controller which doesn't implement all the required actions? Throw an exception?

It seems like I must be missing some kind of really obvious solution and I don't want to go ahead and implement the one above and then have to heavily refactor later when I find a better one. Any ideas?

Edit: Using multiple interfaces seems to be the best solution so far. There is a type safety issue though. If I am to inject a Model which implements interface ReOrderable into my Controller class I want to be able to do __construct(Any Model that implements these interfaces $model). I could make more abstract classes such as ReOrderableModel and then do __construct(ReOrderableModel $model) but there could be any number of combinations of interfaces and I would have to define an additional abstract class for each one. I could also turn Model into an interface and use multiple interface inheritance but the same problem essentially arises. I must be missing something.

preguntado el 10 de marzo de 12 a las 00:03

The structure in the current form is violating the Principio de responsabilidad única. Por qué ? -

3 Respuestas

I would define a, IReOrderable interface, and have your actual Model class implement this interface. It doesn't need to be done at the abstract class level if it is not meant for todos inheriting classes.

Yo uso uno abstract Model class, and then a ton of Interfaces para el real Model implementaciones

respondido 10 mar '12, 00:03

This seems like the best idea. Only problem is when injecting the dependency into the controller constructor: "__construct(Model $model)", I don't want to use IReOrderable for the type declaration as I don't want to simply allow any class that implements it to be injected. I could make more abstract classes I guess: "class ReOrderableModel implements ReOrderable"? Ideally I would like to say __construct(Any Model that implements these interfaces $model) but that is impossible of course. - Jonathan

One option (not sure if it makes sense in your context) is to mimic the Java Collections. Certain operations will throw a UnsupportedOperationException. Trouble is, the caller seldom knows when to expect there.

If you use your second solution, where the 2nd argument indicates the action to be taken, I'd strongly suggest an Enum. (Does php have an equivalent???) Makes it way easier for another programmer to figure out the possibilities, and it definitely avoids many mistakes.

respondido 10 mar '12, 00:03

Yeah I considered that but PHP doesn't support enums and only supports constants which is a bit annoying. PHP doesn't have generics either. Hopefully there will be support for them in the future. Great idea though thanks! The interfaces idea might be the best one so far. - Jonathan

For one thing not all the methods in an abstract class needs be abstract. So there is the option of creating concrete versions of the highly specialized methods and have the child class override those as necessary. Perhaps not as appealing depending on what you want, you can also use additional interfaces to supplement the abstract class. Said interfaces would be implemented by the needing child classes.

To illustrate the above, you can have

 public void function reorder(Map $item){
     //this is a concrete function that does nothing

Because the function is not abstract, then it does not have to be overridden by the children.

respondido 10 mar '12, 02:03

No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas or haz tu propia pregunta.