Observer Tasarım Kalıbı ve Olay Güdümlü Programlama

Share Button

Üzerinde çalıştığım bir projeye kısa bir çay molası vermişken sizlerle çok sevdiğim ve şu an üzerinde çalıştığım projede de kullandığım gözlemci (observer) tasarım kalıbı ve olay güdümlü programlama konularndan kısaca bahsetmek istiyorum.

87628254
Olay güdümlü programlama, iç veya dış etkenlerle meydana gelen olaylar doğrultusunda program akışının değiştirilmesi gerektiği durumlarda kullanılan bir tekniktir. Bir e-ticaret sitesinde ziyaretcinin herhangibir nedenle alışverişi tamamlayamaması durumunda belirli bir kullanıcı gurubunun haberdar edilmesi, herhangibir servisle iletişim sorunu olması durumunda aksiyon alınması gibi durumlar, olay güdümlü programlama gerektirir.

Observer
Observer tasarım kalıbı ise birbirleriyle bire-çok ilişki içindeki nesnelerden herhangibirinde değişiklik olması durumunda bir merkez üzerinden diğer nesnelerin haberdar edilmesi gerektiği durumlarda kullanılır ve olay güdümlü programlama için biçilmiş kaftandır :)

Dilerseniz gelin hemen birlikte gerçek hayattan bir örnek inceleylim.

Bir elektronik ticaret şirketinde çalışıyoruz. Saat başı zamanlanmış bir görevle gelen siparişleri lojistik şirketine iletiyoruz. Ancak siparişlerin herhangibir nedenle iletilememesi durumunda 1 dk bekleyip tekrar deneme gerçekleştirmek istiyoruz.

Kod örneğine geçmeden önce gözlemci tasarım kalıbı ve olay güdümlü programlama arasındaki ilişki konusunda kısa bir bilgi vermekte yarar var. Observer tasarım desenini kullanarak yazılımımızın akışıını olaylarla değiştirebilmek için olayın gerçekleştiği yeri merkez, olayı dinleyen bölümü observer olarak düşünmeliyiz.

Aşağıdaki gibi bir lojistik arayüzümüz var. Tüm lojistik implementasyonlarımız bu arayüzü implement eder.

interface ILogistic
{
	/**
	 * @param string $articleCode
	 * @param string $refNumber
	 * @return bool
	 */
	public function send($articleCode, $refNumber);
	
	/**
	 * @param string $refNum
	 * @return string
	 */
	public function getStatus($refNum);
}

Basit ve anlaşılır olması açısından herbir gözlemcinin tek bir olay içerdiğini varsayıyoruz. Aşağıda gözlemci sınıflarımızın implement edeceği arayüz kodu yeralıyor.

interface IObserver
{
	/**
	 * @param Event $event
	 */
	public function invoke(Event $event);
}

Olayın gerçekleşeceği bölümün implement edeceği IObservable arayüzünü kodlayalım.

interface IObservable
{
	/**
	 * @param IObserver $observer
	 * @param string $eventName
	 */
	public function attach(IObserver $observer, $eventName);
	
	/**
	 * @param Event $event
	 */
	public function fireEvent(Event $event);
}

Olay gerçekleştiğinde olayla ilgili bilgileri gözlemciye (observer) taşıyacak olay sınıfımızı kodlayalım.

class Event
{
	/**
	 * @var string
	 */
	private $_event_type;
	
	/**
	 * @var IObservable
	 */
	private $_source;
	
	/**
	 * @var string
	 */
	private $_last_called_method;
	
	/**
	 * @var array
	 */
	private $_arguments;
	
	/**
	 * @param string $eventName
	 * @param IObservable $source
	 * @param string $lastCalledMethod
	 * @param array $arguments
	 */
	public function __construct($eventName,  IObservable $source, $lastCalledMethod, array $arguments)
	{
		$this->_event_name = $eventName;
		$this->_source = $source;
		$this->_last_called_method = $lastCalledMethod;
		$this->_arguments = $arguments;
	}
	
	/**
	 * @return string
	 */
	public function getEventName()
	{
		return $this->_event_type;
	}
	
	/**
	 * @return IObservable
	 */
	public function getSource()
	{
		return $this->_source;
	}
	
	/**
	 * @return string
	 */
	public function getLastCalledMethod()
	{
		return $this->_last_called_method;
	}
	
	/**
	 * @return array
	 */
	public function getArguments()
	{
		return $this->_arguments;
	}
}

Lojistik implementasyonlarımızın devralacağı soyut sınıfımızı kodlayalım.

abstract class LogisticAbstract
{	
	/**
	 * @param string $articleCode
	 * @param string $refNum
	 * @return array
	 */
	abstract protected function _getSendRequest($articleCode, $refNum);
	
	/**
	 * @param string $refNum
	 * @return array
	 */
	abstract protected function _getStatusRequest($refNum);
	
	/**
	 * @param string $apiResponse
	 * @return bool
	 */
	abstract protected function _parseSendRequest($apiResponse);
	
	/**
	 * @param string $apiResponse
	 * @return string
	 */
	abstract protected function _parseStatusRequest($apiResponse);
	
