Upload
gooh
View
36.158
Download
4
Tags:
Embed Size (px)
DESCRIPTION
While Singletons have become a Pattern-Non-Grata over the years, you still find it surprisingly often in PHP applications and frameworks. This talk will explain what the Singleton pattern is, how it works in PHP and why you should avoid it in your application.
Citation preview
Singletons inSingletons in Why they are bad and how you can eliminate
them from your applications
Gordon OheimGordon Oheim@go_oh
a.k.a.The GoF Book
Singleton
Creational Pattern„control when and how objects are
created“
“Ensure a class has only one instance, and provide a global
access point to it.”
Singleton- instance : Singleton+ getInstance() : Singleton- __construct() : void- __clone() : void- __wakeup() : void
Singleton- instance : Singleton+ getInstance() : Singleton- __construct() : void- __clone() : void- __wakeup() : void
class Singleton… public static function getInstance() { if (!isset(self::$instance)) { self::$instance = new self; } return self::$instance; }}
abstract class Singleton… public static function getInstance() { return isset(static::$instance) ? static::$instance : static::$instance = new static; } final private function __construct() { static::init(); } protected function init() {}}class A extends Singleton { protected static $instance;}
trait Singleton… protected static $instance; final public static function getInstance() { return isset(static::$instance) ? static::$instance : static::$instance = new static; } final private function __construct() { static::init(); } protected function init() {}}class A { use Singleton;}
„Nobody should need a mechanism to make it as easy as pie to clutter the code base
with singletons“
- Sebastian Bergmann
Testing Singletons
„In my stilted view of the universe anything that impedes testing is something to be avoided. There are those who don't agree with this view, but I'll
pit my defect rates against theirs any time.“
- Robert „Uncle Bob“ C. Martin
class TableDataGateway… public function __construct() { $this->db = Db::getInstance(); } public insert(array $data) { $this->db->sql('INSERT …', $data); }}
class TableDataGatewayTest extends PHPUnit… /**
* @dataProvider provideInsertData */
public function testInsertCallsDbWithData($data) { $gateway = new TableDataGateway; $gateway->insert($data); $this->assertSame( $data, $gateway->findById($data['id']) ); }}
throws LogicException: No key found for 'pdo' in Registry
class Db extends Singleton… protected function init() { $this->logger = Logger::getInstance(); $this->pdo = new PDO(Registry::get('pdo')); } public sql($query, $data) { $this->logger->log(printf( 'Executed %s with %s', $query, implode(',', $data) )); $sth = $this->pdo->prepare($query); return $sth->execute($data); } }
Singletons are pathological liars- Miško Hevery
class TableDataGatewayTest extends PHPUnit… /**
* @dataProvider provideInsertData */
public function testInsertCallsDbWithData($data) { Registry::set('pdo', array('…')); Registry::set('log', '/dev/null'); $gateway = new TableDataGateway; $gateway->insert($data); $this->assertSame( $data, $gateway->findById($data['id']) ); }}
Your tests are not isolated.You still need a real database.
No easy way to mock the Db Singleton.
„The real problem with Singletons is that they give you such a good excuse not to think carefully
about the appropriate visibility of an object.“
- Kent Beck
Hard to change code manifests itself in a cascade of
subsequent changes in dependent code
Fragile code breaks in many places when you
change just one place
Non-reusable code is code that you cannot reuse in
another project because it contains too much extra baggage
S.O.L.I.D.
Single Responsibility Principle
A class should have one, and only one, reason to change.
Open Closed Principle
You should be able to extend a classes behavior, without modifying it.
Liskov Substitution Principle
Derived classes must be substitutable for their base classes.
Interface Segregation Principle
Make fine grained interfaces that are client specific.
Dependency Inversion PrincipleDepend on abstractions, not on concretions.
Solid Singleton?
SRP Violation
Creation Logic + Business Logic= 2 reasons to change
Mitigated by using Abstract Singletons
But responsibilities are still strongly coupled through inheritance
OCP Violation
In PHP < 5.2 Singletons are closed for extending
Boilerplate code has to be removed when no longer needed
DIP Violation
Global access breaks encapsulation
Hides dependencies
Adds concrete dependencies into Clients
Signature lies
Singletons in the Wild
Database
FrontController
Logger
Cache
Registry
Request
ResponseConfig
Bootstrap
Application
Acl
FooController
BarController
ThisService
ThatService
Database
FrontController
Logger
Cache
Registry
Request
ResponseConfig
Bootstrap
Application
Acl
FooController
BarController
ThisService
ThatService
Recap:“Ensure a class has only one
instance, and provide a global access point to it.”
„So much of the Singleton pattern is about coaxing language protection mechanisms into
protecting this one aspect: singularity. I guess it is important, but it seems to have grown out of
proportion.“
- Ward Cunningham
You do not need to ensure singularity when you are going to instantiate the
object only once anyway.
You do not need to provide a Global Access Point when you can inject the
object.
class TableDataGateway… public function __construct() { $this->db = Db::getInstance(); }}class Db extends Singleton… protected function init() { $this->logger = Logger::getInstance(); $this->pdo = new PDO(Registry::get('pdo')); } }
class TableDataGateway… public function __construct(Db $db) { $this->db = $db; }}class Db… public function __construct(PDO $pdo, Log $logger) { $this->logger = $logger; $this->pdo = $pdo; } }
But then I have to push dependencies all the way through my object graph?!
Recap: Creational Pattern
„control when and how objects are created“
Use Builders and Factories to create Collaborator Graphs
class UserControllerBuilder { public function build($config) { $log = new Log($config['log']); $pdo = new PDO($config['…']); $db = new Db($pdo, $log); $tdg = new TableDataGateway($db); return new UserController($tdg); }}
class UserControllerBuilder { public function build($config) { $log = new Log($config['log']); $pdo = new PDO($config['…']); $db = new Db($pdo, $log); $tdg = new TableDataGateway($db); return new UserController($tdg); }}
???
class UserControllerBuilder { public function build($config) { $db = new LogDecorator( new PDO($config['…']); new Log($config['log']); ); $tdg = new TableDataGateway($db); return new UserController($tdg); }}
// index.phpinclude '/path/to/autoload.php';$config = new Config('/path/to/config.ini');$router = new Router( array( '/user/{id}' => function() use ($config) { $builder = new UserControllerBuilder; return $builder->build($config); } ));$router->route( new Request($_GET, $_POST, $_SERVER), new Response);
„Singletons aren't necessary when you can design or redesign
to avoid using them.“
- Joshua Kerievsky
„I'm in favor of dropping Singleton. Its use is almost always a design
smell“
- Erich Gamma