Design patterns are general solutions to common problems. They are concluded by experienced programmers after they have encountered the same problem again and again during their careers. However patterns are difficult to understand and implement if you are a new, or even an intermediate programmer due to lack of experience. In this tutorial, I will go through 3 common design patterns and explain them in practical examples. Hopefully after this section, you will be able to implement some of them in your code.
Singleton Pattern
As far as I know, Singleton is the most common pattern. You can find it almost in every PHP framework (e.g., CakePHP and Zend). Particularly it is used to build database connection. The idea of Singleton is to restrict only one instance of a class to exist across the entire application.```
"The Singleton Design Pattern is used to restrict the number of times a specific object can be created to a single time by providing access to a share instance of itself.--Aaron Saray "
Code below shows how to implement Singleton pattern in PHP, please note there are different ways (more less the same) of implementing this pattern in PHP, this is one of them which I personally prefer:
class DbConnection
{
//1
protected static $_instance = NULL;
//2
public static function getInstance()
{
if (!self::$_instance instanceof self) {
self::$_instance = new self;
}
return self::$_instance;
}
//3
protected function __construct()
{
//build connection
}
//4
private function __clone(){
//do nothing
}
}
The
$_instance
will store one and the only one instance of this class.The
getInstance()
function is the actual function providing public access to gain the instance of this class. Make it as a static function, so we can call it using Class:: operator. It first checks whether $_instance is an instance of itself. It is not when it is called first time, so it will create an instance of itself and assign it to $_instance. Finally, it returns the $_instance variable, which is actually an instance of DbConnection. This way, only one instance of this class will be created during the code stream.Keep the constructor of the class protected, so it cannot be instantiated by public. Please note this is not a complete function, you should build database connection in this function.
Simply create a private __clone() function to overwrite the default one. This way we can block the magic method.
Instead of building database connection each time when you need, which may take a lot of resources, now you can do:
$dbCont1 = DbConnection::getInstance();
...
...
$dbCont2 = DbConnection::getInstance();
$dbCont1 and $dbCont2 now are actually the same instance, which means the same connection.
Factory Pattern
If you are using large chunk of if/else or switch statement in your code to create instances of similar classes. You may have seen the problem, which is every time when you introduce a new class, you will have to add more conditions to the statement. And when you make a simple change to one class's constructor, you will need to go through each condition to make sure everything is changed accordingly. Especially in a team development environment, it may cause extra development and testing efforts. Factory pattern provides a simple interface for external codes to acquire an instance of a class. It makes code much easier to maintain and test.
In the scenario below, $apiObject is assigned different objects based on $type variable.
$apiObject = null;
switch($type){
case 'twitter':
$apiObject = new TwitterApi();
break;
case 'facebook':
$apiObject = new FacebookApi();
break;
case 'google':
$apiObject = new GoogleApi();
break;
default:
break;
}
When instantiation of a class requires logic to determine, we can use Factory pattern. Thanks to PHP's ability to create a new instance of a class using a variable, we can implement a Factory class as below:
class SocialFactory
{
//1
public static function create($type)
{
//2
$class = ucfirst($type).'Api';
//3
return new $class;
}
}
- The function used to create class instance is called Factory method. It is function create ($type) in this example, and we make it static, so we can call it using Class:: operator.
- Format the class name. In this example, make the first character of $type capitalized and append 'Api'to it.
- Finally we return the object by calling new $class. This is the feature mentioned above: dynamically creating object using a string variable.
Instead of long lines of switch statement, now you can call SocialFactory to create the object for you:
$apiObject = SocialApi::create($type) ;
Imagine your client asks you to add "Flickr" to the application. You will first implement "FlickrApi" class. In the old code you will need to add one more condition to the switch statement. With Factory pattern, you can actually keep the main code untouched. This becomes extremely useful in a team development environment. Individual developer can focus on his own tasks instead of worrying about the changes in the main code.
Observer Pattern
Observer pattern is very popular in open source projects. Since most of open source projects have a core base which is maintained by its core developers. And it still gives outside developers' freedom to add any additional features to it without touching the core base. This is accomplished by plugin system which is normally created using Observer pattern. The main class which will notify its listener class (Observer) upon its changes of state is called Observerable class. A change of state can be from a simple property update to business logic. Observerable class does not care about what its Observers do; it does not even know their existence. This way, two objects are loosely coupled. It became very easy to add additional functions without alerting main code.
Below is an example of an Observerable interface
class Observerable
{
//1
protected $_observers = array();
//2
public function attachObserver($type,$observer)
{
$this->_observers[$type][]=$observer;
}
//3
public function notifyObserver($type){
//4
if(isset($this->_observers[$type])){
//5
foreach($this->_observers[$type] as $observer){
//6
if(method_exists($observer,'callback')){
$observer->callback($this);
}
}
}
}
}
Firstly, we declare a protected array property to store Observers for this class.
This function will store Observers to $_observers array according to its type using a multiple dimensional array.We do this because there are different types of changes of state for Observers to listen to.
This function will notify Observers upon observable classes' changes of state.
In function notifyObserver($type), we first check if the array is set, which means if there are Observers added.
If the answer is yes, we loop through every single Observer.
Lastly, we check if there is a callback function defined in observable, if so, we call it and pass the current object to the Observer.
Let us see how we can make use of the Observerable interface defined above. Let us assume that we have a Post class, it will send an email to admin each time there is a comment added to it. Normally what we do is to implement a sendEmail function in the main code and call it after comment is added:
class Post
{
function addComment($commentString)
{
//validate comment
/***code to validate***/
//add comment to database
/***code to add comment to***/
//send email
$this->sendEmail();
}
function sendEmail()
{
/***code to send email***/
}
}}
Now if we use Observer pattern, we first need to alert Post to extend Observable. And then instead of sendEmail(), we use $this->notifyObserver('AddComment'). So Observers will be notified each time a comment is added. Secondly, we need to implement an Observer class, which is a Mailer in this case with a callBack function, and add it to Post class in the main code stream.
class Post extends Observerable
{
public function addComment($commentString)
{
//valida comment
/***code to validate***/
//add comment to database
/***code to add comment to***/
$this->notifyObserver('AddComment');
}
}
class Mailer
{
function callBack()
{
/***code to send email***/
}
}
/***** code before *****/
$mailer = new Mailer();
$post->attachObserver('AddComment',$mailer);
/***** code after *****/
With Observer pattern, we have separated Post with Mail class, and if you need to introduce an new logic upon the comment is added, all you have to do is to implement an Observer class and attach it to Post object. In a large project environment, Observers are normally auto loaded using some kind of mechanism; you will not have to alert the code stream. For example, placing Observers in a specific folder.
No comments:
Post a Comment