İçeriğe geç
academia.sh

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’den 18’e inince kullanan örtük etkilendi: çıkış 0, çıktı 120’den 118’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.

Aramak için yazmaya başlayın.

↑↓ ↵ Esc gezin · aç · kapat