Neden bir proxy'ye ihtiyacınız var?
Bu model, bir nesneye kontrollü erişimle ilgili sorunları çözmeye yardımcı olur. "Neden kontrollü erişime ihtiyacımız var?" diye sorabilirsiniz. Neyin ne olduğunu anlamanıza yardımcı olacak birkaç duruma bakalım.örnek 1
Bir veritabanından raporları dışa aktarmaktan sorumlu bir sınıfın bulunduğu, bir grup eski kod içeren büyük bir projemiz olduğunu hayal edin. Sınıf senkronize çalışır. Yani veritabanı talebi işlerken tüm sistem boştadır. Bir raporun oluşturulması ortalama olarak 30 dakika sürer. Buna göre, ihracat süreci sabah 12:30'da başlar ve yönetim raporu sabah alır. Bir denetim, raporu normal çalışma saatleri içinde hemen alabilmenin daha iyi olacağını ortaya çıkardı. Başlama zamanı ertelenemez ve sistem veri tabanından yanıt beklerken bloke edemez. Çözüm, sistemin çalışma şeklini değiştirmek, raporu ayrı bir iş parçacığında oluşturmak ve dışa aktarmaktır. Bu çözüm, sistemin her zamanki gibi çalışmasına izin verecek ve yönetim yeni raporlar alacaktır. Fakat, bir sorun var: sistemin diğer bölümleri onun işlevselliğini kullandığından mevcut kod yeniden yazılamıyor. Bu durumda, raporları dışa aktarma isteklerini alacak, başlangıç zamanını günlüğe kaydedecek ve ayrı bir iş parçacığı başlatacak bir ara proxy sınıfını tanıtmak için proxy modelini kullanabiliriz. Rapor oluşturulduğunda, ileti dizisi sonlandırılır ve herkes mutlu olur.Örnek 2
Bir geliştirme ekibi bir etkinlik web sitesi oluşturuyor. Ekip, yeni etkinliklerle ilgili verileri almak için bir üçüncü taraf hizmetini sorgular. Özel bir özel kitaplık, hizmetle etkileşimi kolaylaştırır. Geliştirme sırasında bir sorun keşfedildi: üçüncü taraf sistem verilerini günde bir kez günceller, ancak bir kullanıcı sayfayı her yenilediğinde ona bir istek gönderilir. Bu, çok sayıda istek oluşturur ve hizmet yanıt vermeyi durdurur. Çözüm, hizmetin yanıtını önbelleğe almak ve önbelleğe alınan sonucu sayfalar yeniden yüklenirken ziyaretçilere döndürerek önbelleği gerektiği gibi güncellemektir. Bu durumda proxy tasarım deseni, mevcut işlevselliği değiştirmeyen mükemmel bir çözümdür.Tasarım deseninin arkasındaki ilke
Bu modeli uygulamak için bir proxy sınıfı oluşturmanız gerekir. İstemci kodu için davranışını taklit ederek hizmet sınıfının arayüzünü uygular. Bu şekilde, müşteri gerçek nesne yerine bir proxy ile etkileşime girer. Kural olarak, tüm istekler hizmet sınıfına iletilir, ancak öncesinde veya sonrasında ek işlemler yapılır. Basitçe söylemek gerekirse, bir proxy, müşteri kodu ile hedef nesne arasındaki bir katmandır. Eski ve çok yavaş bir sabit diskten sorgu sonuçlarını önbelleğe alma örneğini ele alalım. Diyelim ki, mantığı değiştirilemeyen eski bir uygulamada elektrikli trenler için bir zaman çizelgesinden bahsediyoruz. Her gün sabit bir zamanda güncellenmiş bir zaman çizelgesine sahip bir disk yerleştirilir. Böylece sahibiz:TrainTimetable
arayüz.ElectricTrainTimetable
, bu arayüzü uygulayan.- İstemci kodu, bu sınıf aracılığıyla dosya sistemiyle etkileşime girer.
TimetableDisplay
müşteri sınıfı YöntemiprintTimetable()
, sınıfın yöntemlerini kullanırElectricTrainTimetable
.
printTimetable()
, ElectricTrainTimetable
sınıf diske erişir, verileri yükler ve istemciye sunar. Sistem çalışıyor ama çok yavaş. Sonuç olarak, önbelleğe alma mekanizması eklenerek sistem performansının artırılmasına karar verildi. Bu, proxy modeli kullanılarak yapılabilir: Böylece sınıf, eski sınıf yerine sınıfla TimetableDisplay
etkileşime girdiğini bile fark etmez . ElectricTrainTimetableProxy
Yeni uygulama, zaman çizelgesini günde bir kez yükler. Tekrar istekleri için bellekten önceden yüklenmiş nesneyi döndürür.
Bir proxy için en iyi görevler nelerdir?
İşte bu kalıbın kesinlikle işe yarayacağı birkaç durum:- Önbelleğe almak
- Gecikmeli veya yavaş başlatma Bir nesneyi gerektiği gibi yükleyebiliyorsanız neden hemen yükleyesiniz?
- Günlüğe kaydetme istekleri
- Verilerin ve erişimin ara doğrulaması
- Çalışan iş parçacıkları başlatılıyor
- Bir nesneye erişimi kaydetme
Avantajlar ve dezavantajlar
- + Hizmet nesnesine erişimi istediğiniz gibi kontrol edebilirsiniz
- + Hizmet nesnesinin yaşam döngüsünü yönetmeyle ilgili ek yetenekler
- + Servis nesnesi olmadan çalışır
- + Performansı ve kod güvenliğini artırır.
- - Ek istekler nedeniyle performansın kötüleşme riski vardır
- - Sınıf hiyerarşisini daha karmaşık hale getirir
Pratikte proxy modeli
Bir sabit diskten tren tarifelerini okuyan bir sistem uygulayalım:
public interface TrainTimetable {
String[] getTimetable();
String getTrainDepartureTime();
}
İşte ana arayüzü uygulayan sınıf:
public class ElectricTrainTimetable implements TrainTimetable {
@Override
public String[] getTimetable() {
ArrayList<String> list = new ArrayList<>();
try {
Scanner scanner = new Scanner(new FileReader(new File("/tmp/electric_trains.csv")));
while (scanner.hasNextLine()) {
String line = scanner.nextLine();
list.add(line);
}
} catch (IOException e) {
System.err.println("Error: " + e);
}
return list.toArray(new String[list.size()]);
}
@Override
public String getTrainDepartureTime(String trainId) {
String[] timetable = getTimetable();
for (int i = 0; i < timetable.length; i++) {
if (timetable[i].startsWith(trainId+";")) return timetable[i];
}
return "";
}
}
Tren tarifesini her aldığınızda, program diskten bir dosya okur. Ama bu sorunlarımızın sadece başlangıcı. Tek bir tren için bile zaman çizelgesini her aldığınızda tüm dosya okunur! Böyle bir kodun yalnızca yapılmaması gerekenlere ilişkin örneklerde bulunması iyidir :) İstemci sınıfı:
public class TimetableDisplay {
private TrainTimetable trainTimetable = new ElectricTrainTimetable();
public void printTimetable() {
String[] timetable = trainTimetable.getTimetable();
String[] tmpArr;
System.out.println("Train\\tFrom\\tTo\\t\\tDeparture time\\tArrival time\\tTravel time");
for (int i = 0; i < timetable.length; i++) {
tmpArr = timetable[i].split(";");
System.out.printf("%s\t%s\t%s\t\t%s\t\t\t\t%s\t\t\t%s\n", tmpArr[0], tmpArr[1], tmpArr[2], tmpArr[3], tmpArr[4], tmpArr[5]);
}
}
}
Örnek dosya:
9B-6854;London;Prague;13:43;21:15;07:32
BA-1404;Paris;Graz;14:25;21:25;07:00
9B-8710;Prague;Vienna;04:48;08:49;04:01;
9B-8122;Prague;Graz;04:48;08:49;04:01
Test edelim:
public static void main(String[] args) {
TimetableDisplay timetableDisplay = new timetableDisplay();
timetableDisplay.printTimetable();
}
Çıktı:
Train From To Departure time Arrival time Travel time
9B-6854 London Prague 13:43 21:15 07:32
BA-1404 Paris Graz 14:25 21:25 07:00
9B-8710 Prague Vienna 04:48 08:49 04:01
9B-8122 Prague Graz 04:48 08:49 04:01
Şimdi modelimizi tanıtmak için gerekli adımları inceleyelim:
-
Orijinal nesne yerine proxy kullanımına izin veren bir arayüz tanımlayın. Örneğimizde, bu
TrainTimetable
. -
Proxy sınıfını oluşturun. Hizmet nesnesine bir referansı olmalıdır (sınıfta oluşturun veya yapıcıya iletin).
İşte proxy sınıfımız:
public class ElectricTrainTimetableProxy implements TrainTimetable { // Reference to the original object private TrainTimetable trainTimetable = new ElectricTrainTimetable(); private String[] timetableCache = null @Override public String[] getTimetable() { return trainTimetable.getTimetable(); } @Override public String getTrainDepartureTime(String trainId) { return trainTimetable.getTrainDepartureTime(trainId); } public void clearCache() { trainTimetable = null; } }
Bu aşamada, sadece orijinal nesneye referansla bir sınıf oluşturuyoruz ve tüm çağrıları ona yönlendiriyoruz.
-
Proxy sınıfının mantığını uygulayalım. Temel olarak, çağrılar her zaman orijinal nesneye yönlendirilir.
public class ElectricTrainTimetableProxy implements TrainTimetable { // Reference to the original object private TrainTimetable trainTimetable = new ElectricTrainTimetable(); private String[] timetableCache = null @Override public String[] getTimetable() { if (timetableCache == null) { timetableCache = trainTimetable.getTimetable(); } return timetableCache; } @Override public String getTrainDepartureTime(String trainId) { if (timetableCache == null) { timetableCache = trainTimetable.getTimetable(); } for (int i = 0; i < timetableCache.length; i++) { if (timetableCache[i].startsWith(trainId+";")) return timetableCache[i]; } return ""; } public void clearCache() { trainTimetable = null; } }
getTimetable()
Zaman çizelgesi dizisinin bellekte önbelleğe alınıp alınmadığını kontrol eder . Değilse, verileri diskten yüklemek için bir istek gönderir ve sonucu kaydeder. Zaman çizelgesi zaten talep edilmişse, nesneyi hızlı bir şekilde bellekten döndürür.Basit işlevselliği sayesinde getTrainDepartureTime() yönteminin orijinal nesneye yeniden yönlendirilmesi gerekmiyordu. İşlevselliğini yeni bir yöntemde kopyaladık.
Bunu yapma. Kodu çoğaltmanız veya benzer bir şey yapmanız gerekiyorsa, bir şeyler ters gitti ve soruna tekrar farklı bir açıdan bakmanız gerekiyor. Basit örneğimizde, başka seçeneğimiz yoktu. Ancak gerçek projelerde kod büyük olasılıkla daha doğru yazılacaktır.
-
İstemci kodunda orijinal nesne yerine bir proxy nesnesi oluşturun:
public class TimetableDisplay { // Changed reference private TrainTimetable trainTimetable = new ElectricTrainTimetableProxy(); public void printTimetable() { String[] timetable = trainTimetable.getTimetable(); String[] tmpArr; System.out.println("Train\\tFrom\\tTo\\t\\tDeparture time\\tArrival time\\tTravel time"); for (int i = 0; i < timetable.length; i++) { tmpArr = timetable[i].split(";"); System.out.printf("%s\t%s\t%s\t\t%s\t\t\t\t%s\t\t\t%s\n", tmpArr[0], tmpArr[1], tmpArr[2], tmpArr[3], tmpArr[4], tmpArr[5]); } } }
Kontrol etmek
Train From To Departure time Arrival time Travel time 9B-6854 London Prague 13:43 21:15 07:32 BA-1404 Paris Graz 14:25 21:25 07:00 9B-8710 Prague Vienna 04:48 08:49 04:01 9B-8122 Prague Graz 04:48 08:49 04:01
Harika, düzgün çalışıyor.
Belirli koşullara bağlı olarak hem orijinal nesne hem de proxy nesne oluşturan bir fabrika seçeneğini de düşünebilirsiniz.
GO TO FULL VERSION