Bu yazı Design Patterns/Tasarım Desenleri nedir? başlıklı yazı dizisinin bir parçasıdır.
Bu içerik ağırlıklı olarak refactoring.guru sitesindeki içeriğin tercümesi ve derlenmesinden oluşturulmuştur.
Tüm tasarım desenleri ya da diğer adıyla tasarım kalıplarına yönelik ayrıntılı içeriklere yazının sonundaki bağlantılardan ulaşabilirsiniz.
Proxy tasarım deseninin amacı
Proxy başka bir nesne için bir yedek veya yer tutucu sağlamanıza olanak tanıyan yapısal bir tasarım desenidir. Bir proxy, orijinal nesneye erişimi kontrol ederek, istek orijinal nesneye ulaşmadan önce veya sonra bir şeyler gerçekleştirmenize izin verir.
Sorun
Neden bir nesneye erişimi kontrol etmek isteyesiniz? İşte bir örnek: Çok büyük miktarda sistem kaynağı tüketen devasa bir nesneniz var. Bu nesneye her zaman değil sadece arada sırada ihtiyaç duyuyorsunuz.
Tembel başlatma (Lazy initialization) uygulayabilir, yani bu nesneyi yalnızca gerçekten ihtiyaç duyulduğunda oluşturabilirsiniz. Tüm istemcilerin nesnenin ertelenmiş başlatma kodlarının yürütülmesini beklemesi gerekir. Ne yazık ki, bu muhtemelen çok fazla kod tekrarına neden olacaktır.
İdeal bir dünyada, bu kodu doğrudan nesnemizin sınıfına koymak isteriz, ancak bu her zaman mümkün değildir. Örneğin, bu sınıf kapalı bir 3. taraf yazılımın bir parçası olabilir.
Çözüm
Proxy modeli, orijinal servis nesnesiyle aynı arabirime sahip yeni bir ara sınıf, yani proxy sınıfı oluşturmanızı önerir. Ardından, proxy nesnesini orijinal nesneyei kullanan tüm istemcilerine iletmesi için uygulamanızı güncellersiniz. Bir istemciden bir istek aldığında, proxy gerçek bir hizmet nesnesi oluşturur ve tüm işi ona devreder.
İyi ama bunun faydası nedir? Sınıfın ana işlevinden önce ya da sonra başka bir şey çalıştırmak istediğinizde proxy sınıfı size bu imkanı sunar. Proxy orijinal sınıfla aynı arayüze sahip olduğu için, gerçek bir hizmet nesnesi bekleyen tüm istemcilere hizmet nesnesiymiş gibi gönderilebilir.
Uygulanabilirlik
Proxy tasarım desenini kullanabileceğiniz onlarca yöntem vardır. En popüler olanları üzerinden geçelim ;
Tembel başlatma / Sanal Proxy ( Lazy Initialization / Virtual Proxy ). Sadece arada sırada ihtiyacınız olmasına rağmen sürekli çalışan, yoğun sistem kaynağı tüketen ağır bir servis nesneniz olması durumu.
Nesneyi uygulama açıldığında oluşturmak yerine bunu kullanılması gereken ana kadar öteleyebilirsiniz.
Erişim kontrolü ( koruma proxy’si). Bu servis nesnesini sadece belirli istemcilerin kullanmasını istediğiniz durumlar içindir; örneğin nesneleriniz bir işletim sisteminin hayati parçaları ve istemcilerde çalıştırılan uygulamalarsa ( kötü niyetli olanlar dahil )
Proxy sadece istemci çeşitli şartları sağlıyorsa isteği servis nesnesine iletir.
Uzak bir servisin yerel olarak yürütülmesi ( uzak proxy / remote proxy ). Bu servis nesnesi uzaktaki bir sunucuda olduğunda geçerlidir.
Bu durumda proxy istemci isteğini ağ üzerinden iletir ve ağ üzerinden çalışmakla ilgili tüm detayları o halleder.
İstekleri kayıt altına alma ( logging proxy ) Bir servis nesnesine gönderilen istekleri kayıt altına almak istediğinizde kullanılır.
Proxy istekleri servise göndermeden önce kayıt tutar.
İstek sonuçlarını ön belleğe alma ( caching proxy ) Bu tür, özellikle büyük sorgu sonuçları söz konusu olduğunda, istemcinin talebine dönen sonuçları ön belleğe alıp bu ön belleğin yaşam süresini yönetmek istediğinizde geçerlidir.
Bu proxy türü her zaman aynı sonucu verecek isteklerin sonuçlarını ön belleğe almak için kullanışlıdır. Proxy istek parametrelerini ön bellek anahtarı olarak kullanabilir.
Akıllı referans ( Smart reference ) Bu kullanan bir başka nesne yoksa ağır bir servis nesnesini serbest bırakmak için kullanılır.
Bu proxy bir servis nesnesinin referansını veya sonuçlarını alan istemcilerin bir listesini tutar. Zaman zaman bu istemcileri tarayarak hala etkin olup olmadıklarını kontrol eder. İstemci listesi boşalırsa, servis nesnesini serbest bırakarak ilgili sistem kaynaklarını boşalmasını sağlayabilir.
Proxy istemcinin servis nesnesini değiştirip değiştirmediğini de kontrol eder. Böylece değiştirilmemiş nesneler başka istemciler tarafından da kullanılabilir.
Diğer tasarım desenleri/kalıpları ile ilişkisi
- Adapter mevcut bir nesnenin arayüzünü değiştirirken, Decorator nesnenin arayüzünü değiştirmeden yeni özellikler ekler. Ayrıca Adapter özyinelemeli (recursive) kompozisyonları desteklemezken, Decoratorle bu mümkündür.
- Facade‘ler kompleks öğelerle bir ara tampon görevi görmeleri nedeniyle Proxy‘lere benzerler. Proxy Facade‘den farklı olarak sevis nesnesi ile aynı arayüze sahiptir, bu da birbirlerinin yerine geçebilmelerini sağlar.
- Decorator ve Proxy‘nin yapıları benzer, fakat amaçları bambaşkadır. Her iki desende, bir nesnenin işin bir kısmını başka bir nesneye delege ettiği composition prensibine dayanır. Aralarındaki en önemli fark; Proxy genellikle servis nesnesinin yaşam süresini kendisi belirlerken Decoratorlerde bu her zaman istemcinin kontrolündedir.
Proxy Tasarım Deseni Kod Örnekleri
Örnek PHP Kodu
<?php
namespace RefactoringGuru\Proxy\Conceptual;
/**
* The Subject interface declares common operations for both RealSubject and the
* Proxy. As long as the client works with RealSubject using this interface,
* you'll be able to pass it a proxy instead of a real subject.
*/
interface Subject
{
public function request(): void;
}
/**
* The RealSubject contains some core business logic. Usually, RealSubjects are
* capable of doing some useful work which may also be very slow or sensitive -
* e.g. correcting input data. A Proxy can solve these issues without any
* changes to the RealSubject's code.
*/
class RealSubject implements Subject
{
public function request(): void
{
echo "RealSubject: Handling request.\n";
}
}
/**
* The Proxy has an interface identical to the RealSubject.
*/
class Proxy implements Subject
{
/**
* @var RealSubject
*/
private $realSubject;
/**
* The Proxy maintains a reference to an object of the RealSubject class. It
* can be either lazy-loaded or passed to the Proxy by the client.
*/
public function __construct(RealSubject $realSubject)
{
$this->realSubject = $realSubject;
}
/**
* The most common applications of the Proxy pattern are lazy loading,
* caching, controlling the access, logging, etc. A Proxy can perform one of
* these things and then, depending on the result, pass the execution to the
* same method in a linked RealSubject object.
*/
public function request(): void
{
if ($this->checkAccess()) {
$this->realSubject->request();
$this->logAccess();
}
}
private function checkAccess(): bool
{
// Some real checks should go here.
echo "Proxy: Checking access prior to firing a real request.\n";
return true;
}
private function logAccess(): void
{
echo "Proxy: Logging the time of request.\n";
}
}
/**
* The client code is supposed to work with all objects (both subjects and
* proxies) via the Subject interface in order to support both real subjects and
* proxies. In real life, however, clients mostly work with their real subjects
* directly. In this case, to implement the pattern more easily, you can extend
* your proxy from the real subject's class.
*/
function clientCode(Subject $subject)
{
// ...
$subject->request();
// ...
}
echo "Client: Executing the client code with a real subject:\n";
$realSubject = new RealSubject();
clientCode($realSubject);
echo "\n";
echo "Client: Executing the same client code with a proxy:\n";
$proxy = new Proxy($realSubject);
clientCode($proxy);
Örnek Python Kodu
from abc import ABC, abstractmethod
class Subject(ABC):
"""
The Subject interface declares common operations for both RealSubject and
the Proxy. As long as the client works with RealSubject using this
interface, you'll be able to pass it a proxy instead of a real subject.
"""
@abstractmethod
def request(self) -> None:
pass
class RealSubject(Subject):
"""
The RealSubject contains some core business logic. Usually, RealSubjects are
capable of doing some useful work which may also be very slow or sensitive -
e.g. correcting input data. A Proxy can solve these issues without any
changes to the RealSubject's code.
"""
def request(self) -> None:
print("RealSubject: Handling request.")
class Proxy(Subject):
"""
The Proxy has an interface identical to the RealSubject.
"""
def __init__(self, real_subject: RealSubject) -> None:
self._real_subject = real_subject
def request(self) -> None:
"""
The most common applications of the Proxy pattern are lazy loading,
caching, controlling the access, logging, etc. A Proxy can perform one
of these things and then, depending on the result, pass the execution to
the same method in a linked RealSubject object.
"""
if self.check_access():
self._real_subject.request()
self.log_access()
def check_access(self) -> bool:
print("Proxy: Checking access prior to firing a real request.")
return True
def log_access(self) -> None:
print("Proxy: Logging the time of request.", end="")
def client_code(subject: Subject) -> None:
"""
The client code is supposed to work with all objects (both subjects and
proxies) via the Subject interface in order to support both real subjects
and proxies. In real life, however, clients mostly work with their real
subjects directly. In this case, to implement the pattern more easily, you
can extend your proxy from the real subject's class.
"""
# ...
subject.request()
# ...
if __name__ == "__main__":
print("Client: Executing the client code with a real subject:")
real_subject = RealSubject()
client_code(real_subject)
print("")
print("Client: Executing the same client code with a proxy:")
proxy = Proxy(real_subject)
client_code(proxy)