dagfinn | 02 May, 2008 23:05
Everybody who writes object-oriented code knows about constructors. You need them so the program knows how to instantiate objects, right? And you especially need them when a lot of things have to be done while instantiating an object. And personally, I've never considered visibility restrictions important enough to be a major argument against those languages that have lacked them (PHP 4). So why would I be skeptical of public constructors?
I got the idea after reading Joshua Kerievsky's book Refactoring to Patterns. One of his refactorings is called Replace Constructors with Creation Methods. In Java, unlike PHP, you can have multiple constructors that are distinguished only by the number and type of arguments. That may be practical sometimes, but as Kerievsky's example shows, it be more readable to have creation methods with different names instead. Which is what you have to anyway in PHP.
But why not take this one step further and do it even when there is only one constructor? For example, here's an ultra-basic Redirect class:
class Redirect { private $url; public function __construct($url) { $this->url = $url; } public function execute() { header("Location: ".$this->url); exit; } }
This may seem simplistic, but it is the kind of class I might actually use at an early stage in the development of a web application. Anyway, we can apply what is essentially the same process as Kerievsky's by adding a creation method whose name is somewhat more telling than __construct().
class Redirect { private $url; public function __construct($url) { $this->url = $url; } public static function withUrl($url) { return new self($url); } public function execute() { header("Location: ".$this->url); exit; } }
Now all that remains is to make the constructor private.
private function __construct($url) { $this->url = $url; }
Is this an improvement? The additional code might be considered unnecessary complexity, or in a word: clutter. On the other hand, our client code might seem more readable when it looks like this:
$redirect = Redirect::withUrl("http://www.example.com/index.php"); $redirect->execute();
Instead of:
$redirect = new Redirect("http://www.example.com/index.php"); $redirect->execute();
You could object that it's fairly obvious anyway in this example that we're inputting a URL. But if the URL string is supplied as a variable or method call instead of a plain string, it would be less obvious.
No doubt there are many cases in which a plain constructor is the best choice, but I think this is worth considering.
Ben Ramsey | 05/05/2008, 09:30
Jared | 05/05/2008, 09:31
Joshua E Cook | 05/05/2008, 10:16
Federico | 05/05/2008, 12:31
Federico | 05/05/2008, 12:33
Marques Johansson | 05/05/2008, 13:45
dagfinn | 05/05/2008, 15:26
philippe | 06/05/2008, 06:17
using static factories helps me to manage caching/pooling features and more. Basically something like that:
public static function getInstance($id){
global $cache;
if($cache->contains($id)){
$a=$cache->get($id);
}else{
$a=new myClass($id);
$cache->add($a);
}
return $a;
}
Matthew | 06/05/2008, 07:07
This is not something I've thought about before, but it's an interesting idea. It also has the benefit on attaching more meaning to the argument that needs to be passed.
Two quick nitpicks:
* header("Location: $url"); should be header("Location: {$this->url}");
* You can do away with __CLASS__ by just using self (at least on PHP5). i.e. return new self($url)
(I'm sure the formatting will be a mess, but I can't find a link to a list of valid markup)
Ben Ramsey | 06/05/2008, 07:09
@dagfinn: While I understand that using the word "harmful" is a good attention-getter, what does it teach PHP programmers who are new to OOP? I don't think an argument can be made here for using static factory methods over constructors as being a "best practice." It's really an application architecture decision. The truth is: well-documented code can solve this problem, and using something like PHPDocumentor with PHP docblocks can provide clarity for your entire team with regard to the parameter list for a public constructor. Using the static factory method is definitely a great tip and can improve readability, but we shouldn't mislead programmers into thinking public constructors are harmful. :-)
dagfinn | 06/05/2008, 07:47
@Ben: I appreciate your point, but I'm not trying to teach basics. I'm thinking out loud about something that the gurus appear not to have considered so far. (I haven't seen it discussed before, anyway.) Maybe I should have made that clearer. It still seems to me that an intention-revealing creation method (it's not strictly a factory method) is superior to a plain constructor. I share Jared's skeptical attitude to static methods in general, but plain constructors have the same problem, since the client code has no instance avilable that can be replaced with one belonging to another class (as in Abstract Factory). As for documentation, my view is that method naming is more important and robust, because it's much harder for it to get out of sync with what's happening in the code.
dagfinn | 06/05/2008, 11:09
@Matthew: I fixed your nitpicks. I thought I could implement it untested, but I couldn't. Why am I not surprised? ;-)
niKo | 06/05/2008, 11:20
I can't resist it'd be a bit better like this Redirect::withUrl("http://www.example.com/index.php")->execute();
Mark Shapiro | 06/05/2008, 12:55
Delphi (ObjectPascal) doesn't use the 'new' syntax at all, though it does have 'constructors'. In a Delphi class, the default constructor is always named Create, but you can have other constructors with other names - by convention, these usually begin with the word Create, so you would have something like:
(in the class definition:)
constructor CreateWithURL( url: String );
(in the calling code:)
redirection := Redirection.CreateWithURL( "http://....");
The fact that CreateWithURL is defined as a constructor rather than a class (static) method changes the semantics of when/how it can be called, and what happens when it is called.
I like this style of constructors because it retains the constructor semantics while giving you the self-documenting benefits of creation methods.
Of course, with named constructors, the normal 'new' syntax would have to be changed to support identifying which constructor you want to call using something other than parameter types, and you'd need some syntax to identify that a method is a named constructor rather than some other type of method.
Michael Muryn | 12/05/2008, 11:30
My first comment would be that a static method named "withUrl" is maybe not significative (as static methods are not used only for creation). I would suggest to always use something that explains that the method will create an instance like Mark Shapiro explained with his Delphi example (e.g. create*).
Of course, someone that want to put the emphasis on the "new" keyword might want to do a pattern that support code like new Redirect("with_url", "http://www.example.com/index.php"); -- but that is only another way to do a same thing and might have the disadvantage of being less obvious, less separated, etc. (but now we enter into more subjectivity!)
All in all, naming a constructor when it has parameters might be more significative (and having a default [public] constructor with no data might be OK -- as a lot of case are like this, so making it private might not be OK, unless you want to explicitly do static "create" method for each of your class!).
My second comment to niKo: ... or if OO is not important... redirect_url("http://www.example.com/index.php");
| « | January 2009 | » | ||||
|---|---|---|---|---|---|---|
| Su | Mo | Tu | We | Th | Fr | Sa |
| 1 | 2 | 3 | ||||
| 4 | 5 | 6 | 7 | 8 | 9 | 10 |
| 11 | 12 | 13 | 14 | 15 | 16 | 17 |
| 18 | 19 | 20 | 21 | 22 | 23 | 24 |
| 25 | 26 | 27 | 28 | 29 | 30 | 31 |
Re: Public constructors considered harmful
Glen | 03/05/2008, 06:03