April 2018, Budapest
in object oriented
Most software today is very much like an Egyptian pyramid with millions of bricks piled on top of each other, with no structural integrity, but just done by brute force and thousands of slaves.
Alan Kay
The book that started it all (1994).
Community refers to authors as the "Gang of Four".
Some figures and text in these slides come from this book.
final class Singleton
{
private static $inst = null;
public static function getInstance()
{
if (self::$inst === null) {
self::$inst = new Singleton();
}
return self::$inst;
}
private function __construct()
{ }
}
$obj = Singleton::getInstance();
Ensure a class only has one instance, and provide a global point of access to it.
Front Controller of MVC frameworks often implement the Singleton pattern.
// Entry point index.php of a ZF application
require_once '../bootstrap.php';
Zend_Controller_Front::getInstance()
->setControllerDirectory('modules/default/controllers')
->registerPlugin(new Content_Plugin())
// ...
->dispatch();
Front Controller of MVC frameworks often implement the Singleton pattern.
Overusing Singleton is an anti-pattern.
class DatabaseProvider
{
public static function create($type)
{ /* ... */ }
}
class MySQLProvider extends DatabaseProvider
{ /* ... */ }
class MSSQLProvider extends DatabaseProvider
{ /* ... */ }
$provider = DatabaseProvider::create('mysql');
Define an interface for creating an object, but let subclasses decide which class to instantiate. Factory lets a class defer instantiation to subclasses.
use Symfony\Component\Form\Forms;
$formFactory = Forms::createFormFactory();
use Symfony\Component\HttpFoundation\Request;
$request = new Request(/* ... */);
$request = Request::create(/* ... */);
$request = Request::createFromGlobals();
Often used in MVC frameworks, e.g. in Symfony:
Separate the construction of a complex object from its representation so that the same construction process can create different representations.
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\Extension\Core\Type\DateType;
class TaskController extends Controller
{
public function createAction(Request $request)
{
$form = $this->createFormBuilder()
->add('task', TextType::class)
->add('dueDate', DateType::class)
->getForm();
return $this->render('task/create.html.twig', array(
'form' => $form->createView(),
));
}
}
$qb = $repository->createQueryBuilder('p');
$query = $qb
->add('where', $qb->expr()->like('p.name', ':name'))
->add('orderBy', $qb->expr()->orderBy('p.price', 'ASC'))
->setParameter('name', 'computer')
->getQuery();
Often used in query building (e.g. Doctrine):
Often used in form building (e.g. Symfony):
Define a family of algorithms, encapsulate each one, and make them interchangeable. Strategy lets the algorithm vary independently from clients that use it.
interface OutputFormatter
{
public function process($data);
}
class JsonFormatter implements OutputFormatter
{
public function process($data)
{ /* ...*/ }
}
class XmlFormatter implements OutputFormatter
{
public function process($data)
{ /* ...*/ }
}
class View
{
public $formatter;
public __construct(OutputFormatter $formatter)
{
$this->formatter = formatter;
}
public function render($data)
{
return $this->formatter->process($data);
}
}
$view = new View(new JsonFormatter());
$data = [ 'name' => 'Máté' ];
$view->render($data);
Example usage: output rendering
Also used in dependency injection containers.
Define a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically.
interface IObservable
{
function addObserver(IObserver $observer);
}
interface IObserver
{
function onChanged(IObservable $sender, $args);
}
class User
{ /* ... */ }
class UserManager implements IObservable
{
private $_users = array();
private $_observers = array();
public function addUser(User $user)
{
$this->_users[] = $user;
foreach($this->_observers as $obs)
$obs->onChanged($this, $user);
}
public function addObserver( $observer )
{
$this->_observers[] = $observer;
}
}
$manager = new UserManager();
$manager->addObserver( new UserLogger());
$manager->addUser(new User("Gipsz Jakab"));
class UserLogger implements IObserver
{
public function onChanged($sender, $args)
{
echo "${args->name} user was added" . PHP_EOL;
}
}
Define an object that encapsulates how a set of objects interact. Mediator promotes loose coupling by keeping objects from referring to each other explicitly.
The EventDispatcher system in Symfony also uses the Mediator design pattern.
use Symfony\Component\EventDispatcher\EventDispatcher;
use Symfony\Component\EventDispatcher\Event;
$dispatcher = new EventDispatcher();
// add listeners
$dispatcher->addListener('blog.post.saved', function (BlogPostEvent $event) {
echo 'Updating RSS feed' . PHP_EOL;
});
$dispatcher->addListener('blog.post.saved', function (BlogPostEvent $event) {
echo 'Sending emails' . PHP_EOL;
});
$blogPost = new BlogPost(...);
$manager = new Manager(...);
$manager->save($blogPost);
// dispatch the event
$event = new BlogPostEvent($blogPost);
$dispatcher->dispatch('blog.post.saved', $event);
Producer
(Observable)
Consumer
(Observer)
Consumer
(Observer)
Consumer
(Observer)
Producer
Consumer
Consumer
Consumer
Mediator
Encapsulate a request as an object, thereby parameterizing clients with different requests, queue or log requests.
interface ICommand
{
function execute();
}
class LogCommand implements ICommand
{
public function execute()
{
// Write logs
}
}
class MailCommand implements ICommand
{
public function execute()
{
// Send email
}
}
class CommandChain implements ICommand
{
private $_commands = array();
public function add(ICommand $cmd)
{
$this->_commands[] = $cmd;
}
public function execute( $name, $args )
{
foreach($this->_commands as $cmd)
{
$cmd->execute();
}
}
}
$cmd = new CommandChain();
$cmd->add(new LogCommand());
$cmd->add(new MailCommand());
$cmd->execute();
May embed commands on multiple levels.
Access the elements of an aggregate object sequentially without exposing its underlying representation.
CreateIterator is also an example to the Factory design pattern.
Often used in ORM frameworks, e.g. with Doctrine:
$context = $this->getDoctrine()->getManager();
$query = $context->createQuery('SELECT ...');
$i = 0;
// implements PHP's native Iterator interface
$iterable = $query->iterate();
foreach ($iterable as $row)
{
$obj = $row[0];
// work on object ...
if ($i % 100 == 0)
{
// Executes all updates
$context->flush();
// Detaches all objects from Doctrine
$context->clear();
}
++$i;
}
$context->flush();
$context = $this->getDoctrine()->getManager();
$query = $context->createQuery('SELECT ...');
$i = 0;
$iterable = $query->iterate();
while($iterable->valid())
{
$row = $iterable->current();
$obj = $row[0];
// work on object ...
if ($i % 100 == 0)
{
// Executes all updates
$context->flush();
// Detaches all objects from Doctrine
$context->clear();
}
$iterable->next();
++$i;
}
$context->flush();
Provide a placeholder for another object to control access to it.
Image
ImageProxy
Controller
In memory
In memory
On disk
Lazy loading (like in ORM frameworks) also utilizes proxy pattern:
$context = $this->getDoctrine()->getManager();
$products = $context->getRepository(Product::class);
$qb = $products->createQueryBuilder('p');
$query = $qb
->add('where',
$qb->expr()->like('p.name', '?1'))
->add('orderBy',
$qb->expr()->orderBy('p.price', 'ASC'))
->setParameter(1, 'computer')
->getQuery();
// $query is a proxy for $result
$result = $query->getResult();
foreach($result as $product)
{
// ...
}
MyDbContext context = new MyDbContext();
DbSet<Product> products = context.Products;
Product searched = Products.Find(42);
searched.Price = 500;
context.SaveChanges();
IQueryable<Product> query = products
.Where(p => p.Name.Contains('computer'))
.OrderBy(p => p.Price);
IList<Product> result = query.ToList();
foreach(Product product in result)
{
// ...
}
foreach(Product product in query)
{
// ...
}
PHP with Doctrine
C# with Entity Framework & LINQ
Attach additional responsibilities to an object dynamically. Decorators provide a flexible alternative to subclassing for extending functionality.
Often used in front-end visualization frameworks.
$view =
new BorderDecorator(
new ScrollBarDecorator(
new TextView()
)
);
$view->render();
Typical use-case is constructing form fields:
Client
<<interface>
ServiceInterface
Service
Injector
class FilePersistence
{
public function __construct($filename)
{ /* ... */ }
public function load() { /* ... */ }
public function save() { /* ... */ }
}
class MyController extends Controller
{
private $_persistence;
public function __construct()
{
$this->_persistence =
new FilePersistence("save.dat");
}
// actions ...
}
With this architecture the model (persistence) and the controller layers are tightly coupled.
What happens if we add another implementation of the model layer?
class FilePersistence
{
public function __construct($filename)
{ /* ... */ }
public function load() { /* ... */ }
public function save() { /* ... */ }
}
class MyController extends Controller
{
private $_persistence;
public function __construct()
{
$this->_persistence =
new FilePersistence("save.dat");
}
// actions ...
}
class DatabasePersistence
{
public function __construct(
$host, $user, $passwd, $dbname)
{ /* ... */ }
public function load() { /* ... */ }
public function save() { /* ... */ }
}
We shall extract an interface and use subtype polymorphism.
interface PersistenceInterface
{
public function load();
public function save();
}
class FilePersistence
implements PersistenceInterface
{
public function __construct($filename)
{ /* ... */ }
public function load() { /* ... */ }
public function save() { /* ... */ }
}
class DatabasePersistence
implements PersistenceInterface
{
public function __construct(
$host, $user, $passwd, $dbname)
{ /* ... */ }
public function load() { /* ... */ }
public function save() { /* ... */ }
}
How can we make the chosen implementation dynamically selectable?
class MyController extends Controller
{
private $_persistence;
public function __construct()
{
// How can we select dynamically, in runtime?
$this->_persistence = new FilePersistence(
"save.dat");
$this->_persistence = new DatabasePersistence(
"localhost", "loginet", "loginet", "loginet");
}
// actions ...
}
Make the controller and the model layer loosely coupled!
class MyController extends Controller
{
private $_persistence;
public function __construct(PersistenceInterface $persistence)
{
$this->_persistence = $persistence
}
// actions ...
}
Injection can be constructor-based or setter- based (method)
UserController
<<Symfony>>
UserManager
<<Custom Service>>
EntityManager
<<Doctrine>>
Direction of aggregation
Direction of dependency injection
The flow of control is inversed, therefore we name it
Inversion of Control (IoC)
Symfony has a bundled IoC container named: Service Container
use Symfony\Component\HttpFoundation\Request;
use Psr\Log\LoggerInterface;
/**
* @Route("/test")
*/
public function testAction(Request $request, LoggerInterface $logger)
{
$logger->info('Wow, I just used the Symfony Service Container!');
}
use Psr\Log\LoggerInterface;
/**
* @Route("/test")
*/
public function testAction()
{
$logger = $this->get(LoggerInterface::class);
$logger->info('Wow, I just used the Symfony Service Container!');
}
Request an object by injection:
Request an object manually:
class UserService
{
private $_repository;
public function __construct(EntityManagerInterface $entityManager)
{
// This only works in a Controller subclass:
//$this->getDoctrine()->getRepository(User::class);
// So we fetch the entity mananger via dependency injection!
$this->_repository = $entityManager->getRepository(User::class);
}
// methods ...
}
class UserController extends Controller
{
private $_service;
public function __construct(UserService $service)
{
$_service = $service;
}
// actions ...
}
class UserService
{
private $_repository;
private $_superAdmin;
public function __construct(EntityManagerInterface $entityManager, $superAdmin)
{
$this->_repository = $entityManager->getRepository(User::class);
$this->_superAdmin = $superAdmin;
}
// methods ...
}
# config/services.yaml
services:
# ...
# explicitly configure the service
AppBundle\Service\UserService:
arguments:
$superAdmin: 'admin@loginet.hu'
How to inject arguments with non-registered types?
Configure service registration:
class SomeController extends Controller
{
private $_persistence;
public function __construct(PersistenceInterface $persistence)
{
$this->_persistence = $persistence;
}
// actions ...
}
# config/services.yaml
services:
# ...
# explicitly configure the service
AppBundle\Service\PersistenceInterface: AppBundle\Service\DatabasePersistence
How to inject interface arguments with
multiple implementations?
Configure service registration:
class SomeController extends Controller
{
private $_persistence;
public function __construct(
DatabasePersistence $persistence)
{
$this->_persistence = $persistence;
}
// actions ...
}
# config/services.yaml
services:
# ...
# explicitly configure the service
AppBundle\Service\DatabasePersistence:
factory: [AppBundle\Service\PersistenceFactory, createDatabasePersistence]
How to inject arguments whom creation follows the
factory design pattern?
Configure service registration:
class PersistenceFactory
{
public function createFilePersistence()
{ /* ... */ }
public function createDatabasePersistence()
{ /* ... */ }
}
class UserService
{
private $_persistence;
private $_mailer;
public function __construct(DatabasePersistence $persistence)
{
$this->_persistence = $persistence;
}
public function setMailer(MailerInterface $mailer)
{
$this->mailer = $mailer;
}
}
# config/services.yaml
services:
# ...
# explicitly configure the service
AppBundle\Service\UserService:
calls:
- [setMailer, ['@mailer']] // using alias OR
- [setMailer, [\Swift_Mailer]] // using concrete class
How to inject optional arguments?
# config/services.yaml
services:
# ...
# explicitly configure the service
AppBundle\Service\UserService:
shared: false
What about object lifetime in an IoC container?
In Symfony the default is shared, non-shared is configurable, singleton is not supported.