Ders 05 / 12
Statik Üyeler ve Sabitler
Sınıfa bağlı özellik, yöntem ve sabitler, statik fabrika yöntemiyle yapıcının gizlenmesi ve bunların kullananı hangi değişikliklerde durdurduğu.
Yayın Güncelleme
Bu derste
- Yapıcıyı gizleyip nesneyi statik fabrika yöntemiyle yaratır
- Statik fabrikaya parametre eklenmesinin kullananı durdurmadığını ölçer
- Yapıcının kendisini gizlemenin ve sınıf sabitinin değerini değiştirmenin kullananı nasıl etkilediğini karşılaştırır
İçindekiler
Trait’ler yöntemleri paylaştırıyordu; bu dersin konusu ise bir nesneye değil sınıfın kendisine bağlı üyelerdir. Soru şudur: nesne yaratma yolu bir statik fabrika yöntemine taşınınca yapıcıya parametre eklemek kullananı neden durdurmuyor ve yapıcı doğrudan açık kalsaydı bu neden farklı olurdu?
Statik Üyeler
Statik (static) bir üye sınıfa bağlıdır ve nesne yaratılmadan Sinif:: ile erişilir. Sınıfın kendi içinden aynı sınıfa self:: ile erişilir. Statik özellik sınıf başına tek kopyadır ve bütün nesneler onu paylaşır. Sınıf sabiti (class constant) const ile bildirilir, değeri değişmez ve self::ORAN ya da Vergi::ORAN ile okunur.
<?php // Vergi.php — sinifa bagli sabit, ozellik ve yontem class Vergi { public const ORAN = 20; public static int $hesaplanan = 0; public static function ekle(int $tutar): int { self::$hesaplanan++; return $tutar + intdiv($tutar * self::ORAN, 100); } }
<?php // vergi_kullan.php — statik uyelere nesne olmadan erisilir require 'Vergi.php'; echo Vergi::ekle(100), "\n"; echo Vergi::ekle(200), "\n"; echo Vergi::ORAN, ' ', Vergi::$hesaplanan, "\n";
120 240 20 2
NM17. Vergi için hiçbir nesne yaratılmadı ve ekle() iki kez çalıştı; $hesaplanan o iki çağrıyı tek kopyada saydı. Bu paylaşım M11/K03’te kimin neyi gördüğü sorusuyla ölçülmüştü ve burada yeniden ölçülmüyor. Sınıfın alt sınıfta yeniden belirlenmesini isteyen static:: kullanımı (geç statik bağlama) ise M08/K02’nin konusudur ve burada ölçülmüyor.
Statik Fabrika Yöntemi
Statik fabrika yöntemi (static factory method), bir nesneyi yaratan statik bir yöntemdir. Yapıcı private yapılınca nesne yalnız bu yöntemden doğar. Böylece sınıf, nesnenin hangi değerlerle yaratılabileceğini kendisi sınırlar.
<?php // Para.php — yapicisi gizli, nesneyi statik fabrika uretir class Para { private function __construct(private int $kurus) {} public static function tl(int $tl): self { return new self($tl * 100); } public function kurus(): int { return $this->kurus; } }
<?php // para_kullan.php — nesne yalniz fabrika yoluyla yaratilir require 'Para.php'; echo Para::tl(5)->kurus(), "\n"; try { $para = new Para(500); } catch (Error $e) { echo get_class($e), "\n"; }
500 Error
NM18. Fabrika 5 lirayı 500 kuruşa çevirdi, doğrudan new Para(500) ise Error verdi çünkü yapıcı kapalıdır. Kullananın tek yolu Para::tl() oldu. Bu dersin sorusu bu tek yolun neden değiştirilebilir olduğudur.
Nesne Bağlamı ve Statik Bağlam
Örnek yöntemi bir nesnenin üzerinde çalışır ve $this o nesnedir. Statik yöntemde ise nesne yoktur, bu yüzden $this kullanılamaz; yalnız statik üyelere self:: ile ulaşılır. Aynı sınıf iki bağlamı birlikte taşıyabilir: örnek özelliği her nesnede ayrı, statik özellik tek kopyadır.
<?php // Sayac.php — ornek ozelligi ve statik ozellik bir arada class Sayac { private static int $toplam = 0; private int $yerel = 0; public function say(): string { self::$toplam++; $this->yerel++; return $this->yerel . '/' . self::$toplam; } public static function toplam(): int { return self::$toplam; } public static function bozuk(): int { return $this->yerel; } }
<?php // sayac_kullan.php — iki nesnenin yerel sayaci ayri, toplam ortak require 'Sayac.php'; $birinci = new Sayac(); $ikinci = new Sayac(); echo $birinci->say(), ' ', $birinci->say(), ' ', $ikinci->say(), "\n"; echo Sayac::toplam(), "\n"; try { Sayac::bozuk(); } catch (Error $e) { echo get_class($e), "\n"; }
1/1 2/2 1/3 3 Error
$ikinci kendi sayacında 1 gördü, ama toplamda 3: ilk nesnenin iki çağrısı da aynı statik özelliğe yazılmıştı. Statik yöntemde $this aranınca Error doğdu. Bir sınıfın alt sınıflarda farklı çözülen statik çağrısı için static:: kullanılır; geç statik bağlama denen bu davranış M08/K02’de ölçülmüştür ve burada yinelenmez.
Sınıf Sabitinin Görünürlüğü
Sınıf sabiti de özellik ve yöntem gibi görünürlük taşır. public const her yerden okunur, private const yalnız sınıfın kendi içinden. Sabit tanımı başka bir sabite dayanabilir, ama fonksiyon çağrısı gibi sabit olmayan bir ifadeye dayanamaz.
<?php // Ayarlar.php — farkli gorunurlukte sabitler class Ayarlar { public const AD = 'panel'; private const SIR = 5; public const KATSAYI = self::SIR * 2; public static function sir(): int { return self::SIR; } }
<?php // ayarlar_kullan.php — private sabite sinif disindan ulasilamaz require 'Ayarlar.php'; echo Ayarlar::AD, ' ', Ayarlar::KATSAYI, ' ', Ayarlar::sir(), "\n"; try { echo Ayarlar::SIR; } catch (Error $e) { echo get_class($e), "\n"; }
panel 10 5 Error
KATSAYI sınıf içindeki SIR’den türedi ve dışarıdan okunabildi; SIR ise yalnız sir() yöntemi üzerinden dışarı çıktı. Bu, Sınıflar ve Nesneler dersindeki özellik görünürlüğünün sabit için aynen geçerli olduğunu gösterir: sabiti private yapmak, değerdeki bir değişikliğin hangi kullananlara ulaşabileceğini sınırlar.
Ölçüm Çekirdeği
Bu dersin ölçümü de yayilim.php dosyasını kullanır; çekirdek ilk derste satır satır açıklanmıştı.
<?php // yayilim.php — bir degisikligin nereye ulastigini olcen yardimcilar function yaz(string $dizin, array $dosyalar): void { foreach ($dosyalar as $ad => $icerik) { if ($icerik === null) { continue; // null: dosya silindi } $yol = "$dizin/$ad"; if (!is_dir(dirname($yol))) { mkdir(dirname($yol), 0777, true); } file_put_contents($yol, $icerik); } } function oku(string $dizin): array { $dosyalar = []; $gezgin = new RecursiveIteratorIterator( new RecursiveDirectoryIterator($dizin, FilesystemIterator::SKIP_DOTS) ); foreach ($gezgin as $dosya) { $ad = substr($dosya->getPathname(), strlen($dizin) + 1); $dosyalar[$ad] = file_get_contents($dosya->getPathname()); } return $dosyalar; } function kostur(string $dizin): array { $komut = 'cd ' . escapeshellarg($dizin) . ' && php -d display_errors=0 main.php 2>/dev/null'; exec($komut, $satirlar, $kod); return [$kod, implode("\n", $satirlar)]; } function olc(string $ad, array $taban, array $degisiklik, array $baglanti = []): string { if (array_key_exists('main.php', $degisiklik)) { throw new LogicException("$ad: kullanan duzenlenmez"); } yaz("olcum/$ad/once", $taban); yaz("olcum/$ad/sonra", array_merge($taban, $degisiklik)); [$kodOnce, $ciktiOnce] = kostur("olcum/$ad/once"); [$kodSonra, $ciktiSonra] = kostur("olcum/$ad/sonra"); if ($kodOnce !== 0) { throw new LogicException("$ad: taban calismadi"); } $sinif = match (true) { $kodSonra !== 0 => 'kiran', $ciktiOnce !== $ciktiSonra => 'ortuk', array_intersect(array_keys($degisiklik), $baglanti) !== [] => 'baglantida', default => 'yerinde', }; printf( "%-18s %d dosya cikis=%-3d cikti %-6s -> %s\n", $ad, count($degisiklik), $kodSonra, $ciktiOnce === $ciktiSonra ? 'ayni' : 'farkli', $sinif ); return $sinif; }
Fabrikaya Parametre Eklenir
Ölçülen değişiklik, Para sınıfının yapıcısına birim adında ikinci bir parametre eklemektir. Fabrika bu parametreyi kendisi sağlar: tl() yapıcıya 'TL' verir. Kullanan main.php Para::tl(5)->kurus() değerini yazdırır.
<?php // fabrika.php — yapiciya parametre eklenir, fabrika degismeden kullanana ayni degeri verir require_once 'yayilim.php'; olc('statik-fabrika', [ 'para.php' => <<<'PHP' <?php class Para { private function __construct(private int $kurus) {} public static function tl(int $tl): self { return new self($tl * 100); } public function kurus(): int { return $this->kurus; } } PHP, 'main.php' => <<<'PHP' <?php require 'para.php'; echo Para::tl(5)->kurus(), "\n"; PHP, ], [ 'para.php' => <<<'PHP' <?php class Para { private function __construct(private int $kurus, private string $birim) {} public static function tl(int $tl): self { return new self($tl * 100, 'TL'); } public function kurus(): int { return $this->kurus; } } PHP, ]); foreach (['once', 'sonra'] as $asama) { [$kod, $cikti] = kostur("olcum/statik-fabrika/$asama"); echo $asama, ' cikis=', $kod, ' cikti=', $cikti, "\n"; }
statik-fabrika 1 dosya cikis=0 cikti ayni -> yerinde once cikis=0 cikti=500 sonra cikis=0 cikti=500
NM19. Yapıcının imzası değişti, ama kullananın gördüğü şey Para::tl(5)->kurus() olduğu için çıktı 500 olarak kaldı. Yapıcıyı yalnız fabrika çağırıyordu ve fabrikayı yazan taraf yeni parametreyi kendi içinde sağladı. Bu dersin birinci ölçü sınıfı yerindedir.
Sınırlayıcı Ölçüm
Yerinde kalışın nedeni fabrika mı yoksa değişikliğin kendisi mi? Yapıcıyı açık bırakıp kullananın new Para(500) demesini sağlayarak ayırabiliriz: aynı parametre eklenince kullanan doğrudan yapıcıya bağlıdır.
<?php // acik_yapici.php — yapici acik, kullanan new ile dogrudan cagiriyor require_once 'yayilim.php'; olc('acik-yapici', [ 'para.php' => <<<'PHP' <?php class Para { public function __construct(private int $kurus) {} public function kurus(): int { return $this->kurus; } } PHP, 'main.php' => <<<'PHP' <?php require 'para.php'; echo (new Para(500))->kurus(), "\n"; PHP, ], [ 'para.php' => <<<'PHP' <?php class Para { public function __construct(private int $kurus, private string $birim) {} public function kurus(): int { return $this->kurus; } } PHP, ]); foreach (['once', 'sonra'] as $asama) { [$kod, $cikti] = kostur("olcum/acik-yapici/$asama"); echo $asama, ' cikis=', $kod, ' cikti=', $cikti, "\n"; }
acik-yapici 1 dosya cikis=255 cikti farkli -> kiran once cikis=0 cikti=500 sonra cikis=255 cikti=
NM20. Aynı eklenen parametre, aynı sınıf; ama kullanan yapıcıyı kendisi çağırınca ikinci parametre zorunlu hâle geldi ve çıkış 255 oldu. Demek ki yerinde kalışı sağlayan parametrenin kendisi değil, kullananın yapıcıya hiç dokunamamasıydı. Fabrika yapıcı üzerindeki bağımlılığı tek bir noktaya toplar.
Sınıf sabiti ise başka bir sınıfa girer. Sınıf sabitinin değeri değişirse kullananı durduran bir şey yoktur, çünkü tip ve imza aynı kalır.
<?php // sabit.php — sinif sabitinin degeri degisir, imza ayni kalir require_once 'yayilim.php'; olc('sinif-sabiti', [ 'vergi.php' => <<<'PHP' <?php class Vergi { public const ORAN = 20; public static function ekle(int $tutar): int { return $tutar + intdiv($tutar * self::ORAN, 100); } } PHP, 'main.php' => <<<'PHP' <?php require 'vergi.php'; echo Vergi::ekle(100), "\n"; PHP, ], [ 'vergi.php' => <<<'PHP' <?php class Vergi { public const ORAN = 18; public static function ekle(int $tutar): int { return $tutar + intdiv($tutar * self::ORAN, 100); } } PHP, ]); foreach (['once', 'sonra'] as $asama) { [$kod, $cikti] = kostur("olcum/sinif-sabiti/$asama"); echo $asama, ' cikis=', $kod, ' cikti=', $cikti, "\n"; }
sinif-sabiti 1 dosya cikis=0 cikti farkli -> ortuk once cikis=0 cikti=120 sonra cikis=0 cikti=118
NM21. ORAN 20’den 18’e indi, kullanan hata almadı, ama aldığı değer 120’den 118’e kaydı: çıkış 0, çıktı farklı. Sabitin değeri bir sözleşme sayılmadığı için hiçbir yer durmadı. Bu ölçü sınıfı örtüktür.
Alt Sınıfta Statik Özellik
Alt sınıf, üst sınıfın statik özelliğini miras alır ama yeni bir kopya almaz: ikisi aynı depolamayı paylaşır. Alt sınıf özelliği kendi gövdesinde yeniden tanımlarsa ayrı bir kopya doğar. İkisi arasındaki fark, sayaç ya da kayıt defteri gibi yapıları kalıtımla genişletirken sonuç doğurur.
<?php // Paylasim.php — ust sinifin statik ozelligi alt sinifla paylasilir class Taban { public static int $adet = 0; } class Ortak extends Taban { } class Ayri extends Taban { public static int $adet = 0; }
<?php // paylasim_kullan.php — Ortak Taban ile paylasir, Ayri paylasmaz require 'Paylasim.php'; Ortak::$adet = 5; echo Taban::$adet, "\n"; Ayri::$adet = 9; echo Taban::$adet, ' ', Ayri::$adet, "\n";
5 5 9
Ortak::$adet yazımı aslında Taban’ın özelliğine yazdı; bu yüzden Taban::$adet de 5 verdi. Ayri özelliği yeniden tanımladığı için kendi değerini tuttu ve Taban’ı etkilemedi. Alt sınıflara ayrı sayaç vermek isteyen kod, özelliği her alt sınıfta yeniden tanımlamak zorundadır; unutulursa sayaçlar sessizce birleşir.
Statik Üye ve Sınama Güçlüğü
Statik üyenin ortaklığı sınıf adıyla her yerden erişilebildiği için, bir kodun ne durumda çalışacağı önceki çağrılara bağlanır. Sınama senaryoları birbirinden bağımsız olmalıdır; statik durum bu bağımsızlığı bozar. Aşağıdaki iki senaryo ayrı ayrı yazılmıştır ama ikincisi birincinin bıraktığı veriyi görür.
<?php // Onbellek.php — sinif adiyla erisilen ortak durum class Onbellek { private static array $veri = []; public static function koy(string $anahtar, int $deger): void { self::$veri[$anahtar] = $deger; } public static function say(): int { return count(self::$veri); } }
<?php // senaryo.php — ikinci senaryo birincinin biraktigi veriyi gorur require 'Onbellek.php'; function birinciSenaryo(): int { Onbellek::koy('a', 1); return Onbellek::say(); } function ikinciSenaryo(): int { return Onbellek::say(); } echo birinciSenaryo(), ' ', ikinciSenaryo(), "\n";
1 1
ikinciSenaryo() hiçbir şey koymadı ve yine de 1 gördü. Tek tek çalışsa 0 görecekti; sırayla çalışınca sonucu çalıştırma sırası belirliyor. Aynı veriyi bir nesnenin özelliği olarak tutup nesneyi dışarıdan vermek bu bağımlılığı koparır: her senaryo kendi nesnesini yaratır ve başkasının bıraktığını görmez. Bağımlılığı dışarıdan vermek kurs kapanışında ayrıca ele alınacak; bu ders yalnız statik durumun neden bu çözümü gerektirdiğini gösterdi.
Özet
- Statik üye sınıfa bağlıdır;
self::ile erişilir, statik özellik sınıf başına tek kopyadır ve sınıf sabiti değişmez. - Yapıcıyı gizleyip nesneyi statik fabrika yöntemiyle yaratmak, nesne yaratma yolunu tek noktaya toplar.
- Yapıcıya parametre eklenince fabrikadan yaratan kullanan yerinde kaldı, çıktı
500; bu dersin ölçü sınıfı yerindedir. - Aynı ekleme yapıcı açıkken kullanan
new Para(500)diyorsa kıran oldu, çıkış 255. - Sınıf sabitinin değeri
20’den18’e inince kullanan örtük etkilendi: çıkış 0, çıktı120’den118’e kaydı.
Sonraki Adım
Sabitin değeri kullananı sessizce kaydırdı; sihirli yöntemler ise okumanın kendisini bir yönteme çevirir. Sihirli Yöntemler dersi bir özelliğin okunduğu __get yönteminin içeriği değişince kullananın ne gördüğünü soruyor.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.