Рейтинг@Mail.ru
 
Инкапсуляция.

Как и подавляющее большинство терминов в программировании, инкапсуляция вызывает смятение и панику.
Слово такое заумное, объяснение еще круче:
Инкапсуляция — свойство языка программирования, позволяющее объединить и защитить данные и код 
в объект и скрыть реализацию объекта от пользователя (прикладного программиста). 
При этом пользователю предоставляется только спецификация (интерфейс) объекта.

                                                                              Википедия ©

Какие данные, от кого защищиать, что за секреты такие. Что за интерфейс? Где у него кнопка?

Все как обычно гораздо проще. Инкапсуляция, это черный ящик. Внутри что то происходит, но мы можем только пользоваться результатами.
Или влиять на работу так, как нам разрешили.
Это похоже на автомобиль - есть руль и педали,
а что там под капотом стучит и в выхлопной трубе стреляет - не нашего собачьего ума дело )). Пусть автослесарь разбирается.
Или еще похоже на компьютер. Есть клавиатура, есть монитор, а как эта железяка работает - загадка природы. Да нам знать и не надо, лишь бы работало.

Рассмотрим для примера простенький скрипт грабителя грабера погоды.

Допустим мы решили у себя на сайте сделать такой информер:

Всё банально просто, берем код на Гисметео и вставляем к себе на сайт.
1
2
3
4
5
<a href="http://www.gismeteo.ru/city/daily/4064/"> 
<img src="http://informer.gismeteo.ru/new/4064-30.GIF" alt="GISMETEO: Погода по г.Бобруйск" title="GISMETEO: Погода по г.Бобруйск" border="0"> 
</a>

Но. Зачем нам каждый раз посылать запросы на сторонний ресурс, если картинка эта меняется раз в сутки? Особенно этого не любят банкиры, когда к ним часто ныряют за курсом валют. Они и забанить могут. Они вообще себе на уме - нахапали денек и радуются.

Так вот, к примеру нам нужен скрипт, который бы раз в сутки получал картинку с Гисметео и сохранял на нашем сервере. Это называется кэширование.
Это не сложно:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php

$url 
'http://informer.gismeteo.ru/new/4064-30.GIF'
$pic 'cache/bobruisk.gif'
$time 24

    if(!
file_exists($pic) || date('U') - $time 3600 filemtime($pic)) 
    { 
        
$info file_get_contents($url); 
        
file_put_contents($pic$info);     
    } 
?> 
<img src="<?php echo $pic ?>"  border="0" />


Что тут. Мы смотрим, есть ли у нас в кэше нужный файл. А так же смотрим время его последнего изменения. Если оно больше суток - бежим в Гисметео и тырим свежую картинку.

И так бы ладно, всего несколько строк. Однако если нам понадобится несколько таких картинок, для разных городов, то придется этот код повторять. А це не дило.
Значит сделаем функцию, долго ли умеючи.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
<?php

    $url   
'http://informer.gismeteo.ru/new/'
    
$pic1  '4064-30.GIF'
    
$pic2  '5045-30.GIF'
    
$cache 'cache/'
    
$time  24;

/* 
* Function of cache the informant of weather 
* Функция кэширования информера погоды 
* @param string 
* @param string 
* @param string 
* @param integr 
* @return string 
*/ 
    
function gisGrab($pic$url$caсhe$time
    { 
        
$flag true
         
        if(!
file_exists($cache $pic) || date('U') - $time 3600 filemtime($cache $pic)) 
        { 
            
$flag false
             
            if(
$info @file_get_contents($url $pic)) 
                if(
file_put_contents($cache $pic$info))     
                    return 
$cache $pic
        } 
                     
        return (
$flag)?$cache $pic $url $pic

    } 
?> 
<img src="<?php echo gisGrab($pic1$url$cache$time?>"  border="0" /> 
<img src="<?php echo gisGrab($pic2$url$cache$time?>"  border="0" />
Довольно надежная и фполне так себе функция.

Вроде бы все ничего, можно даже закрыть глаза на неэстетичный вызов. Но с точки зрения оптимальности это неверное решение, так как мы вынуждены проверять каждую картинку, а это куча обращений к файловой системе.
Можно проверить один файл и ориентироваться по нему, объявив какую нибудь переменную статической. Мы так уже делали. Но ориентироваться нужно по последнему файлу, потому что мы перезапишем первый и он покажет, что все нормально. И остальные перезаписывать не станет. Итак, вот что получилось:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
<?php

    $url   
'http://informer.gismeteo.ru/new/'
    
$pic1  '4064-30.GIF'
    
$pic2  '5045-30.GIF'
    
$cache 'cache/'
    
$time  24

/* 
* Function of cache the informant of weather 
* Функция кэширования информера погоды 
* @param string 
* @param string 
* @param string 
* @param integr 
* @param string 
* @return string 
*/ 
    
function gisGrab($pic$url$cache$time$last
    { 
        
$flag true
        static 
$trig false
                 
        if(!
$trig
        {    
            if(
file_exists($cache $pic) && date('U') - $time 3600 filemtime($cache $last)) 
                
$trig true
                 
            if(!
$trig && $info @file_get_contents($url $pic)) 
            { 
                
$flag false
                 
                if(
file_put_contents($cache $pic$info))     
                    return 
$cache $pic
            }                    
        } 
         
        return (
$flag)?$cache $pic $url $pic
    } 
?> 
<img src="<?php echo gisGrab($pic1$url$cache$time$pic2?>"  border="0" /> 
<img src="<?php echo gisGrab($pic2$url$cache$time$pic2?>"  border="0" />


Мало того, что вызов совсем некрасивый (можно конечно глобальными переменными решить, но это не гут), так еще если понадобится добавить картинок, придется во всех вызовах менять последнюю.

Вот тут очень удобно использовать класс. Вот такой допустим:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
<?php

/** 
 * IRB_Cache_Img - Class of preservation of foreign files 
 * NOTE: Requires PHP version 5 or later and GD version 2.0.1 or later 
 * @package irb_cache_img 
 * @author IT studio IRBIS-team 
 * @copyright © 2009 IRBIS-team 
 * @version 0.1 
 * @license http://www.opensource.org/licenses/rpl1.5.txt 
 */     

class IRB_Cache_Img 


    private  
$url   ''
    private  
$cache ''
    private  
$time  0
    private  
$flag  true;     
     
/* 
* Constructor 
* @param string 
* @param string 
* @param integr 
*/         
    
public function __construct($url$cache 'cache/'$time 24
    { 
        
$this->url  $url
        
$this->cache $cache
        
$this->time =  date('U') - $time 3600;     
    } 
         
/* 
* The manager of pictures 
* @access public  
* @param string 
* @return string 
*/     
    
public function getImg($pic
    { 
        if(
$this->flag
            
$this->flag $this->getTime($pic); 
             
        if(
$this->flag
            
$pic $this->writeCache($pic); 
        else 
            
$pic $this->cache $pic
             
        return 
$pic;     
    } 
     
/* 
* Receives time of last change 
* @access private  
* @param string 
* @return boolean 
*/     
    
private function getTime($pic)                 
    {     
        if(!
file_exists($this->cache $pic) || $this->time filemtime($this->cache $pic)) 
            return 
true
        else 
            return 
false
    }     
         
/* 
* Keeps a new picture 
* @access private  
* @param string 
* @return string 
*/     
    
private function writeCache($pic)                 
    {     
        if(!
$info @file_get_contents($this->url $pic)) 
            return 
$this->url $pic
         
        if(!
file_put_contents($this->cache $pic$info))     
            return 
$this->url $pic;         

        return 
$this->cache $pic
    }         


/////////////////////////////////////////////////////////////// 
     
    
$url 'http://informer.gismeteo.ru/new/'
    
$pic1 '4064-30.GIF'
    
$pic2 '5045-30.GIF'

    
$img = new IRB_Cache_Img($url); 
?> 


<img src="<?php echo $img -> getImg($pic2?>"  border="0" /> 
<img src="<?php echo $img -> getImg($pic1?>"  border="0"/>


И хотя букаф несколько больше и явно потягивает индуаизмом, но зато посмотрите на сколько эстетичнее смотрится вызов. А самое главное - теперь не нужно переживать в каком порядке на странице следуют картинки. А это немаловажно, потому что дизайнер может запросто их переставить местами. И не нужно определять , где последняя картинка и сколько их вообще.

Конечно, тут разница заметна не особо, потому что мы выбрали для примера самый элементарный функционал. Но если вариантов гораздо больше, простыми функциями уже обойтись на много сложнее. Чуть позже мы будем разбирать почтовый класс и несколько других. Там это очень ярко заметно.

Однако ближе к нашим барашкам. Во первых, как это работает. А во вторых, что за новые каракули: public и private?

Работает просто. Сначала при инициализации класса мы вызываем конструктор. Он организует внутриклассовые переменные, которые нам понадобятся в разных методах.

Затем мы запускаем метод getImg(). Этот метод смотрит состояние флага. А так как мы ничегошеньки пока не успели натворить, то флаг равен true. А раз так все тоскливо, то вызываем другой метод, который определяет наличие файла и время его последней модификации (изменения).

Вроде бы все как в функции, так в чем тут подвох? А в том, что переменная, объявленная в экземпляре класса, живет до его скоропостижной кончины. Живут они долго и счастливо и помирают в один день. А значит флаг, поднятый назначенный при первом заходе, будет гордо реять, пока мы не получим все картинки. А так же в том, что мы можем на основании этого флага выбирать действия внутри класса - запускать метод кэширования или нет.

Если что то не так (вышло время или файл сбежал), то метод getTime() вернет истину и поползновения продолжатся. Подключится метод кэширования и пойдет проверка каждого файла. А если ничего трогать не надо, файл на месте, время не вышло, то метод вернет false, что и снимет флаг. А без флага ни кто в атаку не пойдет, по этому мы получим картинки из кэша.

То есть порядок следования картинок теперь не важен. Смотрим время модификации первого попавшегося файла, а дальше ориентируемся по внутриклассовому флагу, который действителен на протяжении всей работы объекта. Сколько раз бы мы не вызывали метод getImg(). Именно первого файла, а не последнего, как в функции. Вот в чем вся тонкость и прелесть.

Обратите внимание, что методы внутри класса вызываются так же, как внутриклассовые переменные, а именно с $this->впереди().

Ну а теперь как раз и про объявление переменных. В 4-й версии PHP переменные объявлялись ключевым словом var. Это и в пятерке работает, но очень настоятельно не рекомендуется. А рекомендуется сразу установить доступы. Как переменным, так и методам. По умолчанию все методы публичны, то есть могут вызываться снаружи класса. Но лучше доступ задать явно.

Как можно догадаться, public обозначает - публичный. У нас метод такой тут один (не считая конструктора, он всегда публичный). А именно getImg(), который мы вызывали в сорце картинки. То есть снаружи класса. Остальные вне класса нам не нужны, вот их мы обозвали приватными, то есть внутренними (private). Их ни как не достать, они в домике. Есть еще третий вид - protected. С ним несколько сложнее, поймете, когда разберемся с наследованием.

Вот это и есть преславутая инкапсуляция: переменные и методы запечатаны внутри класса. Мы не можем влиять на класс никак, кроме как разрешенными для этого средствами. Дадено нам в руки getImg() - получаем картинку и радуемся хорошей погоде. А к управлению кэшем лазить нечего, мало ли что случиться может. Прогноз погоды - дело очень точное, никаких отклонений не терпит.
Можно в порядке эксперимента попробовать вызвать метод writeCache() снаружи класса:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/////////////////////////////////////////////////////////////// 
     
    
$url 'http://informer.gismeteo.ru/new/'
    
$pic1 '4064-30.GIF'
    
$pic2 '5045-30.GIF'

    
$img = new IRB_Cache_Img($url); 
    
$img -> writeCache($pic1); 
?> 


<img src="<?php echo $img -> getImg($pic2?>"  border="0" /> 
<img src="<?php echo $img -> getImg($pic1?>"  border="0"/>

и мы незамедлительно получим ошибку. Мол не суйся не в свое дело.

Вообще с инкапсуляцией нужно быть достаточно осторожным. Потому как ООП само по себе расхолаживает программиста и делает его толстым и ленивым. Вот допустим мы понятия не имеем, что там творится в классе - инкапсуляция ведь, а там что то непотребное. И мы, что бы получить какие то данные, не думая вызываем какой нибудь метод. Вместо того, что бы использовать допустим сессию. Нам невдомек, что класс для получения данных из учетной записи, выполняет 32 запроса к разным таблицам. А нам нужен только логин.

Потому что класс писан не нами, и что там творится - темный лес. Инкапсуляция. Черный ящик.
И начинаем мы медленно и верно жрать ресурс и ложить сервер. Мне на практике встречались такие сайты, которые после оптимизации снижали потребление ресурса в 30! раз. И все это из за неумелого обращения с инкапсуляцией. Это примерно как если бы мы заводили огромный КаМАЗ для того, что бы воспользоваться прикуривателем, хотя рядом лежит зажигалка.

Ну а пока посмотрим, что по плану. Тут я сильно надеюсь, что хоть чуточку Вам стало яснее, для чего люди не спали ночей, грызли подушки и рвали на себе пижамы, изобретая Объектно Ориентированное Программирование. Значит не обманем их в ожиданиях и научимся пользоваться наследованием.

Классег кстати можно стырить тут.
Нужно отметить, что этот класс имеет смысл использовать только тогда, когда нет возможности воспользоваться системой CRON. О ней мы тоже расскажем на последующих занятиях.