CodeGym /Java блог /Случаен /Модел на прокси дизайн
John Squirrels
Ниво
San Francisco

Модел на прокси дизайн

Публикувано в групата
В програмирането е важно да планирате правилно архитектурата на вашето приложение. Шаблоните за проектиране са незаменим начин за постигане на това. Днес нека поговорим за проксита.

Защо ви трябва прокси?

Този модел помага за решаване на проблеми, свързани с контролиран достъп до обект. Може да попитате: "Защо имаме нужда от контролиран достъп?" Нека да разгледаме няколко ситуации, които ще ви помогнат да разберете Howво е Howво.

Пример 1

Представете си, че имаме голям проект с куп стар code, където има клас, отговорен за експортирането на отчети от база данни. Класът работи синхронно. Тоест, цялата система не работи, докато базата данни обработва заявката. Средно генерирането на отчет отнема 30 minutesи. Съответно процесът на експортиране започва в 00:30 ч., а ръководството получава доклада сутринта. Одит разкри, че би било по-добре да можете незабавно да получите доклада в нормалното работно време. Началният час не може да бъде отложен и системата не може да блокира, докато чака отговор от базата данни. Решението е да промените начина на работа на системата, като генерирате и експортирате отчета в отделна нишка. Това решение ще позволи на системата да работи Howто обикновено и ръководството ще получава нови отчети. Въпреки това, има проблем: текущият code не може да бъде пренаписан, тъй като други части на системата използват неговата функционалност. В този случай можем да използваме прокси модела, за да въведем междинен прокси клас, който ще получава заявки за експортиране на отчети, ще регистрира началния час и ще стартира отделна нишка. След като отчетът бъде генериран, нишката се прекратява и всички са доволни.

Пример 2

Екип за разработка създава уебсайт за събития. За да получи данни за нови събития, екипът отправя запитване към услуга на трета страна. Специална частна библиотека улеснява взаимодействието с услугата. По време на разработката се открива проблем: системата на трета страна актуализира своите данни веднъж на ден, но се изпраща заявка до нея всеки път, когато потребител опреснява page. Това създава голям брой заявки и услугата спира да отговаря. Решението е да се кешира отговорът на услугата и да се върне кешираният резултат на посетителите, докато страниците се презареждат, като се актуализира кеша, ако е необходимо. В този случай моделът на прокси дизайн е отлично решение, което не променя съществуващата функционалност.

Принципът зад модела на проектиране

За да приложите този модел, трябва да създадете прокси клас. Той имплементира интерфейса на сервизния клас, имитирайки поведението му за клиентския code. По този начин клиентът взаимодейства с прокси instead of с реалния обект. По правило всички заявки се предават на сервизния клас, но с допълнителни действия преди or след. Просто казано, проксито е слой между клиентския code и целевия обект. Разгледайте примера за кеширане на резултати от заявка от стар и много бавен твърд диск. Да предположим, че говорим за разписание на електрически влакове в няHowво древно приложение, чиято логика не може да бъде променена. Всеки ден в определен час се поставя диск с актуализиран график. И така, имаме:
  1. TrainTimetableинтерфейс.
  2. ElectricTrainTimetable, който реализира този интерфейс.
  3. Клиентският code взаимодейства с файловата система чрез този клас.
  4. TimetableDisplayклиентски клас. Неговият printTimetable()метод използва методите на ElectricTrainTimetableкласа.
Диаграмата е проста: Модел на прокси дизайн: - 2в момента, с всяко извикване на printTimetable()метода, ElectricTrainTimetableкласът осъществява достъп до диска, зарежда данните и ги представя на клиента. Системата работи добре, но е много бавна. В резултат на това беше взето решение да се увеличи производителността на системата чрез добавяне на механизъм за кеширане. Това може да се направи с помощта на прокси модела: Модел на прокси дизайн: - 3По този начин TimetableDisplayкласът дори не забелязва, че взаимодейства с ElectricTrainTimetableProxyкласа instead of със стария клас. Новата реализация зарежда графика веднъж на ден. За повторни заявки той връща предварително заредения обект от паметта.

Какви задачи са най-добри за прокси?

Ето няколко ситуации, в които този модел определено ще бъде полезен:
  1. Кеширане
  2. Забавена or мързелива инициализация Защо да зареждате обект веднага, ако можете да го заредите според нуждите?
  3. Заявки за регистриране
  4. Междинна проверка на данни и достъп
  5. Стартиране на работни нишки
  6. Записване на достъп до обект
Има и други случаи на употреба. Разбирайки принципа зад този модел, можете да идентифицирате ситуации, в които той може да бъде приложен успешно. На пръв поглед проксито прави същото като фасадата , но това не е така. Проксито има същия интерфейс като обекта на услугата . Освен това не бъркайте този модел с шаблоните на декоратора or адаптера . Декораторът предоставя разширен интерфейс, а адаптерът осигурява алтернативен интерфейс.

Предимства и недостатъци

  • + Можете да контролирате достъпа до сервизния обект, Howто желаете
  • + Допълнителни възможности, свързани с управлението на жизнения цикъл на обслужващия обект
  • + Работи без обслужващ обект
  • + Подобрява производителността и сигурността на codeа.
  • - Съществува риск ефективността да се влоши поради допълнителни заявки
  • - Това прави класовата йерархия по-сложна

Прокси моделът на практика

Нека внедрим система, която чете разписанията на влаковете от твърд диск:

public interface TrainTimetable {
   String[] getTimetable();
   String getTrainDepartureTime();
}
Ето класа, който имплементира главния интерфейс:

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 "";
   }
}
Всеки път, когато получите разписанието на влаковете, програмата чете файл от диска. Но това е само началото на нашите проблеми. Целият файл се чете всеки път, когато получите разписание дори за един влак! Добре, че такъв code съществува само в примери Howво не трябва да се прави :) Клиентски клас:

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]);
       }
   }
}
Примерен файл:

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
Нека го тестваме:

public static void main(String[] args) {
   TimetableDisplay timetableDisplay = new timetableDisplay();
   timetableDisplay.printTimetable();
}
Изход:

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
Сега нека преминем през стъпките, необходими за въвеждане на нашия модел:
  1. Дефинирайте интерфейс, който позволява използването на прокси instead of оригиналния обект. В нашия пример това е TrainTimetable.

  2. Създайте прокси класа. Трябва да има препратка към сервизния обект (създайте го в класа or предайте на конструктора).

    Ето нашия прокси клас:

    
    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;
       }
    }
    

    На този етап ние просто създаваме клас с препратка към оригиналния обект и пренасочваме всички извиквания към него.

  3. Нека приложим логиката на прокси класа. По принцип обажданията винаги се пренасочват към оригиналния обект.

    
    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()дали масивът от разписания е кеширан в паметта. Ако не, той изпраща заявка за зареждане на данните от диска и запазва резултата. Ако графикът вече е бил поискан, той бързо връща обекта от паметта.

    Благодарение на простата си функционалност, методът getTrainDepartureTime() не трябваше да бъде пренасочван към оригиналния обект. Ние просто дублирахме неговата функционалност в нов метод.

    не прави това Ако трябва да дублирате codeа or да направите нещо подобно, тогава нещо се е объркало и трябва да погледнете проблема отново от различен ъгъл. В нашия прост пример нямахме друга възможност. Но в реални проекти codeът най-вероятно ще бъде написан по-правилно.

  4. В клиентския code създайте прокси обект instead of оригиналния обект:

    
    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]);
           }
       }
    }
    

    Проверете

    
    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
    

    Страхотно, работи коректно.

    Можете също така да разгледате опцията за фабрика, която създава Howто оригинален обект, така и прокси обект, в зависимост от определени условия.

Преди да се сбогуваме, ето една полезна връзка

Това е всичко за днес! Няма да е зле да се върнете към уроците и да изпробвате новите си знания на практика :)
Коментари
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION