NotionCommotion Posted June 20, 2018 Share Posted June 20, 2018 I've definitely personally experienced improper use of inheritance which resulted in a complicated and inflexible mess. I am not saying that inheritance is evil or shouldn't be used, but just when misused, yeah, maybe "evil" is the right word. So, I've looked for opportunities to change the behavior of some of my classes by injecting different objects, and have found a few which has greatly simplified matters. But I still struggling to identify more opportunities. Most of the examples I read seem to be all the same and describe injecting an area calculator in a shape or a database interface in a mapper or some other simple example. While this is great, often it is not some defined "thing" I need to inject but a collection of behaviors which I can't seem to define a name for. For instance, we might have Admin extends User extends Person which seems good on the surface, but then I have a need for an Admin who isn't a user and things get wonkie. So, instead I could inject a Person in an Admin, but I've been told that injecting some subset class into a class (i.e. inject a barchart into a chart) isn't right either. So, where am I going with this? I guess I need just more real examples how it is done right. Any recommendations where I might find some (ideally in PHP)? Also, how would you handle the Admin/Person example without using Inheritance? Thanks Quote Link to comment https://forums.phpfreaks.com/topic/307391-composition-over-inheritance-examples/ Share on other sites More sharing options...
NotionCommotion Posted June 20, 2018 Author Share Posted June 20, 2018 For my Admin/User/Person example, maybe an Admin is just a Person but I don't inject an Admin into a Person but inject an AdminControlPanel which provides the capability of an Admin? Not sure how this would work with injecting an AdditionalBarChartCapabilities into a Chart. Quote Link to comment https://forums.phpfreaks.com/topic/307391-composition-over-inheritance-examples/#findComment-1559022 Share on other sites More sharing options...
requinix Posted June 21, 2018 Share Posted June 21, 2018 Admin extends User extends Person means the admin is a user. If the admin is never a user then this isn't appropriate. But if an admin normally/often/generally is then that's fine, and a non-user admin could be something like a service account. But it's worth thinking more about whether it's a user or not. "User" doesn't necessarily have to be a person so I'm not sure User extends Person is right. Or really even what the purpose of a Person class would be. About the chart thing. Injecting a BarChart into a Chart didn't make sense to me because a BarChart "is a" Chart, and that means it should use inheritance. Inheritance is very much so an "is a" relationship, and if you can describe two classes using that term then inheritance is probably the way to go. What would make sense if if you injected a "BarChartRenderer" into a Chart: the renderer takes care of dealing with how bar charts work, and the Chart is basically just a source of data. ...except that's not quite right either. If Chart is a data source then it should not care about rendering, so better would be the reverse of putting a Chart into BarChartRenderer. Which wouldn't really be injection so much as it would be simply one class using another. Normally. Composition is about "is made from" in that some component is a part of how the object works. A sort of functional dependency. A Chart is not "made from" a BarChart so that's not composition. A BarChartRenderer is not "made from" a Chart so that's not composition either. A reasonable example of this would be having a User class be made from a Person: the User represents an entity in the system that's capable of actions, while a Person is surely a human who the User is acting on behalf of. Your code would deal primarily with a User, and that class would do what it needs to do with the Person behavior. Composition is one step away from dependency injection, and most of the time those two can be converted to and from inheritance without much thinking involved. <?php namespace Inherited { // an admin is a user and gets all the behavior of a user class Admin extends User { public function deleteResource(Resource $r) { ... } } // a user is a person and gets all the behavior of a person class User extends Person { public function createResource(Resource $r) { ... } } class Person { public function useResource(Resource $r) { ... } } } namespace Composed { interface IAdmin extends IUser { public function deleteResource(Resource $r); } // admin is made from a user and delegates some behavior to it class Admin implements IAdmin { public function deleteResource(Resource $r) { ... } public function createResource(Resource $r) { (new User)->createResource($r); } public function useResource(Resource $r) { (new User)->useResource($r); } } interface IUser extends IPerson { public function createResource(Resource $r); } // user is made from a person and delegates some behavior to it class User implements IUser { public function createResource(Resource $r) { ... } public function useResource(Resource $r) { (new Person)->useResource($r); } } inteface IPerson { public function useResource(Resource $r); } class Person implements IPerson { public function useResource(Resource $r) { ... } } } namespace DependencyInjected { interface IAdmin extends IUser { public function deleteResource(Resource $r); } // admin must be given a user to depend on class Admin implements IAdmin { private $_user; public function __construct(User $u) { $this->_user = $u; } public function deleteResource(Resource $r) { ... } public function createResource(Resource $r) { $this->_user->createResource($r); } public function useResource(Resource $r) { $this->_user->useResource($r); } } interface IUser extends IPerson { public function createResource(Resource $r); } // user must be given a person to depend on class User implements IUser { private $_person; public function __construct(Person $p) { $this->_person = $p; } public function createResource(Resource $r) { ... } public function useResource(Resource $r) { $this->_person->useResource($r); } } inteface IPerson { public function useResource(Resource $r); } class Person implements IPerson { public function useResource(Resource $r) { ... } } } ?> <?php namespace Inherited { /* this makes sense */ // a bar chart is a type of chart class BarChart extends Chart { public function getBarWidth() { ... } public function setBarWidth($width) { ... } public function render() { ... } } // a chart forms the base for how types of charts work abstract class Chart { public function getTitle() { ... } public function setTitle($title) { ... } public abstract function render(); } } namespace Composed { /* this is bad */ // a chart is made from a barchart. it would have stuff for other chart types too // class Chart { // private $_config; // public function __construct($config) { $this->_config = $config; } // public function getTitle() { ... } // public function setTitle($title) { ... } // public function getBarWidth() { return BarChart::getBarWidth($this->_config); } // public function setBarWidth($width) { $this->_config = BarChart::setBarWidth($this->_config, $width); } // public function renderAsBarChart() { (new BarChart($this->_config))->render(); } // } // a bar chart has bar chart knowledge // class BarChart { // private $_config; // public function __construct($config) { $this->_config = $config; } // public static function getBarWidth($config) { ... } // public static function setBarWidth($config, $width) { ... } // public function render() { ... } // } } namespace DependencyInjected { /* more reasonable than composition, and a different approach to the problem than inheritance */ interface IChartRenderer { public function render(Chart $chart); } // a chart has chart data class Chart { private $_config; private $_renderer; public function __construct($config, IChartRenderer $renderer) { $this->_config = $config; $this->_renderer = $renderer; } public function getConfig($key, $default = null) { return $this->_config[$key] ?? $default; } public function getTitle() { ... } public function setTitle($title) { ... } public function render() { $this->_renderer->render($this); } } // a bar chart renderer knows how to render charts into the bar style class BarChartRenderer implements IChartRenderer { public function render(Chart $chart) { ... } } } ?> I wrote a lot so I'm going to stop there, but I don't think I said everything I was intending to say. Quote Link to comment https://forums.phpfreaks.com/topic/307391-composition-over-inheritance-examples/#findComment-1559023 Share on other sites More sharing options...
requinix Posted June 21, 2018 Share Posted June 21, 2018 So I'm working with React and they strongly suggest composition over inheritance. I still don't understand why. Here's composition like they say (slightly rewritten): class Dialog extends React.Component { render() { return ( <FancyBorder color="blue"> <h1 className="Dialog-title"> {this.props.title} </h1> <p className="Dialog-message"> {this.props.message} </p> </FancyBorder> ); } } class WelcomeDialog extends React.Component { render() { return <Dialog title="Welcome" message="Thank you for visiting our spacecraft!" />; } } This is inheritance: class WelcomeDialog extends Dialog { constructor() { super({title: "Welcome", message: "Thank you for visiting our spacecraft!"}); } } Quote Link to comment https://forums.phpfreaks.com/topic/307391-composition-over-inheritance-examples/#findComment-1559024 Share on other sites More sharing options...
ignace Posted June 21, 2018 Share Posted June 21, 2018 (edited) Admin seems more like a privilege/role, so I would make that a field of User. Then your User and Person. I get what you are saying, and I have this in my system. I would have a Person object with no reference to a User. And let the User reference Person. Thus decoupling my application from the auth layer. <?php class User { const ROLE_SYSTEM = 'role_system'; const ROLE_ADMIN = 'role_admin'; /** * @var string[] */ private $roles = [ ]; /** * @var Person|null */ private $person; } For system accounts, the Person reference would be NULL as it's not required and it's role would be ROLE_SYSTEM. Edited June 21, 2018 by ignace Quote Link to comment https://forums.phpfreaks.com/topic/307391-composition-over-inheritance-examples/#findComment-1559026 Share on other sites More sharing options...
NotionCommotion Posted June 21, 2018 Author Share Posted June 21, 2018 I like your "is a" and "made from" tests. For a simple applicable, inheritance is simple and concise, but the class size and complexity will increase as functionality is added and there is the potential that functional requirements will diverge. At that time, I will ask myself what abstract concept is it made from and break it out. I am sure it will take time and practice, but think it is a good start. I even pinned a sticky note (something I never do) stating these two tests so I don't fall out of the habit of asking myself them. Quote Link to comment https://forums.phpfreaks.com/topic/307391-composition-over-inheritance-examples/#findComment-1559028 Share on other sites More sharing options...
Recommended Posts
Join the conversation
You can post now and register later. If you have an account, sign in now to post with your account.