I've recently read that using the keyword "new" in a constructor is highly frowned upon, but I'm not sure I understand why? For example, how is:
class A {
public $foo;
function __construct() {
$this->foo = new Bar();
}
}
Any different from:
class A {
public function someMethod() {
$foo = new Bar();
}
}
???
This is really the theory behind dependency injection.
It's not that using "new" is a bad idea, per se. Rather, by instantiating objects inside of your class your are creating hard dependencies which can never be changed or switched out without changing the class itself.
It also violates the paradigm of "coding to interface, not implementation"
Example:
class Phone {
protected $network;
public function __construct() {
$this->network = new Verizon();
$this->network->distinctiveRing();
}
}
class Verizon {
public function call($number) {
....
}
public function distinctiveRing() {
}
}
Now, suppose one day you wanted to create an ATT, TMobile and Sprint phone? Surely they are all able to make calls, and can do so having just a phone number. Also, the phone class shouldn't care who the carrier is, as it's job is to facilitate entering a number - not to actually make a network connection, right?
So, that being said, we shouldn't have to create a new SprintPhone
class that can instantiate another Sprint network object, right? Right.
So what's the better way?
class Phone {
protected $network;
public function __construct(NetworkInterface $network) {
$this->network = $network;
}
}
interface NetworkInterface {
public function call($number);
}
class Verizon implements NetworkInterface {
...
}
class Sprint implements NetworkInterface {
...
}
Well now, you can just say: $phone = new Phone(new Sprint())
or $phone = new Phone(new Verizon())
Also notice that our call to distinctiveRing
is gone. Why? Well, because we don't know that any object implementing NetworkInterface will necessarily support a distinctive ring. But this is good because now our Phone
can support ANY Network
with no code changes.
If you want support for distinctive ring, you can always create a new interface that supports the distinctiveRing
method. In your Phone
object, you can check if your Network
implements DistinctiveRingerInterface
and, if so, make your distinctive ring. But you're no longer tied to a specific network with this approach. Better yet, you were forced to do this because you took the right approach from the start.
And, any other network which can feasibly be created later down the road. More importantly, your class no longer has to care about what kind of Network object it's given. It knows, (because it received an object implementing NetworkInterface), that the Network
object is able to make a call
with a $number
.
This also tends to lead to much better code, with a better separation of concerns.
And finally: testing.
With the first example, if you tried to test your Phone object, it was going to make a call on the Verizon network. Sucks to be the person getting called all day because you're running unit tests, right? Right.
Well, simply create a TestNetwork class that implements NetworkInterface, and pass that to your phone object. Your TestNetwork class can do anything you want inside of it's call
method - or do nothing.
Furthermore, you can just create a mock object using PHPUnit
, and ensure that the call
method on your TestNetwork actually gets called. You couldn't do this before because the Network
was being instantiated inside of your Phone
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With