	/**
	 * @param string $url
	 * @param array $data
	 * @throws Exception
	 * @return string
	 */
	protected function _sendRequest($url, array $data)
	{
		$postData = http_build_query($data);
		$ch = curl_init();
		$options = array(CURLOPT_URL => $url,
					    CURLOPT_RETURNTRANSFER => true,
					    CURLOPT_POST 	   => true,
					    CURLOPT_POSTFIELDS 	   => $postData);
		curl_setopt_array($ch, $options);
		$response = curl_exec($ch);
	    if($error = curl_errno($ch)) {
	    	throw new Exception('Error occurred while sending data to remote server. Detail:' . $error, '');
	    }
	    return $response;
	}
}

Lojistik şirketimizin adı örnek lojistik olsun.

class OrnekLojistik extends LogisticAbstract implements ILogistic, IObservable
{
	/**
	 * @var array
	 */
	private $_observers = array();
	
	/**
	 * @param string $articleCode
	 * @param string $refNumber
	 * @return array
	 */
	protected function _getSendRequest($articleCode, $refNumber)
	{
		$xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>
				<OrnekLogisticRequest>
				  <transaction>
				  <type>send</type>
				  <articleCode>{$articleCode}</articleCode>
				  <refNum>{$refNumber}</refNum>
				  </transaction>
				</OrnekLogisticRequest>";
		return array('data' => $xml);
	}
	
	/**
	 * @param string $refNum
	 * @return array
	 */
	protected function _getStatusRequest($refNum)
	{
		//...
	}
	
	/**
	 * @param string $apiResponse
	 */
	protected function _parseSendRequest($apiResponse)
	{
		try {
			$xml = new SimpleXMLElement($xml);
			return $xml->Response->Status == 'OK';		
		} catch(Exception $e) {
			throw new Exception('Unexpected Response. Response message:' . $apiResponse);
		}
	}
	
	/**
	 * @param string $apiResponse
	*/
	protected function _parseStatusRequest($apiResponse)
	{
		//...
	}
	
	/**
	 * @param string $articleCode
	 * @param string $refNumber
	 */
	public function send($articleCode, $refNumber)
	{
		try {
			$request = $this->_getSendRequest($articleCode, $refNumber);
			$apiResponse = $this->_sendRequest('http://ws.orneklojistik.com/OrnekWS.php', $request);
			$response = $this->_parseSendRequest($apiResponse);
			if(! $response ) {
				$this->fireEvent(new Event('shipping_failed', $this, __METHOD__, func_get_args() ));
			}
			return $response;
		} catch(Exception $e) {
			$this->fireEvent(new Event('shipping_failed', $this, __METHOD__, func_get_args() ));
                        return false;
		}
	}
	
	/**
	 * @param string $refNum
	*/
	public function getStatus($refNum)
	{
		//...		
	}
	
	/**
	 * (non-PHPdoc)
	 * @see IObservable::attach()
	 */
	public function attach(IObserver $observer, $eventName)
	{
		if( ! isset( $this->_observers[$eventName] ) ) {
			$this->_observers[$eventName] = array();
		}
		$this->_observers[$eventName][] = $observer;
	}
	
	/**
	 * (non-PHPdoc)
	 * @see IObservable::fireEvent()
	 */
	public function fireEvent(Event $event)
	{
		if( isset( $this->_observers[$event->getEventName() ] ) ) {
			/* @var $event IObserver  */
			foreach ($this->_observers[$event->getEventName()] as $observer) {
				$observer->invoke($event);
			}
		}
	}
}

Lojistik şirketine gönderim sırasında sorun yaşamamız durumunda çalışacak gözlemci sınıfını kodlayalım.

class OrnekLojistik_Observer_OnFail implements IObserver
{
	/**
	 * @var bool
	 */
	private $_tried = false;

	/**
	 * (non-PHPdoc)
	 * @see IObserver::invoke()
	 */
	public function invoke(Event $event)
	{
		if( ! $this->_tried ) {
			sleep(60);
			call_user_method($event->getLastCalledMethod(), $event->getSource(), $event->getArguments());
		}	
	}
}

Geldik zamanlanmış görevimize. Buyrun size zamanlanmış görev…

class SendLogisticCommand implements ICommand
{
	/**
	 * @param array $args
	 */
	public function run($args)
	{
		$products = array('ABC123', 'DEF234', 'GHI123');
		
		$logistic = new OrnekLojistik();
		$observer = new OrnekLojistik_Observer_OnFail();
		$logistic->attach($observer, 'shipping_failed');
		
		foreach($products as $product) {
			$logistic->send($product, time() );
		}
	}
}

Biraz uzun oldu sanırım :) Hızlıca hazırladığım için ufak tefek hiyerarşi sıkıntıları olabilir ancak genel anlamda sizlere fikir verebileceğini ümit ediyorum.

Share Button

About İbrahim Gündüz

1983 yılında İstanbul’da doğdu. İlkokul yıllarında cobol ve basic le olan tanışması, yazılıma olan ilgisini arttırdı 2005 yılında. Uludağ Üniversitesi Teknik Bilimler Meslek Yüksek Okulu Elektronik bölümünden mezun olan Gündüz, çeşitli alanlarda faaliyet gösteren kurumlarda yazılım geliştirici olarak görev almıştır. Mesleki ilgi alanları, ölçeklenebilir sistemler, uygulama entegrasyonları ve ödeme sistemleridir. Halen Markafoni back end geliştirici olarak çalışmaktadır.

Bir Cevap Yazın

E-posta hesabınız yayımlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir