JavaRush /Java блогы /Random-KK /Прокси дизайн үлгісі

Прокси дизайн үлгісі

Топта жарияланған
Бағдарламалауда қолданбалы архитектураны дұрыс жоспарлау маңызды. Бұл үшін таптырмас құрал - дизайн үлгілері. Бүгін біз прокси немесе басқаша айтқанда, депутат туралы сөйлесетін боламыз.

Сізге депутат не үшін керек?

Бұл үлгі нысанға басқарылатын қатынаспен байланысты мәселелерді шешуге көмектеседі. Сізде сұрақ туындауы мүмкін: «Бізге мұндай басқарылатын қол жеткізу не үшін қажет?» Ненің не екенін анықтауға көмектесетін бірнеше жағдайды қарастырайық.

1-мысал

Бізде дерекқордан есептерді жүктеп алуға жауапты класс бар ескі codeтар жиынтығы бар үлкен жоба бар деп елестетіп көрейік. Класс синхронды түрде жұмыс істейді, яғни дерекқор сұрауды өңдеген кезде бүкіл жүйе жұмыс істемейді. Орта есеппен есеп 30 minutesта жасалады. Осы мүмкіндіктің арқасында оны жүктеп салу 00:30-да басталады және басшылық бұл есепті таңертең алады. Сараптама барысында есеп құрастырылғаннан кейін бірден, яғни бір күн ішінде алу қажет екені анықталды. Бастау уақытын өзгерту мүмкін емес, өйткені жүйе дерекқордан жауап күтеді. Шешім бөлек ағында жүктеп салуды және есеп шығаруды бастау арқылы жұмыс принципін өзгерту болып табылады. Бұл шешім жүйенің әдеттегідей жұмыс істеуіне мүмкіндік береді және басшылық жаңа есептерді алады. Дегенмен, мәселе бар: ағымдағы codeты қайта жазу мүмкін емес, өйткені оның функцияларын жүйенің басқа бөліктері пайдаланады. Бұл жағдайда есепті жүктеп салуға, басталу уақытын тіркеуге және бөлек ағынды іске қосуға сұранысты алатын орынбасар үлгісін пайдаланып аралық прокси класын енгізуге болады. Есеп жасалғанда, ағын өз жұмысын аяқтайды және барлығы бақытты болады.

2-мысал

Әзірлеу тобы постер веб-сайтын жасайды. Жаңа оқиғалар туралы деректерді алу үшін олар үшінші тарап қызметіне жүгінеді, онымен өзара әрекеттесу арнайы жабық кітапхана арқылы жүзеге асырылады. Әзірлеу кезінде мәселе туындады: үшінші тарап жүйесі деректерді күніне бір рет жаңартады және оған сұрау пайдаланушы бетті жаңартқан сайын пайда болады. Бұл сұраулардың көп санын жасайды және қызмет жауап беруді тоқтатады. Шешім қызмет жауабын кэштеу және келушілерді әрбір қайта жүктеу кезінде сақталған нәтижемен қамтамасыз ету, қажет болған жағдайда бұл кэшті жаңарту болып табылады. Бұл жағдайда орынбасар үлгісін пайдалану дайын функционалдылықты өзгертпестен тамаша шешім болып табылады.

Үлгі қалай жұмыс істейді

Бұл үлгіні енгізу үшін прокси класын жасау керек. Ол клиент codeы үшін оның әрекетін модельдейтін қызмет класының интерфейсін жүзеге асырады. Осылайша, нақты an objectінің орнына клиент өзінің проксиімен әрекеттеседі. Әдетте, барлық сұраулар қызмет көрсету класына беріледі, бірақ оның шақыруына дейін немесе одан кейін қосымша әрекеттермен. Қарапайым тілмен айтқанда, бұл прокси нысаны клиент codeы мен мақсатты нысан арасындағы қабат болып табылады. Өте баяу ескі дискіден сұрауды кэштеу мысалын қарастырайық. Бұл жұмыс принципін өзгертуге болмайтын ежелгі қолданбадағы электр пойыздарының кестесі болсын. Жаңартылған кестесі бар диск күн сайын белгіленген уақытта енгізіледі. Сонымен, бізде:
  1. Интерфейс TimetableTrains.
  2. TimetableElectricTrainsБұл интерфейсті жүзеге асыратын класс .
  3. Дәл осы класс арқылы клиент codeы дискінің файлдық жүйесімен әрекеттеседі.
  4. Клиент класы DisplayTimetable. Оның әдістемесі printTimetable()әдістерін пайдаланады TimetableElectricTrains.
Схема қарапайым: Прокси дизайн үлгісі - 2Қазіргі уақытта әдіс шақырылған сайын printTimetable()сынып TimetableElectricTrainsдискіге кіріп, деректерді түсіреді және оны клиентке береді. Бұл жүйе жақсы жұмыс істейді, бірақ өте баяу. Сондықтан кэштеу механизмін қосу арқылы жүйе өнімділігін арттыру туралы шешім қабылданды. Мұны прокси үлгісін пайдалану арқылы жасауға болады: Прокси дизайн үлгісі - 3Осылайша сынып алдыңғы DisplayTimetableсыныппен емес, сыныппен өзара әрекеттесетінін байқамайды . TimetableElectricTrainsProxyЖаңа енгізу кестені күніне бір рет жүктейді және қайталанатын сұраулар бойынша бұрыннан жүктелген нысанды жадтан қайтарады.

Проксиді қандай тапсырмалар үшін қолданған дұрыс?

Міне, бұл үлгі міндетті түрде пайдалы болатын бірнеше жағдайлар:
  1. Кэштеу.
  2. Жалқау іске асыру жалқау іске асыру ретінде де белгілі. Неліктен an objectіні қажетінше жүктей алатын болсаңыз, оны бірден жүктеңіз?
  3. Тіркеу сұраулары.
  4. Аралық деректер мен қол жеткізуді тексеру.
  5. Параллельді өңдеу ағындарын іске қосу.
  6. Қоңырау тарихын жазу немесе санау.
Басқа пайдалану жағдайлары да бар. Бұл үлгінің жұмыс принципін түсіне отырып, сіз оған сәтті қосымшаны таба аласыз. Бір қарағанда, орынбасар қасбетпен бірдей нәрсені жасайды , бірақ олай емес. Проксиде қызмет көрсету нысанымен бірдей интерфейс бар . Сондай-ақ, үлгіні Декоратор немесе Адаптермен шатастырмаңыз . Декоратор кеңейтілген интерфейсті қамтамасыз етеді, ал адаптер балама интерфейсті ұсынады.

Артылықшылықтар мен кемшіліктер

  • + Қызмет көрсету an objectісіне қол жеткізуді өз қалауыңыз бойынша басқара аласыз;
  • + Қызмет көрсету an objectісінің өмірлік циклін басқарудың қосымша мүмкіндіктері;
  • + Қызмет көрсету an objectісісіз жұмыс істейді;
  • + Код өнімділігі мен қауіпсіздігін жақсартады.
  • - қосымша емдеуге байланысты өнімділіктің нашарлау қаупі бар;
  • - Бағдарлама сабақтарының құрылымын қиындатады.

Тәжірибедегі алмастыру үлгісі

Пойыз кестелерін дискіден оқитын жүйені енгізейік:
public interface TimetableTrains {
   String[] getTimetable();
   String getTrainDepartureTime();
}
Негізгі интерфейсті жүзеге асыратын класс:
public class TimetableElectricTrains implements TimetableTrains {

   @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 тек нашар мысалдарда болғаны жақсы :) Клиент класы:
public class DisplayTimetable {
   private TimetableTrains timetableTrains = new TimetableElectricTrains();

