Przeciążanie konstruktora Drukuj
Ocena użytkowników: / 1
SłabyŚwietny 
Wpisany przez Patryk yarpo Jar   
środa, 28 lipca 2010 12:26

Ktoś się może nawet oburzy, że o czym ja tu piszę. Przecież w PHP nie ma przeciążąnia znanego z C / Javy. Istnieje co prawda coś, co jest nazywane "przeciążaniem", jednak działa na innej zasadzie. Ja jednak nie znalazłem innej nazwy. No może "statyczne przeciążanie konstruktora". Zaraz postaram się wyjaśnić, o co mi chodzi.

 

Na początek

  • podstawowa wiedza o PHP5 (konstruktory, pola statyczne)
  • serwer www (może być lokalny, np. WAMP)

 

Zwykła klasa

Oto kod zwykłej klasy, która obudowuje funkcję fopen:

01.class File {
02.    private $sFilePath;
03.    private $hFileHandler;
04.    public function __construct( $file_path ) {
05.        $this->sFilePath = $file_path;
06.    }
07.    public function open( $mode ) {
08.        $this->hFileHandler = fopen($this->sFilePath, $mode);
09.    }
10.    public function write( $content ) {
11.        return fwrite($this->hFileHandler, $content);
12.    }
13.}
14. 
15.$oFile = new File('plik.txt');
16.$oFile->open('w+');
17.$oFile->write('test');

Spokojnie. Jestem świadomy, że powyższy kod nie jest idealny. Zaraz go zmienimy. Jak widać, podstawowe problemy:

  1. wywołanie konstruktora wcale nie otwiera pliku. Może należałoby dać tam 2-gi parametr, będący trybem, w jakim chcemy plik otworzyć.
  2. trzeba pamiętać, żeby wywoływac najpierw File::open(), a potem File::write(). Inaczej czeka nas próba zapisania do nieotwartego pliku => błąd.

To jest zły kod.

ad 1: wstawienie drugiego parametru pozwoli po raz kolejny na błędy. Niech się komuś zdarzy pomyłka i zamiast 'w+' da 'e+'... To się może zdarzyć. Najprowdopodobniej dodatkowo zdarzy się to w miejscu, gdzie nikt nie zauważy, bo rzadko tamten kod jest wywoływany. Ten bug ujawni się w wigilię, gdy będziesz zasiadał do kolacji, a klient właśnie będzie chciał uruchomić nową promocję...

Dodatkowym problemem jest to, że nie sprawdzam, czy plik w ogóle istnieje. Należy to jakoś załatać.

 

Propozycja rozwiązania

Być może lepiej jest mieć kilka konstruktorów. Czy nie byłoby dobrze, móc zrobić tak:

1.$oFile = new FileToWrite('plik.txt');
2.$oFile->write('test');
3. 
4. 
5.    

Możemy oczywiście zrobić nową klasę o takiej nazwie, która by dziedziczyła po File. Ja chciałbym jednak zaproponować coś innego.

 

"Przeciążanie" konstruktorów

01.class File {
02.    private $sFilePath;
03.    private $sMode;
04.    private $hFileHandler = null;
05. 
06.    const WRITE = 'w+'; // 1
07.    const READ  = 'r+';
08.    const ADD   = 'a+';
09. 
10.    static function forceOpenToWrite( $file ) { // 2
11.        self::createFileIfNotExists($file);
12.        return self::openToWrite($file);
13.    }
14. 
15.    static function forceOpenToAdd( $file ) { // 2
16.        self::createFileIfNotExists($file);
17.        return self::openToAdd($file);
18.    }
19. 
20.    static public function openToWrite( $file ) { // 3
21.        return new self($file, self::WRITE);
22.    }
23. 
24.    static public function openToRead( $file ) { // 3
25.        return new self($file, self::READ);
26.    }
27. 
28.    static public function openToAdd( $file ) { // 3
29.        return new self($file, self::ADD);
30.    }
31. 
32.    static private function createFileIfNotExists($file_path)  { // 4
33.        if (!file_exists($file_path))  {
34.            $file = fopen($file_path, self::WRITE);
35.            fclose($file);
36.        }
37.    }
38. 
39.    private function __construct( $file_path, $mode )  { // 5
40.        $this->sMode = strtolower($mode);
41.        $this->sFilePath = $file_path;
42.        if (!$this->fileExistsOrCanBeCreate($file_path))  { // 6
43.            throw new Exception("Nie ma takiego pliku");
44.        }
45.        $this->open($mode);
46.    }
47. 
48.    private function fileExistsOrCanBeCreate() { // 6
49.        return file_exists($this->sFilePath) || self::WRITE === $this->sMode;
50.    }
51. 
52.    public function __destruct() {
53.        $this->close();
54.    }
55. 
56.    public function read() { // 7
57.        return file_get_contents($this->sFilePath);
58.    }
59. 
60.    public function write( $content ) { //7
61.        return fwrite($this->hFileHandler, $content);
62.    }
63. 
64.    private function open( $mode ) { // 8
65.        $this->hFileHandler = fopen($this->sFilePath, $mode);
66.    }
67. 
68.    private function close() { // 8
69.        fclose($this->hFileHandler);
70.        $this->hFileHandler = null;
71.    }
72.}
73.$oFile = File::openToWrite('test1.txt');
74.$oFile->write('to jest test poprawny');
75. 
76.$oFile = File::openToAdd('test2.txt');
77.$oFile->write('to jest test poprawny');
78. 
79.$oFile = File::forceOpenToAdd('test2.txt');
80.$oFile->write('to jest test poprawny');
81. 
82.$oFile = File::openToRead('test3.txt');
83.echo $oFile->read();
84. 
85. 
86.    

Może krotko wyjaśnię ideę:

  1. Używam stałych zamiast ciągów znaków. Zauważ w ilu miejscach stałe te są wykorzystane. Jeśli nagle będę chciał wykorzystać zamiast 'w+' samo 'w' albo 'wb+' zmiany ogranicze do jednego miejsca.
  2. "Konstruktor" zwracający obiekt pliku do dopisywania lub pusty, Słowo `force' oznacz "jeśli podanego pliku nie ma - stwórz" -> patrz [4].
  3. Metody zwracające obiekty odpowiednio do zapisu odczytu, dodawania. Nie wymuszają utworzenia pliku, jeśli on nie istnieje.
  4. Metoda pomocnicza [stąd private] tworząca plik, jeśli nie istnieje.
  5. Prywatny konstruktor. Skoro mamy w "statycznych konstruktorach" rozpatrzone wszystkie możliwe ścieżki to po co udostępniać publicznie konstruktor, który może zostać błędnie wywołany.
  6. To jest jeden z moich fetyszy. Zamiast dziwnego i nic nie mówiącego warunku, wyrzuć go do osobnej metody i nazwij tak, abyś nie musiał komentować konstrukcji warunkowej.
  7. Jedyne publiczne metody. W końcu ten obiekt ma jedynie zapisywać lub odczytywać dane do/z pliku.
  8. Teraz już nie pozwalamy programiście decydować co i kiedy ma być otwarte zamknięte. On ma tylko robić to czego oczekujemy: zapisać lub odczytać dane. Tylko i aż tyle. Im mniej miejsc, gdzie może się pomylić tym lepiej.

Oczywiście powyższy kod wymagałby jeszcze poprawek. Przecież fopen wcale nie musi utworzyć pliku [za mało miejsca na dysku, brak uprawnień, setka innych powodów, których nie umiemy sobie wyobrazić]. Jednak tu chciałem pokazać pewną ideę, a nie cały gotowy system. Zapraszam do testowania.

Jeśli ktoś byłby zainteresowany, to udostępniam kolejne kroki jak przechodziłem od kodu nr 1 do ostatecznego: