0

In Doctrine, assuming I want to implement different types of vehicles. Maybe a car, a plane, a bicycle and so on ... all these vehicles have many different properties but also very common things like they are all made of a material.

So if i want to get this material ... should i implement a abstract class Vehicle with a property material and implement getter/setter or should i define a interface IVehicle and define the getter/setter in there to be sure there is really a method for getting and setting the material? Or should i even use both in combination?

It feels "professional" using the interface ... but it feels wrong to define getter/setters in interfaces, so my personal feeling is:

Don't use an interface, just use the abstract class.

But is the abstract class approach protected against misuse? For example on another place i definitely expect a Material type returning from the getMaterial() function ... is the abstract class approach also save for not returning complete unexpected things (like the interface does)?

So if i extend this vehicle for another concrete class, a developer should not be able to return unexpected things, but should also be able to change the logic in the particular method if needed.

Jim Panse
  • 338
  • 3
  • 11
  • Why not use both? You can create an abstract `Vehicle` class which implements some interfaces, e.g. `Drivable`, `LoadableBoot`, etc. Each interface only defines specific methods. – imel96 Jul 19 '18 at 09:15
  • Yes thats right, but should i also move setters/getters method into the interface when i expect on another places to definitely return a particular type? or are they fine in the abstract class? can i be sure that the getter is not overwritten by someone and returning unexpecting things? – Jim Panse Jul 19 '18 at 09:24
  • No, setters/getters don't really belong in the two interface examples that I provided. `Drivable` should only have methods like `accelerate()` & `stop()`. Setters/getters are not really part of something that defines `Drivable`, it make sense to `setFuel()` for a car but not for a bicycle and you can still sort of "drive" a bike without it. I'm not sure what you mean by interface can return unexpected things, unless you're not using PHP >= 7.0, you can specify return type of your methods. – imel96 Jul 19 '18 at 09:44
  • Yes okay i understand ... with unexpected things i mean, if i write a method "getMaterial" in my abstract class and a programmer overwrites this method and returns a string to me instead of my expected Material-type ... can the inheritance approach avoid this? I am really sure, an interface is made just for that ... but abstract classes!? – Jim Panse Jul 19 '18 at 09:49
  • I haven't use Doctrine for a long time, but if you can use return type, it shouldn't matter. Whether you use class or interface, you can define "getMaterial" method as `function getMaterial(): Material` and it will only return `Material`. Without using return type, there's no way enforce what `getMaterial()` can return. I think you should give some example code if you want to get more detail answer. – imel96 Jul 19 '18 at 10:00
  • please don't **[cross-post](https://meta.stackexchange.com/tags/cross-posting/info "'Cross-posting is frowned upon'")**: https://stackoverflow.com/questions/51417846/should-i-use-an-abstract-class-or-an-interface-for-my-doctrine-model "Cross-posting is frowned upon as it leads to fragmented answers splattered all over the network..." – gnat Jul 19 '18 at 11:11
  • yes i forgot to close, sorry – Jim Panse Jul 19 '18 at 11:31

2 Answers2

1

The golden rule is favor composition over inheritance. This means that you should not use abstract base class.

Despite that you have to distinguish between "Data Objects" and Objects providing "business behavior".

Data classes should have (almost) no logic. They provide access to the contained values via getter/setter method. Business classes should not expose their implementation details and therefore have no getter/setter at all, only methods to apply the business behavior to data objects either passed in as parameters or held as internal state.

Composed objects might not expose their components.
Prominent example is Point2D and Point3D. This example is usually used to introduce inheritance but when Point3D extends Point2D they fail the equals contract since point2D.equals(point3D) is true (with most inheritance based implementations) while reverse point3D.equals(point2D) is false.
(equals contract requires that both either fails or passes for the same two objects.)

But a composition based implementation of Point3D should not have a getter to the Poind2D component but should delegate the access to the Point2D attributes.

Conclusion

  • Inheritance should be used with interfaces.
  • concrete classes should be composed.
  • Never have business classes exposing their member variables (neither by direct access nor through getter/setter)
  • Think carefully if data classes should expose their member variables or if they would better delegate access.
Timothy Truckle
  • 2,336
  • 9
  • 12
  • 1
    Okay, that is a good explanation. So i have the problem on an other place. In PHP, especially Symfony with Sonata Admin and Doctrine isn't really designed to use an extra business logic between controller and entities ... all goes just into entities. If i would use Symfony only i could attach an layer for business logic by myself, but in sonata admin configuration you say only "This is my controller" and "This is the corresponding entity to manage" ... no business logic. Conclusion: To design it really clean, my used framework is not suitable for that. Thats a pity ... – Jim Panse Jul 19 '18 at 11:30
  • At least, there is no easy way to this without rewriting huge parts of the core functionality of this bundle – Jim Panse Jul 19 '18 at 11:36
1

You wouldn't use an interface unless you absolutely needed to. Using your example of vehicles, supposing your program supports Vehicle abstract class. Classes which derive from this include Car, Motorcycle, Bus, etc. You use this in your program and everything is fine.

Then tomorrow your boss comes up to you and asks that you also deal with trains.. Trains are different. They work on rails, they have stops, they can't reverse. They are implemented entirely differently from your Vehicle abstract class which at least allows free range of direction and are implemented as such.

It makes sense at this point to add an interface called Transportation which deals with taking something from point A to point B. This fits both Vehicle and your new Train class. Your program would then prefer Transportation whenever applicable throughout and you would use it only in terms of taking a person from point A to point B.

The interface fits here because:

  1. What you need from the class isn't specific to Vehicle (you merely need to move from one point to another).
  2. Implementation of one class is entirely different from the implementation of another.

Needless to say, if you only have one implementation (which is to say, prior to the need to create Train class), then a Transportation class is superfluous and unnecessary. It only adds to the complexity of your program with no obvious benefit.

In other words, it isn't about looking professional or not. You write the program in such a way as to minimize complexity whenever possible. Interfaces are an acceptable complication of your program should it fit the above two points. Otherwise, don't be afraid to use the real or abstract class instead.

Neil
  • 22,670
  • 45
  • 76
  • I think this is the best explanation to me. Maybe no matter what to choice, there are always pros and cons using this or that ... – Jim Panse Jul 19 '18 at 13:31