   public void printTimetable() {
       String[] timetable = timetableTrains.getTimetable();
       String[] tmpArr;
       System.out.println("Поезд\tОткуда\tКуда\t\tВремя отправления\tВремя прибытия\tВремя в пути");
       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;Лондон;Прага;13:43;21:15;07:32
BA-1404;Париж;Грац;14:25;21:25;07:00
9B-8710;Прага;Вена;04:48;08:49;04:01;
9B-8122;Прага;Грац;04:48;08:49;04:01
сынап көрейік:
public static void main(String[] args) {
   DisplayTimetable displayTimetable = new DisplayTimetable();
   displayTimetable.printTimetable();
}
Қорытынды:

Поезд  Откуда  Куда   Время отправления Время прибытия    Время в пути
9B-6854  Лондон  Прага    13:43         21:15         07:32
BA-1404  Париж   Грац   14:25         21:25         07:00
9B-8710  Прага   Вена   04:48         08:49         04:01
9B-8122  Прага   Грац   04:48         08:49         04:01
Енді үлгімізді жүзеге асырудың қадамдарын қарастырайық:
  1. Бастапқы нысанның орнына жаңа проксиді пайдалануға мүмкіндік беретін интерфейсті анықтаңыз. Біздің мысалда бұл TimetableTrains.

  2. Прокси класын жасаңыз. Онда қызмет көрсету an objectісіне сілтеме болуы керек (сыныпта жасау немесе конструкторда өту);

    Міне, біздің прокси классымыз:

    public class TimetableElectricTrainsProxy implements TimetableTrains {
       // Ссылка на оригинальный an object
       private TimetableTrains timetableTrains = new TimetableElectricTrains();
    
       private String[] timetableCache = null
    
       @Override
       public String[] getTimetable() {
           return timetableTrains.getTimetable();
       }
    
       @Override
       public String getTrainDepartureTime(String trainId) {
           return timetableTrains.getTrainDepartureTime(trainId);
       }
    
       public void clearCache() {
           timetableTrains = null;
       }
    }

    Бұл кезеңде біз жай ғана бастапқы нысанға сілтеме жасайтын класс жасаймыз және оған барлық қоңырауларды жібереміз.

  3. Біз прокси класының логикасын жүзеге асырамыз. Негізінде қоңырау әрқашан бастапқы нысанға қайта бағытталады.

    public class TimetableElectricTrainsProxy implements TimetableTrains {
       // Ссылка на оригинальный an object
       private TimetableTrains timetableTrains = new TimetableElectricTrains();
    
       private String[] timetableCache = null
    
       @Override
       public String[] getTimetable() {
           if(timetableCache == null) {
               timetableCache = timetableTrains.getTimetable();
           }
           return timetableCache;
       }
    
       @Override
       public String getTrainDepartureTime(String trainId) {
           if(timetableCache == null) {
               timetableCache = timetableTrains.getTimetable();
           }
           for(int i = 0; i < timetableCache.length; i++) {
               if(timetableCache[i].startsWith(trainId+";")) return timetableCache[i];
           }
           return "";
       }
    
       public void clearCache() {
           timetableTrains = null;
       }
    }

    Әдіс getTimetable()кесте массивінің жадта кэштелгенін тексереді. Олай болмаса, ол нәтижені сақтай отырып, дискіден деректерді жүктеуге сұраныс береді. Егер сұрау әлдеқашан іске қосылған болса, ол жадтан нысанды жылдам қайтарады.

    Қарапайым функционалдығының арқасында getTrainDepartireTime() әдісін бастапқы нысанға қайта бағыттау қажет болмады. Біз оның функционалдығын жаңа әдіске көшірдік.

    Сіз мұны істей алмайсыз. Егер сізге codeты көшіру немесе ұқсас манипуляцияларды орындау қажет болса, бұл бірдеңе дұрыс болмады және мәселені басқа бұрыштан қарау керек дегенді білдіреді. Біздің қарапайым мысалда басқа жол жоқ, бірақ нақты жобаларда code дұрысырақ жазылуы мүмкін.

  4. Клиент codeындағы бастапқы нысанның жасалуын ауыстыру нысанымен ауыстырыңыз:

    public class DisplayTimetable {
       // Измененная link
       private TimetableTrains timetableTrains = new TimetableElectricTrainsProxy();
    
       public void printTimetable() {
           String[] timetable = timetableTrains.getTimetable();
           String[] tmpArr;
           System.out.println("Поезд\tОткуда\tКуда\t\tВремя отправления\tВремя прибытия\tВремя в пути");
           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  Лондон  Прага    13:43         21:15         07:32
    BA-1404  Париж   Грац   14:25         21:25         07:00
    9B-8710  Прага   Вена   04:48         08:49         04:01
    9B-8122  Прага   Грац   04:48         08:49         04:01

    Керемет, ол дұрыс жұмыс істейді.

    Сондай-ақ белгілі бір шарттарға байланысты бастапқы нысанды да, ауыстыру нысанын да жасайтын зауытты қарастыруға болады.

Нүктенің орнына пайдалы сілтеме

  1. Үлгілер туралы тамаша мақала және «Депутат» туралы аздап

Бүгінге бәрі осы! Оқуға қайта оралып, жаңа біліміңізді тәжірибеде сынап көрсеңіз жақсы болар еді :)
Пікірлер
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION