Skip to content

9. Trait'ler

Trait'ler, sınıflara benzer yapılardır. Ancak bunlar için örnek oluşturulamaz. Bunlar, sınıflara dahil edilmek üzere tasarlanmıştır. Bir Trait'i bir sınıfa dahil etmek, Trait'in kodunu sınıfa kopyalamakla aynı etkiye sahiptir. Bir Trait'e, birden fazla sınıfta yeniden kullanılabilecek kodlar eklenir.

9.1. Komut dosyası ağacı

Image

9.2. Bir sınıfta bir özellik ekleme

[traits-01.php] komut dosyası, bir sınıfta bir özelliğin temel kullanımını gösterir:


<?php

class Class04 {
  // özellik
  private $name;

  // yapıcı
  public function __construct(string $name) {
    $this->name = $name;
  }

  // alıcı ve ayarlayıcılar
  public function getName(): string {
    return $this->name;
  }

  public function setName(string $name): void {
    $this->name = $name;
  }

}

trait Trait04 {
  // özellik
  private $name;

  // alıcı ve ayarlayıcılar
  public function getName(): string {
    return $this->name;
  }

  public function setName(string $name): void {
    $this->name = $name;
  }

}

class Class05 {
  // Trait'in dahil edilmesi
  use Trait04;

  // yapıcı
  public function __construct(string $name) {
    $this->name = $name;
  }

}

// test --------------
$class04 = new Class04("Tim");
$class05 = new Class05("Burton");
print $class04->getName() . "\n";
print $class05->getName() . "\n";
// iki sınıfın görüntülenmesi
print_r($class04);
print_r($class05);

Kod açıklamaları

  • 3-21. satırlar: [Class04] sınıfının bir öznitelik, get/set yöntemleri ve bir oluşturucu ile tanımlanması;
  • 23-36. satırlar: [Class04] kodunu yapıcı olmadan alınır ve olduğu gibi [Trait04] trait'ine aktarılır. Bir trait'in örneklenememesi nedeniyle yapıcı alınmaz;
  • 23. satır: [trait] anahtar sözcüğü, [Trait04]'i bir sınıf değil, bir trait yapar;
  • 38-45. satırlar: [Class05] sınıfı tanımlanıyor; bu sınıf, [Trait04] trait'inin kodunu (satır 40) devralıyor ve sınıfa örneklenebilir hale getirmek için [Class04] sınıfındakiyle aynı bir yapıcı (satır 43-45) ekliyor;
  • 40. satır: Bir sınıf içine bir Trait’in eklenmesini sağlayan anahtar kelime [use]’tir;
  • 50-56. satırlar: Testler, [Class04] ve [Class05] sınıflarının aynı şekilde çalıştığını göstermektedir;

Sonuçlar

Tim
Burton
Class04 Object
(
    [name:Class04:private] => Tim
)
Class05 Object
(
    [name:Class05:private] => Burton
)

3-10. satırlardaki sonuçlar, [Class04] ve [Class05] sınıflarının aynı içeriğe sahip olduğunu göstermektedir;

Sonuç

Bir sınıfta [use Trait] komutunun kullanılması, [Trait] kodunu sınıfa eklemekle eşdeğerdir.

9.3. Aynı sınıf öğesini farklı sınıflarda kullanmak

Bir özelliğin ilk avantajı, farklı sınıflar arasında aynı kodun (özellikler + yöntemler) yeniden kullanılması gibi görünüyor. Ancak, basit sınıflar kullanarak da aynı hedefe ulaşılabileceğini göreceğiz.

Sınıflar arasında bir trait nesnesinin paylaşılması, aşağıdaki [trait-02.php] betiği ile gösterilmiştir:


<?php

trait Trait01 {
  // özellik
  private $id = 0;

  // yöntem
  public function doSomething() {
    print "Trait01::doSomething… ($this->id)\n";
  }

}

class Class02 {
  // Trait01'in dahil edilmesi
  use Trait01 {
    // [Trait01::doSomething] yöntemine erişilebilir
    // sınıf içinde [doSomethingInTrait] adıyla
    Trait01::doSomething as doSomethingInTrait;
  }

  // sınıfa özgü yöntem
  public function doSomething(): void {
    // id özniteliği
    $this->id += 10;
    // Trait01 yönteminin kullanımı
    $this->doSomethingInTrait();
    // yerel görüntüleme
    print "Class02->doSomething\n";
  }

}

class Class03 {
  // Trait01'in dahil edilmesi
  use Trait01;

  // sınıfa özgü yerel yöntem
  public function doSomethingElse(): void {
    // id özniteliği
    $this->id += 10;
    // Trait01 yönteminin kullanımı
    $this->doSomething();
    // yerel görüntüleme
    print "Class03->doSomethingElse\n";
  }

}

// test01 ----------------
function test01(): void {
  $class02 = new Class02();
  $class03 = new Class03();
  $class02->doSomething();
  $class03->doSomethingElse();
}

// test01
print "test01-----------------\n";
test01();

Açıklamalar

  • 3-10. satırlar: bir özellik (5. satır) ve bir yöntem (8-10. satırlar) tanımlayan bir özellik.
  • [Trait01] trait'i, [Class02] (satır 14-32) ve [Class03] (satır 34-46) adlı iki sınıfa enjekte edilir.
  • 16-20. satırlar: [Trait01]'in [Class02]'e eklenmesi;
  • 19. satır bir çakışmayı çözmeyi amaçlamaktadır: [Trait01] ve [Class02] sınıflarının her ikisinde de [doSomething] adlı bir yöntem bulunmaktadır. İki durum göz önünde bulundurulmalıdır:
    • [Class02::doSomething] yöntemi sınıfın dışından çağrılır. Bu durumda, [Class02::doSomething] yöntemi, [Trait01::doSomething] yöntemine göre önceliklidir ve çağrılan yöntem budur;
    • [Class02::doSomething] yöntemi sınıfın içinden çağrılır. Bu durumda bir çakışma oluşur: PHP yorumlayıcısı hangi yöntemi çağıracağını bilemez;
  1. satır, [Trait01::doSomething] yöntemini [doSomethingInTrait] olarak yeniden adlandırmaya olanak tanır. Böylece, [Class02]'in içinde şu notasyonlar kullanılacaktır:
  • [doSomethingInTrait], [Trait01::doSomething] yöntemini çağırmak için;
  • [doSomething], [Class02::doSomething] yöntemini çağırmak için;
  • 25. ve 27. satırlar: [Class02] sınıfı, [Trait01] sınıfının özniteliğini ve yöntemini sanki kendisine aitmiş gibi kullanır;
  • 34-48. satırlar: [Class03] sınıfı, [Class02] sınıfıyla aynıdır. [Trait01]'in buraya eklenmesi daha basittir, çünkü [Trait01] ve [Class03] sınıflarının yöntemleri arasında çakışma yoktur;

Sonuçlar

1
2
3
4
5
test01-----------------
Trait01::doSomething… (10)
Class02->doSomething
Trait01::doSomething… (10)
Class03->doSomethingElse

[Trait01] özelliğinin [Class02] ve [Class03] sınıfları arasında paylaşılmadığına dikkat edilmelidir. Böylece[Trait01::i] özniteliği, [Trait01]'in [Class02] ve [Class03] sınıflarına dahil edilmesi sonucu, [Class02::i] ve [Class03::i] adında iki farklı özniteliğe dönüşür. Sonuçların 2. ve 4. satırları bunu göstermektedir. Eğer [Trait01::i] özniteliği [Class02] ve [Class03] sınıfları arasında paylaşılmış olsaydı, 4. satırda 10 yerine 20 değeri olurdu.

[trait-03.php] komut dosyası, özellik yerine bir sınıf kullanarak da aynı sonuca ulaşılabileceğini göstermektedir:


<?php

// özelliğin yerini alan sınıf
class Class01 {
  // özelliği
  private $id = 0;

  // ayarlayıcı
  public function setId(int $id) {
    $this->id = $id;
  }

  // getter
  public function getId(): int {
    return $this->id;
  }

  // yöntem
  public function doSomething(): void {
    print "Class01::doSomething… ($this->id)\n";
  }

}

class Class02 {
  // Class01 dahil etme
  private $class01;

  // ayarlayıcı
  public function setClass01(Class01 $class01) {
    $this->class01 = $class01;
  }

  // sınıfa özgü yöntem
  public function doSomething(): void {
    // Class01 özniteliğini değiştir
    $id = $this->class01->getId();
    $id += 10;
    $this->class01->setId($id);
    // Class01 yöntemini kullanma
    $this->class01->doSomething();
    // yerel görüntüleme
    print "Class02->doSomething\n";
  }

}

class Class03 {
  // Class01'in dahil edilmesi
  private $class01;

  // setter
  public function setClass01(Class01 $class01) {
    $this->class01 = $class01;
  }

  // sınıfa özgü yöntem
  public function doSomethingElse(): void {
    // Class01 özniteliğini değiştirme
    $id = $this->class01->getId();
    $id += 10;
    $this->class01->setId($id);
    // Class01 yönteminin kullanımı
    $this->class01->doSomething();
    // yerel görüntüleme
    print "Class03->doSomethingElse\n";
  }

}

// test01 ----------------
function test01(): void {
  // iki nesne
  $class02 = new Class02();
  $class03 = new Class03();
  // [Class01]'in iki farklı örneğine erişecek
  $class02->setClass01(new Class01());
  $class03->setClass01(new Class01());
  // doğrulama
  $class02->doSomething();
  $class03->doSomethingElse();
}

// test02 ----------------
function test02(): void {
  // [Class01]'in paylaşılan örneği
  $class01 = new Class01();
  // iki nesne
  $class02 = new Class02();
  $class03 = new Class03();
  // aynı [Class01] örneğine erişecek
  $class02->setClass01($class01);
  $class03->setClass01($class01);
  // doğrulama
  $class02->doSomething();
  $class03->doSomethingElse();
}

// test01
print "test01-----------------\n";
test01();
// test02
print "test02-----------------\n";
test02();

Yorumlar

  • 4-23. satırlar: [Class01] sınıfı, [Trait01] sınıfının yerini almıştır. [Trait01] sınıf kodunun, [Class02] ve [Class03] sınıf kodlarına dahil edilmesi gerekirdi. Burada ise durum böyle olmayacaktır. [Class01] sınıf koduna yapılan bir referans, [Class02] ve [Class03] sınıflarına eklenecektir. Bu da, [Class01] sınıfının özniteliklerinin, [Class02] ve [Class03] sınıflarının kodunun dışında kalacağı anlamına gelir. Burada olduğu gibi, [id] özniteliği özel olduğundan (6. satır), bir getter (14-16. satırlar) ve bir setter (9-11. satırlar) tanımlanması gerekir. Bu, trait ile arasındaki ilk farktır: sınıfın özel özniteliklerine erişim kodu oluşturulmalıdır. [id] özniteliğinin görünürlüğünü [public] olarak değiştirebilirdik, ancak bunu yapmak asla tavsiye edilmez. Bir özniteliğin değerini ayarlamak için bir setter kullanmak, değerin geçerliliğini doğrulamaya olanak tanır;
  • 27. satır: [Class02] koduna [Class01] sınıfına bir referans eklenmiştir. Bu öznitelik özel olduğundan, onu başlatmak için bir setter oluşturmamız gerekir (30-32. satırlar);
  • satır 37-41: [Class01] kodunun her türlü kullanımı için [$this→class01] özniteliği üzerinden geçmemiz gerekir;
  • satır 48-69: [Class03] sınıfı, [Classe02] sınıfının bir klonudur; tek farkı, yönteminin adının farklı olmasıdır;
  • satır 72-82: 1. test. Bu test, [Class02] ve [Class03] sınıflarına [Class01] sınıfının iki farklı örneğini enjekte etmeyi içerir (satır 77 ve 78);
  • satır 85-97: 2. test, [Class02] ve [Class03] sınıflarına [Class01] sınıfının aynı örneğini enjekte eder (satır 97, 92, 93);
  • satır 100-101: [test01] testinin çalıştırılması;
  • satır 103-104: [test02] testinin yürütülmesi;

Sonuçlar

test01-----------------
Class01::doSomething… (10)
Class02->doSomething
Class01::doSomething… (10)
Class03->doSomethingElse
test02-----------------
Class01::doSomething… (10)
Class02->doSomething
Class01::doSomething… (20)
Class03->doSomethingElse

Sonuçlara ilişkin açıklamalar

  • 2-5. satırlar: [Trait01] çizgisiyle elde edilen sonuçlarla aynı sonuçlar elde edilir. Buradan, bu durumda trait kullanımının zorunlu olmadığı, ancak trait'in özniteliklerine erişmek için yöntemlere gerek kalmaması nedeniyle kodun kısaldığı sonucuna varılabilir: bu öznitelikler, trait'in dahil edildiği kodun ayrılmaz bir parçasıdır;
  • 7-10. satırlar: [Class01] referansının hem [Class02] hem de [Class03] sınıflarına enjekte edilmiş olması nedeniyle, [Class01::id] özniteliği iki sınıf arasında paylaşılmıştır. Bu nedenle sonuçların 9. satırında, özellik kullanıldığında 10 yerine 20 değeri görüntülenmektedir. Buradan şu sonuç çıkarılabilir: Eğer özelliğin öznitelikleri sınıflar arasında paylaşılmak zorundaysa, o zaman özellik kullanılamaz ve bunun yerine bir sınıf kullanılmalıdır;

9.4. Yöntemleri bir trait içinde gruplandırma

Önceki örnekte, trait öznitelikler ve yöntemler içeriyordu. Burada ise yalnızca yöntemler içeren durumu ele alacağız. Bu durumda trait, farklı sınıflarda kullanılabilen bir yöntem faktörleştirmesine benzer. Trait burada öznitelik içermediğinden, bir araya getirdiği yöntemlerin yalnızca kendilerine aktarılan parametreler üzerinde çalıştığı durumu ele alacağız. Aslında bu zorunlu değildir: bir trait, [$this→attribut1] özniteliği olmadan da bu öznitelik üzerinde işlem yapabilir. Bu durumda, [$this→attribut1] özniteliğini sağlamak, bu trait’i kullanan sınıfların görevidir.

Özelliğin yalnızca kendisine aktarılan parametreler üzerinde çalışan yöntemlere sahip olması durumunda, bu özelliğin, kendisiyle aynı yöntemlere sahip ve statik olarak tanımlanmış bir sınıfla değiştirilebileceğini göstereceğiz.

Trait’in kullanımı, önceki örnekteki trait’i tüm özniteliklerinden arındırarak kullanan [trait-04.php] komut dosyası ile gösterilmektedir:


<?php

trait Trait01 {

  // paylaşılacak yöntem
  public function doSomething() {
    print "Trait01::doSomething ….\n";
  }

}

class Class02 {
  // Trait01'in dahil edilmesi
  use Trait01 {
    // [Trait01::doSomething] yöntemi erişilebilir
    // sınıf içinde [doSomethingInTrait] adıyla
    Trait01::doSomething as doSomethingInTrait;
  }

  public function doSomething(): void {
    // Trait01 yöntemini çağırıyor
    $this->doSomethingInTrait();
    // yerel görüntüleme
    print "Class02->doSomething\n";
  }

}

class Class03 {
  // Trait01'in dahil edilmesi
  use Trait01;

  // sınıfa ait yerel yöntem
  public function doSomethingElse(): void {
    // Trait01 yöntemini çağırma
    $this->doSomething();
    // yerel görüntüleme
    print "Class03->doSomethingElse\n";
  }

}

// test ----------------
(new Class02())->doSomething();
(new Class03())->doSomethingElse();

Açıklamalar

  • 3-10. satırlar: [Trait01] trait’inde artık öznitelik yoktur;
  • satır 14-18: [Trait01], [Class02] içine dahil edilmiştir. 22. satırda [Trait01] yöntemi kullanılmaktadır;
  • satır 31: [Trait01]'in [Class03]'e dahil edilmesi. [Trait01]'in yöntemi satır 36'da kullanılmaktadır;

Sonuçlar

1
2
3
4
Trait01::doSomething ….
Class02->doSomething
Trait01::doSomething ….
Class03->doSomethingElse

Bu kullanım örneğinde, [Trait01] özelliği kolaylıkla bir sınıfla değiştirilebilir. Bu, aşağıdaki [trait-05.php] komut dosyasında gösterilmiştir:


<?php

abstract class Class01 {

  // paylaşılacak statik yöntem
  public static function doSomething() {
    print "Class01::doSomething ….\n";
  }

}

class Class02 {

  public function doSomething(): void {
    // Class01 yöntemini çağırma
    Class01::doSomething();
    // yerel görüntüleme
    print "Class02->doSomething\n";
  }

}

class Class03 {

  // sınıfa özgü yerel yöntem
  public function doSomethingElse(): void {
    // Class01'den yöntem çağrısı
    Class01::doSomething();
    // yerel görüntüleme
    print "Class03->doSomethingElse\n";
  }

}

// test ----------------
(new Class02())->doSomething();
(new Class03())->doSomethingElse();

Açıklamalar

  • 3-10. satırlar: [Trait01] özelliği, tüm yöntemleri statik olarak tanımlanmış [Class01] soyut sınıfıyla değiştirilir. Sınıf, yalnızca örneklenmesini engellemek amacıyla soyut olarak tanımlanmıştır. Türetilmesini engellemek için [final] yazmak da istenirdi, ancak PHP 7, bir sınıf için [final abstract] önekini kabul etmez. Ya biri ya da diğeri, ikisi birden olamaz;
  • 16. satır: [$this→doSomethingInTrait] yazmak yerine artık [Class01::doSomething] yazıyoruz, yani [Class01] sınıfının [doSomething] statik yöntemini çağırıyoruz;
  • 28. satır: Aynı işlemi [Class03]'te de tekrarlıyoruz;

Sonuçlar

1
2
3
4
Class01::doSomething ….
Class02->doSomething
Class01::doSomething ….
Class03->doSomethingElse

[Trait01] özelliğiyle elde edilen sonuçla tamamen aynı sonuç elde edildi; bu da bu özelliğin kullanımından kaçınılabileceğini göstermektedir. Bir özelliğin yöntemlerinin, o özelliğin kendisinde bu özniteliği barındırmasa bile [$this→attribut1] özniteliği üzerinde çalışabileceğini belirtmiştik. Bu durumda, bu özelliği kullanan sınıfların [$this→attribut1] özniteliğini sağlaması gerekir. Bu, alışılmadık bir durumdur: O halde, özelliği kullanan sınıfların sahip olması gereken [$this→attribut1] özniteliğini, özelliğin kendisine “yükseltmek” daha mantıklıdır. Böylece bu öznitelik, özelliği kullanan sınıfın öznitelikleri arasında mutlaka yer alacaktır.

9.5. Bir özellik ile çoklu miras

PHP literatüründe, trait'in çoklu miras almayı, yani bir sınıfın birden fazla sınıftan miras alabilmesini sağladığı sıkça belirtilir. C++ dili bu imkânı sunar, ancak yalnızca tekli miras almayı tanıyan Java veya C# dillerinde bu imkân yoktur. Bir türetilmiş sınıfta trait kullanımı, çoklu mirasa benzeyen bir şeyin uygulanmasına olanak sağlasa da, bu kullanım örneğinin yine de basit sınıflarla uygulanabileceğini göstereceğiz.

[trait-06.php] komut dosyası, bir türetilmiş sınıf ve bir özellik kullanır:


<?php

trait Trait01 {
  // özellik
  private $i;

  // paylaşılacak yöntem
  public function doSomethingInTrait01() {
    // Trait01::$i'in değiştirilmesi
    $this->i++;
    // görüntüleme
    print "Trait01::doSomethingInTrait01… i=$this->i\n";
  }

}

class Class02 {
  // özellik
  protected $j = 0;

  // yöntem
  public function doSomethingInClass02(): void {
    // Class02::j'yi değiştirme
    $this->j += 10;
    // görüntüleme
    print "Class02->doSomethingInClass02… j=$this->j\n";
  }

}

// türetilmiş sınıf
class Class03 extends Class02 {
  // Class02:j ve Trait01::i'den miras alır
  // Trait01 dahil etme
  use Trait01;

  // yöntem
  public function doSomethingInClass03(): void {
    // Trait01 yöntemini kullanma
    $this->doSomethingInTrait01();
    // Trait01::i'nin değiştirilmesi
    $this->i += 100;
    // Class03::j'nin değiştirilmesi (==Class02::j)
    $this->j += 1000;
    // görüntüleme
    print "Class03->doSomethingInClass03… i=$this->i, j=$this->j\n";
  }

}

// test ----------------
(new Class02())->doSomethingInClass02();
(new Class03())->doSomethingInClass03();

Açıklamalar

  • 3-15. satırlar: Bir öznitelik ve bu özniteliği işleyen bir yöntem içeren [Trait01] trait’ine geri dönülür;
  • 17-29. satırlar: [Class02] sınıfı, [Trait01] özelliğiyle hiçbir ilgisi yoktur. Bu sınıf, söz konusu özelliği kullanmaz;
  • 19. satır: [Class02] sınıfının tek özniteliği, türetilmiş sınıflardan erişilebilir olması için [protected] görünürlük düzeyiyle tanımlanmıştır;
  • 32. satır: [Class03] sınıfı, [Class02] sınıfını genişletir. Ayrıca, [Trait01] özelliğini de içerir (35. satır). Son olarak, [Class02] sınıfının özniteliklerini ve yöntemlerini miras alır ve [Trait01] sınıfının özniteliklerini ve yöntemlerini de içerir. Dolayısıyla, burada çoklu miras ile benzer bir durum söz konusudur;

Sonuçlar

1
2
3
Class02->doSomethingInClass02… j=10
Trait01::doSomethingInTrait01… i=1
Class03->doSomethingInClass03… i=101, j=1000

Önceki bir örnekte yapıldığı gibi, şunu göstereceğiz:

  • özelliğin bir sınıfla değiştirilebileceğini;
  • türetilen sınıfa trait'i dahil etmek yerine, sınıfın bir örneğine yapılan referansı dahil ediyoruz;

[trait-07.php] komut dosyası şöyledir:


<?php

class Class01 {
  // özellik
  protected $i;

  // getter ve setter
  public function getI(): int {
    return $this->i;
  }

  public function setI(int $i): void {
    $this->i = $i;
  }

  // yöntem
  public function doSomethingInClass01(): void {
    // değişiklik Class01::$i
    $this->i++;
    // görüntüleme
    print "Class01::doSomething in Class01… i=$this->i\n";
  }

}

class Class02 {
  // özellik
  protected $j = 0;

  // sınıfa özgü yöntem
  public function doSomethingInClass02(): void {
    // Class02::j'nin değiştirilmesi
    $this->j += 10;
    // görüntüleme
    print "Class02->doSomethingInClass02… j=$this->j\n";
  }

}

class Class03 extends Class02 {
  // Class01 dahil etme
  private $class01;

  // ayarlayıcı
  public function setClass01(Class01 $class01) {
    $this->class01 = $class01;
  }

  // sınıfa özgü yerel yöntem
  public function doSomethingInClass03(): void {
    // Class01 yönteminin kullanımı
    $this->class01->doSomethingInClass01();
    // Class01::i'nin değiştirilmesi
    $i = $this->class01->getI();
    $i += 100;
    $this->class01->setI($i);
    // Class03::j'nin değiştirilmesi
    $this->j += 1000;
    // görüntüleme
    print "Class03->doSomethingInClass03… i=$i, j=$this->j\n";
  }

}

// test ----------------
$class01 = new Class01();
$class02 = new Class02();
$class03 = new Class03();
$class03->setClass01($class01);
$class02->doSomethingInClass02();
$class03->doSomethingInClass03();

Açıklamalar

  • 3-24. satırlar: [Class01] sınıfı, [Trait01] özelliğinin yerini alır. [Class01] sınıfı bir referans yoluyla sınıflara dahil edileceğinden, [$i] özniteliği için get / set yöntemleri eklenmiştir;
  • 26-38. satırlar: [Classe02] sınıfında değişiklik yapılmamıştır;
  • 40. satır: [Class03] sınıfı, [Classe02] sınıfını genişletir;
  • satır 42: [Classe01] sınıfı, bir referans aracılığıyla [Classe03] sınıfına dahil edilir;
  • satır 45-47: [Classe01] sınıfına yapılan referansı başlatmak için setter sınıfı tanımlanmıştır;
  • satır 50-61: [doSomethingInClass03] yöntemi, daha önce yapılanla aynı işlevi görür, ancak kodu daha karmaşıktır;

Sonuçlar

1
2
3
Class02->doSomethingInClass02… j=10
Class01::doSomething in Class01… i=1
Class03->doSomethingInClass03… i=101, j=1000

Bu örnekten, burada da trait'in zorunlu olmadığı sonucuna varılabilir, ancak türetilmiş sınıfta daha kısa kod yazılmasını sağladığı da kabul edilmelidir.

9.6. Soyut sınıf yerine trait kullanmak

Sıkça şu kullanım senaryosuyla karşılaşılır: Birden fazla uygulamaya yol açabilecek oldukça genel bir I arayüzü oluşturulur. Bu uygulamalar ortak bir kodu paylaşır, ancak diğer yöntemler açısından farklılık gösterir. Bu kullanım senaryosunu iki şekilde uygulayabiliriz:

  1. Türetilmiş sınıflarda ortak olan kodu bir araya getiren C adlı bir soyut sınıf oluşturulur. C sınıfı, I arayüzünü uygular; ancak türetilmiş sınıflarda tanımlanması gereken bazı yöntemler, C sınıfında soyut olarak tanımlanır ve bu nedenle C sınıfı da soyut bir sınıftır. Ardından, C sınıfından türetilen C1 ve C2 sınıfları oluşturulur; bu sınıfların her biri, üst sınıfları olan C'nin tanımlanmamış (soyut) yöntemlerini kendi yöntemleriyle uygular;
  2. önceki çözümdeki C soyut sınıfıyla neredeyse aynı olan T özelliği oluşturulur. Bu özellik, sözdizimsel olarak bunu yapamadığı için I arayüzünü uygulamaz. Ardından, I arayüzünü uygulayan ve T özelliğini kullanan C1 ve C2 sınıfları oluşturulur. Bu sınıfların yapması gereken tek şey, içe aktardıkları T özelliği tarafından uygulanmayan I arayüzünün yöntemlerini uygulamaktır;

İşte bu iki çözümün ne kadar birbirine yakın olduğunu gösteren bir örnek.

Uygulama 1, daha önce açıklanan Çözüm 1’i ([trait-08.php]) uygular:


<?php

interface Interface1 {

  public function doSomething(): void;

  public function doSomethingElse(): void;
}

abstract class AbstractClass implements Interface1 {
  // özellikler
  protected $attr1 = 11;
  protected $attr2 = 12;

  // alıcı ve ayarlayıcılar
  public function getAttr1() {
    return $this->attr1;
  }

  public function getAttr2() {
    return $this->attr2;
  }

  public function setAttr1($attr1) {
    $this->attr1 = $attr1;
    return $this;
  }

  public function setAttr2($attr2) {
    $this->attr2 = $attr2;
    return $this;
  }

  // uygulanmış yöntem
  public function doSomething(): void {
    print "AbstractClass::doSomething [$this->attr1,$this->attr2]\n";
  }

  // uygulanmamış yöntem
  abstract public function doSomethingElse(): void;
}

// türetilmiş sınıf 1
class Class1 extends AbstractClass {
  // özellik
  private $attr3 = 13;

  // getter ve setter
  public function getAttr3() {
    return $this->attr3;
  }

  public function setAttr3($attr3) {
    $this->attr3 = $attr3;
    return $this;
  }

  // uygulama doSomethingElse
  public function doSomethingElse(): void {
    print "Class1::doSomethingElse [$this->attr1,$this->attr2,$this->attr3]\n";
  }

}

// türetilmiş sınıf 2
class Class2 extends AbstractClass {
  // özellik
  private $attr4 = 14;

  public function getAttr4() {
    return $this->attr4;
  }

  public function setAttr4($attr4) {
    $this->attr4 = $attr4;
    return $this;
  }

  // uygulama doSomethingElse
  public function doSomethingElse(): void {
    print "Class2::doSomethingElse [$this->attr1,$this->attr2,$this->attr4]\n";
  }

}

// harici işlev
function useInterfaceWith(Interface1 $interface):void{
  $interface->doSomething();
  $interface->doSomethingElse();
}

// testler
useInterfaceWith(new Class1());
useInterfaceWith(new Class2());

Açıklamalar

  • 3-8. satırlar: [Interface1] arayüzünün iki yöntemi vardır;
  • 10-41. satırlar: [AbstractClass] soyut sınıfı, [Interface1] arayüzünü uygular (10. satır). Bu sınıf, getter ve setter yöntemlerine sahip iki özniteliğe sahiptir (satır 12-32) ve [Interface1] arayüzünün [doSomething] yöntemini uygular (satır 35-37) ancak [doSomethingElse] yöntemini uygulayamaz. Bu nedenle bu yöntem soyut olarak tanımlanmıştır (satır 40). [AbstractClass] soyut sınıfı örneklenemez ve bir amaca hizmet edebilmesi için mutlaka bir alt sınıfın türetilmesi gerekir;
  • satır 44-63: Class1 sınıfı, [AbstractClass] soyut sınıfını genişletir ve dolayısıyla [Interface1] arayüzünü uygular (satır 14). Bu sınıf, üst sınıfının tanımlamadığı [doSomethingElse] yöntemine bir gövde ekler (satır 59-61). Ayrıca, üst sınıfının özniteliklerine bir öznitelik daha ekler (satır 46-56);
  • satır 66-82: Class2 sınıfı, [AbstractClass] soyut sınıfını genişletir ve dolayısıyla [Interface1] arayüzünü uygular (satır 66). Üst sınıfının tanımlamadığı [doSomethingElse] yöntemine bir gövde ekler (satır 80-82). Ayrıca üst sınıfının özniteliklerine bir öznitelik daha ekler (satır 68-77);
  • satır 87-90: [useInterfaceWith] işlevi, parametre olarak bir [Interface1] türünü alır ve bu arayüzün iki yöntemini çağırır;
  • satır 93-94: [useInterfaceWith] işlevi ilk kez [Class1] türüyle, ikinci kez ise [Class2] türüyle çağrılır. Bu doğrudur, çünkü bu iki tür de [Interface1] arayüzünü uygulamaktadır;

Sonuçlar


AbstractClass::doSomething [11,12]
Class1::doSomethingElse [11,12,13]
AbstractClass::doSomething [11,12]
Class2::doSomethingElse [11,12,14]

Şimdi [trait-09.php] komut dosyasıyla 2. çözümü uyguluyoruz. Bu, soyut sınıfı bir trait ile değiştirmekten ibarettir:


<?php

interface Interface1 {

  public function doSomething(): void;

  public function doSomethingElse(): void;
}

trait Trait1 {
  // özellikler
  private $attr1 = 11;
  private $attr2 = 12;

  // alıcı ve ayarlayıcılar
  public function getAttr1() {
    return $this->attr1;
  }

  public function getAttr2() {
    return $this->attr2;
  }

  public function setAttr1($attr1) {
    $this->attr1 = $attr1;
    return $this;
  }

  public function setAttr2($attr2) {
    $this->attr2 = $attr2;
    return $this;
  }

  // uygulanmış yöntem
  public function doSomething(): void {
    print "Trait::doSomething [$this->attr1,$this->attr2]\n";
  }
}

// türetilmiş sınıf 1
class Class1 implements Interface1 {
  // özelliğin kullanımı
  use Trait1;
  // özellik
  private $attr3 = 13;

  // getter ve setter
  public function getAttr3() {
    return $this->attr3;
  }

  public function setAttr3($attr3) {
    $this->attr3 = $attr3;
    return $this;
  }

  // uygulama doSomethingElse
  public function doSomethingElse(): void {
    print "Class1::doSomethingElse [$this->attr1,$this->attr2,$this->attr3]\n";
  }

}

// türetilmiş sınıf 2
class Class2 implements Interface1 {
  // özelliğin kullanımı
  use Trait1;
  // özellik
  private $attr4 = 14;

  public function getAttr4() {
    return $this->attr4;
  }

  public function setAttr4($attr4) {
    $this->attr4 = $attr4;
    return $this;
  }

  // doSomethingElse uygulaması
  public function doSomethingElse(): void {
    print "Class2::doSomethingElse [$this->attr1,$this->attr2,$this->attr4]\n";
  }

}

// arayüzü kullanan harici işlev
function useInterfaceWith(Interface1 $interface): void {
  $interface->doSomething();
  $interface->doSomethingElse();
}

// testler
useInterfaceWith(new Class1());
useInterfaceWith(new Class2());

Açıklamalar

  • 3-8. satırlar: [Interface1] arayüzünde değişiklik yapılmamıştır;
  • 10-41. satırlar: [Trait1] özelliği, Çözüm 1'deki [AbstractClass] soyut sınıfının yerini almaktadır. Kod, aşağıdaki ayrıntılar dışında aynıdır:
    • 10. satır: [Trait1] özelliği, [Interface1] arayüzünü uygulamamaktadır. Bu, sözdizimi açısından imkansızdır;
    • 12-13. satırlar: [AbstractClass] soyut sınıfının özniteliklerinden [protected] görünürlük özniteliği burada [private] olarak değiştirilmiştir. Bu iki öznitelik, türetilmiş sınıflara, getter ve setter'lardan geçmek zorunda kalmadan üst sınıfın (protected) veya özelliğin (private) özniteliklerine doğrudan erişim sağlamayı amaçlamaktadır;
    • [Trait1] özelliği, [doSomethingElse] soyut yöntemini tanımlamamaktadır;
  • 41-62. satırlar: Çözüm 2'deki [Class1] sınıfı, aşağıdaki ayrıntılar dışında Çözüm 1'deki [Class1] sınıfıyla aynıdır:
    • 41. satır: [Class1] sınıfı, [Interface1] arayüzünü uygularken, Çözüm 1'de ise [AbstractClass] soyut sınıfını genişletiyordu;
    • 43. satır: arayüzün bir kısmını uygulamak için [Trait1] özelliğini kullanır;
  • 65-85. satırlar: [Class1] için yapılan yorumların aynısı burada da geçerlidir;
  • 87-95. satırlar: kodun geri kalanında değişiklik yok;

Sonuçlar


Trait::doSomething [11,12]
Class1::doSomethingElse [11,12,13]
Trait::doSomething [11,12]
Class2::doSomethingElse [11,12,14]

Gerçekten de aynı sonuçlar elde ediliyor.

9.7. Conclusion

Yukarıdaki örneklerden anlaşıldığı üzere, çizgi kullanımının net bir avantaj sağlayacağı kullanım durumları net değildir. Örneklerimizde, çizgi yerine bir sınıf kullanarak her zaman onsuz da idare edilebilir. Bununla birlikte, bu kullanımın, sanki bu kod bir üst sınıfa aitmiş gibi, farklı türetilmiş sınıflar arasında kodu faktörlere ayırmak için pratik olduğu görülmektedir. Aşağıdaki örnekte de bunu yapacağız.