Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using the "new" keyword in constructors

Tags:

oop

php

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();
    }
}

???

like image 242
mister martin Avatar asked Dec 01 '22 20:12

mister martin


1 Answers

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.

like image 101
Colin M Avatar answered Dec 15 '22 12:12

Colin M