This document was uploaded by user and they confirmed that they have the permission to share
it. If you are author or own the copyright of this book, please report to us by using this DMCA
report form. Report DMCA
Overview
Download & View Advanced Oop And Design Patterns as PDF for free.
Every WHILE program can be simulated by a WHILE program with only one while loop.
OOP is syntactical sugar
Then Why OOP?
Readability
class BlogPosting { public function addComment(Comment $comment) }
class BlogPosting { public function addComment(Comment $comment) { $this->comments[] = $comment; } }
class BlogPosting { public function addComment(Comment $comment) { $this->comments[] = $comment; if ($comment->getAuthor()->isRegistered()) { $comment->setApproved(true); } } }
Encapsulation
Keeping Secrets
Interface and Implementation
$email->send(); $pdf->generate();
Maintainability
Relative Cost of a Bugfix
>150x
50x 20x 1x
5x
Req
Design
10x Code
DevT
AccT
Source: Barry Boehm: „EQUITY Keynote Address“, March 19th, 2007
Ops
Extensibility
class Customer { protected $discount = 0;
}
public function getDiscount() { return $this->discount; }
class PremiumCustomer extends Customer { protected $discount = 5; }
class PremiumCustomer extends Customer { protected $discount = 5; public function getDiscount() { if ($this->signupYear < 2003) { return 10; } if ($this->getTotalRevenue() > 100000) { return 10; } return parent::getDiscount(); } }
class PremiumCustomer extends Customer { protected $discount = 5; public function getDiscount() { if ($this->fulfilsYearCriterion() || $this->fulfilsRevenueCriterion()) { return 10; } return parent::getDiscount(); } ... }
class PremiumCustomer extends Customer { ... protected function fulfilsYearCriterion() { return $this->signupYear < 2003; } protected function fulfilsRevenueCriterion() { return $this->getTotalRevenue() > 100000; } ... }
class PremiumCustomer extends Customer { protected $specialDiscount= 10; public function getDiscount() { if ($this->fulfilsYearCriterion() || $this->fulfilsRevenueCriterion()) { return $this->specialDiscount; } return parent::getDiscount(); }
define('BASEPATH', '/some/absolute/path'); ... class Something { public function loadData() { $this->data = file_get_contents(BASEPATH . '/file.ext'); } } ... $something = new Something(); $something->loadData();
class Something { protected $basePath = BASEPATH;
}
public function loadData() { $this->data = file_get_contents($this->basePath . '/file.ext'); }
class Something { protected $basePath = BASEPATH; public function setBasePath($basePath) { $this->basePath = $basePath; } public function loadData() { $this->data = file_get_contents($this->basePath . '/file.ext'); } }
$something = new Something(); $something->setBasePath(BASEPATH); $something->loadData();
class Something { protected $basePath; public function setBasePath($basePath) { $this->basePath = $basePath; } public function loadData() { $this->data = file_get_contents($this->basePath . '/file.ext'); } }
class Something { protected $basePath; public function setBasePath($basePath) { $this->basePath = $basePath; } public function loadData() { if (is_null($this->basePath)) { throw new RuntimeException('No basepath set'); } $this->data = file_get_contents($this->basePath . '/file.ext'); } }
class Something { protected $basePath; public function __construct($basePath) { $this->basePath = $basePath; } public function loadData() { if (is_null($this->basePath)) { throw new RuntimeException('No basepath set'); } $this->data = file_get_contents($this->basePath . '/file.ext'); } }
Files are also a dependency
class Something { public function __construct($data) { $this->data = $data; } }
Dependencies on Global Variables
class Something { public function loadData() { global $dsn; … load data from database ... }
}
class Something { public function loadData($dsn) { … load data from database ... } }
class Something { protected $dsn; public function __construct($dsn) { $this->dsn = $dsn; } public function loadData() { … load data from database ... } }
class Something { protected $dbAdapter;
}
public function __construct(DatabaseAdapter $dbAdapter) { $this->dbAdapter = $dbAdapter; }
Dependency Injection
class Something { protected $dbAdapter; public function __construct(DatabaseAdapter $dbAdapter) { $this->dbAdapter = $dbAdapter; } public function loadData() { … ask somebody else to load data from database ... } }
Delegation
Let others do the work.
class DatabaseAdapter { protected $dsn;
}
public function __construct($dsn) { $this->dsn = $dsn; }
$dbAdapter = new DatabaseAdapter(); $something = new Something($dbAdapter); $something->doWork();
class DummyDatabaseAdapter extends DatabaseAdapter { } $dbAdapter = new DummyDatabaseAdapter(); $something = new Something($dbAdapter); $something->doWork();
class Something { protected $dbAdapter; public function __construct(DatabaseAdapter $dbAdapter = null) { if (is_null($dbAdapter)) { $dbAdapter = new DefaultDatabaseAdapter(); } }
}
$this->dbAdapter = $dbAdapter;
public function loadData() { … load data from database ... }
Dependencies on Functions
class Something { public function doWork() { ... global_function(...); ... } }
require_once 'global_function.php'; $something = new Something(); $something->doWork();
require_once 'mock_global_function.php'; $something = new Something(); $something->doWork();
Dependencies on Static Calls
class Something { public function loadData() { $dsn = Configuration::getDsn(); … load data from database ... }
}
class Something { protected $configurationClass = 'Configuration'; public function setConfigurationClass($classname) { $this->configurationClass = $classname } public function loadData() { $classname = $this->configurationClass; $db = $classname::getDsn(); … load data from database ... } }
?
class Something { protected $configurationClass = 'Configuration'; public function setConfigurationClass($classname) { $this->configurationClass = $classname } public function loadData() { $classname = $this->configurationClass; $db = $classname::getDsn(); … load data from database ... } }
Will $classname actually be a class?
class Something { protected $configurationClass = 'Configuration'; public function setConfigurationClass($classname) { $this->configurationClass = $classname } public function loadData() { $classname = $this->configurationClass; $db = $classname::getDsn(); … load data from database ... } }
Will the class have a getDsn() method?
Better let PHP enforce this.
Dependencies on Objects
class Something { public function doWork() { ... $this->log('…'); ... } }
class Something { protected function log($message) { file_put_contents(LOGFILE, $message, FILE_APPEND); } public function doWork() { ... $this->log('…'); ... } }
class Logger { public function log($message) { file_put_contents(LOGFILE, $message, FILE_APPEND); } }
class Something { protected $logger; public function __construct() { $this->logger = new Logger(); } ... }
class Something { protected $logger; public function __construct() { $this->logger = new Logger(); } public function doWork() { … $this->logger->log(...); ... } }
class Something { protected $logger; public function __construct(Logger $logger) { $this->logger = $logger; } public function doWork() { … $this->logger->log(...); ... } }
class Logger { public function log($message) { file_put_contents(LOGFILE, $message, FILE_APPEND); } }
class Logger { protected $file; public function __construct($file) { $this->file = $file; } public function log($message) { ... file_put_contents($this->file, $message, FILE_APPEND); ... } }
class DatabaseLogger extends Logger { protected $dsn; public function __construct($dsn) { $this->dsn = $dsn; } public function log($message) { … log $message to database $dsn ... } }
class DbGateway { protected $dsn; public function __construct($dsn) { $this->dsn = $dsn; } ... }
class DbGateway { protected $isConnected = false; ... protected function connect() { … connect to $dsn ... } public function query($query) { if (!$this->isConnected) { $this->connect(); } } }
...
… run $query ...
class DatabaseLogger extends Logger extends DbGateway { ... }
Multiple Inheritance
?
class Something { public function __construct(Logger $logger) { $this->logger = $logger; } public function doWork() { ... $this->logger->log(...); ... } }
Do we really need to extend Logger?
class FileLogger { protected $file; public function __construct($file) { $this->file = $file; } public function log($message) { ... file_put_contents($this->file, $message, FILE_APPEND); ... } }
class DatabaseLogger extends DbGateway { public function log($message) { ... … write $message to database using $this->query() … ... } }
class Something { public function __construct(Logger $logger) { $this->logger = $logger; } public function doWork() { ... $this->logger->log(...); ... } }
How do we make the type hint work?
Interfaces
interface Loggable { public function log($message); }
class FileLogger implements Loggable { protected $file; public function __construct($file) { $this->file = $file; } public function log($message) { ... file_put_contents($this->file, $message, FILE_APPEND); ... } }
class DatabaseLogger extends DbGateway implements Loggable { public function log($message) { ... … write $message to database using $this->query() … ... } }
class Something { public function __construct(Loggable $logger) { $this->logger = $logger; } protected function doWork() { ... $this->logger->log(...); ... } }
$logger = new FileLogger('/path/to/logfile'); $something = new Something($logger); $something->doWork();
$logger = new DatabaseLogger($dsn); $something = new Something($logger); $something->doWork();
$logger = new DummyLogger(); $something = new Something($logger); $something->doWork();
class DummyLogger implements Loggable { public function log($message) { } }
Interfaces make good type hints.
Abstract Classes and Methods
class Something { public function run() { // initialize … // perform calculations … // write log entry … // sort output ... } }
class Something { public function run() { $this->initialize(); $this->calculateStuff(); $this->doLogging(); $this->sortOutput(); } }
class ConcreteSomething extends Something { protected function initialize() { } protected function calculateStuff() { } protected function doLogging() { }
}
protected function sortOutput() { }
Abstract classes cannot be instantiated.
„Every pattern describes a problem which occurs over and over again in our environment, and then describes the core of the solution to that problem, in such a way that you can use this solution a million times over, without ever doing it the same way twice.“ -- Christopher Alexander
A design pattern is a general reusable solution to a commonly occurring problem.
A design pattern is not a finished design that can be transformed directly into code.
Architectural Patterns Design Patterns Idioms
Problem: display a progress bar
class Something { protected function performCalculation() { ... foreach ($items as $item) { … process the item … print '.'; } ... } }
Subject/Observer
The subject maintains a list of observers and notifies them on status changes.
interface SplSubject { public function attach(SplObserver $observer); public function detach(SplObserver $observer); public function notify(); }
interface SplObserver { public function update(SplSubject $subject); }
class Subject implements SplSubject { protected $observers; public function __construct() { $this->observers = new SplObjectStorage(); } ... }
class Subject implements SplSubject { … public function attach(SplObserver $observer) { $observers->attach($observer); } public function detach(SplObserver $observer) { $observers->detach($observer); } ... }
class Subject implements SplSubject { ... public function notify() { foreach ($this->observers as $observer) { $observer->update($this); } } }
class Something extends Subject implements SplSubject { public function doWork() { … foreach ($items as $item) { … $this->notify(); } ... } }
class ProgressBar implements SplObserver { public function update(SplSubject $subject) { print '. '; } }
class Something extends Subject implements SplSubject { public function doWork() { … foreach ($items as $item) { $this->currentItem = $item; … $this->notify(); } ... }
}
public function getCurrentItem() { return $this->currentItem() }
class ProgressBar implements SplObserver { public function update(SplSubject $subject) { $item = $subject->getCurrentItem(); … } }
class ProgressBar implements SplObserver { public function update(SplSubject $subject) { $item = $subject->getCurrentItem(); if ($item->isDir()) { print '[' . $item->getPathname() . '] '; } if ($item->isFile()) { print '.'; } }
}
$progressBar = new ProgressBar(); $something = new Something(); $something->attach($progressBar); $something->doWork();
Factory
Defines an interface for creating a family of objects. The concrete classes that are to be instantiated are not clearly defined.
Creates objects for you.
abstract class Plane { static public function getInstance($type) { return new $type; } }
class Boeing extends Plane { } class Airbus extends Plane { } class Boeing777 extends Boeing { } class AirbusA340 extends Airbus { }
abstract class Plane { static public function getInstance($type) { switch ($type) { case 'Boeing777': return new Boeing777(); … default: throw new RuntimeException('...'); } } }
abstract class Plane { static public function getInstance($type) { if (!in_array($type, array('Boeing777', 'AirbusA340', …))) { throw new RuntimeException('...'); } return new $type; }
}
abstract class Plane { static public function getInstance($type) { if (!class_exists($type)) { throw new RuntimeException('...'); } return new $type; } }
abstract class Plane { protected $classMap = array( '777' => 'Boeing777', ... ); static public function getInstance($type) { if (!isset($this->classMap[$type])) { throw new RuntimeException('...'); } $classname = $this->classMap[$type]; return new $classname; } }
Singleton
Limits number of instances of a class to one object.
Limits number of instances of a class to one object.
class Singleton { static protected $instance; static public function getInstance() { if (is_null(self::$instance)) { self::$instance = new Singleton(); } return self::$instance; } }
class Singleton { static protected $instance; private function __construct() {} private function __clone() {} static public function getInstance() { if (is_null(self::$instance)) { self::$instance = new Singleton(); } return self::$instance; } }
class Something { public function doWork() { $singleton = Singleton::getInstance(); $singleton->... ... } }
class Something { public function doWork($reference) { $reference->... ... } }
Registry
A well-known object used by others to find shared objects and services.
… $db = StaticRegistry::getDbAdapter(); ...
class StaticRegistry { static protected $dbAdapter; static public function getDbAdapter() { if (is_null(self::$dbAdapter)) { self::$dbAdapter = new DbAdapter(); } } }
class SingletonRegistry { static protected $instance; protected $dbAdapter; static public function getInstance() { … } public function getDbAdapter() { if (is_null($this->dbAdapter)) { $this->dbAdapter = new DbAdapter(); } } }
return $this->dbAdapter;
class Registry { … protected $dbAdapterClass = 'DbAdapter'; protected $dbAdapter; public function setDbAdapterClass($class) { $this->dbAdapterClass = $class; } public function getDbAdapter() { if (is_null($this->dbAdapter)) { $classname = $this->dbAdapterClass; $this->dbAdapter = new $classname; } return $this->dbAdapter; }
}
MVC
Classical
MVC
Model
Represents domain-specific data
View
Renders model data
Controller
Handles events and modifies model
Presentation
V
Logic
C
Data Access
M
Model != Data Access
Model != Data Access
Controller
View
Model
Controller observes View Controller
View
Model
Controller fetches data from View Controller
View
Model
Controller modifies Model Controller
View
Model
View observes Model Controller
View
Model
View fetches data from Model Controller
View
Model
MVC
on the Web
V
Client HTTP Request
HTTP Response Server
C
M
View is remote
Controller
View
Model
Controller can't observe View Controller
View
Model
View can't observe Model Controller
View
Model
View doesn't talk to Controller Front Controller
Controller
View
Model
Front Controller
Controller
HTTP Request
View
Model
Front Controller
execute()
Controller
HTTP Request
View
Model
Front Controller
execute()
Controller
HTTP Request
View
getData()
Model
Front Controller HTTP Request
View
execute()
Controller
() a t a tD e s
getData()
Model
Controller selects View
View generates full HTML page
Workflows by Controller chaining
Lean Controller, Fat Model
No data access in Controller or Model
No presentation in Controller or Model
Separation of Concerns
Golden Rules
„Hang the rules. They're more like guidelines anyway.“
--Elizabeth Swann, Pirates of the Caribbean
Make dependencies explicit.
„Life can only be understood backwards, backwards, but it must be lived forwards.“ forwards.“ -- Soren Kierkegaard
Solve your problem.
Do one thing at a time.
Let others do the work.
Do not care about others.
Keep it simple.
„Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it.“ -- Brian W. Kernighan
Do not comment.
Do not * comment. *
Some restrictions apply.
”The most important single aspect of software development ... … is to be clear about what you are trying to build.” -- Bjarne Stroustrup
Name the thing.
„There's no sense being exact about something if you don't even know what you're talking about.“ -- John von Neumann
It has to fit on one page.
”Measuring programming progress by lines of code is like measuring aircraft building progress by weight.” weight.” -- Bill Gates
Accept Change.
”I may not have gone where I intended to go, but I think I have ended up where I needed to be.“ -- Douglas Adams