Özellikler ve arayüzler


346

Son zamanlarda PHP üzerinde çalışmaya çalışıyordum ve kendimi özelliklere asıldığımı görüyorum. Yatay kodun yeniden kullanılması kavramını ve soyut bir sınıftan mutlaka miras almak istemediğini anlıyorum. Anlamadığım şey: Özellikleri ve arayüzleri kullanma arasındaki önemli fark nedir?

Ne zaman birini ya da diğerini kullanacağınızı açıklayan iyi bir blog yazısı ya da makale aramayı denedim, ancak şimdiye kadar bulduğum örnekler çok benzer görünüyor.


6
arabirimde işlev gövdelerinde kod yoktur. aslında herhangi bir işlev gövdesi yoktur.
hakre

2
Çok beğenilmiş cevabım olmasına rağmen, kayıt için genellikle anti-trait / mixin olduğumu belirtmek istiyorum . Özelliklerin genellikle katı OOP uygulamalarını nasıl zayıflattığını okumak için bu sohbet dökümünü kontrol edin .
rdlowrey

2
Aksine tartışırım. PHP ile yıllar önce çalışmış ve özelliklerin ortaya çıkmasından bu yana, değerlerini kanıtlamanın kolay olduğunu düşünüyorum. 'Görüntü modellerinin' nesneler gibi yürümesini ve konuşmasını sağlayan bu pratik örneği okuyun Imagick, özelliklerden önceki eski günlerde gerekli olan tüm şişkinlik daha az.
quickshiftin

Özellikler ve arayüz benzerdir. Temel fark, Özelliklerin yöntemleri uygulamanıza izin vermesidir, Arayüz yapmaz.
John

Yanıtlar:


239

Bir arabirim, uygulayıcı sınıfın uygulamak zorunda olduğu bir dizi yöntemi tanımlar .

Bir özellik use' olduğunda , yöntemlerin uygulamaları da ortaya çıkar - ki bu bir Interface.

En büyük fark bu.

Gönderen PHP RFC için Yatay Yeniden :

Özellikler, PHP gibi tek miras dillerinde kodun yeniden kullanılması için bir mekanizmadır. Bir Özelliğin, bir geliştiricinin farklı sınıf hiyerarşilerinde yaşayan birkaç bağımsız sınıfta yöntem kümelerini serbestçe yeniden kullanmasını sağlayarak tek mirasın bazı sınırlamalarını azaltması amaçlanır.


2
@JREAM Uygulamada hiçbir şey. Gerçekte, çok daha fazlası.
Alec Gorge

79
Dışındaki özellikler hiçbir şekilde arayüz değildir. Arayüzler, kontrol edilebilecek özelliklerdir. Özellikler kontrol edilemez, bu nedenle sadece uygulama içindir. Bunlar arayüzlerin tam tersidir.
RFC'deki

196
Özellikler aslında dil destekli kopyalama ve yapıştırmadır .
Şahid

10
Bu bir metafor değil. Bu bir kelimenin anlamını kastediyor. Bir kutuyu hacimli bir yüzey olarak tanımlamak gibidir.
cleong

6
İrcmaxell ve Shadi'nin yorumlarını genişletmek için: Bir nesnenin bir arabirim (instanceof aracılığıyla) uygulayıp uygulamadığını kontrol edebilir ve bir yöntem bağımsız değişkeninin bir arabirim yöntem imzasında bir tür ipucu ile uyguladığından emin olabilirsiniz. Özellikler için ilgili kontrolleri gerçekleştiremezsiniz.
Brian D'Astous

531

Kamu Hizmeti Duyurusu:

Ben özellikleri için hemen hemen her zaman bir kod kokusu olduğunu ve kompozisyon lehine kaçınılması gerektiğini düşünüyorum kayıt için belirtmek istiyorum. Benim düşünceme göre, tek bir kalıtımın bir anti-desen olma noktasına kadar kötüye kullanıldığı ve çoklu kalıtımın sadece bu sorunu birleştirdiğini düşünüyorum. Çoğu durumda miras yerine kompozisyonu tercih ederek çok daha iyi hizmet edersiniz (tek veya çoklu olsun). Hala özellikler ve arayüzlerle ilişkileri ile ilgileniyorsanız, okumaya devam edin ...


Bunu söyleyerek başlayalım:

Nesneye Dayalı Programlama (OOP) kavramak zor bir paradigma olabilir. Sınıfları kullanmanız, kodunuzun Nesneye Dayalı (OO) olduğu anlamına gelmez.

OO kodu yazmak için, OOP'nin gerçekten nesnelerinizin yetenekleri hakkında olduğunu anlamanız gerekir. Sınıfları, gerçekte yaptıkları yerine yapabilecekleri açısından düşünmelisiniz . Bu, biraz kod "bir şey yap" yapmaya odaklanılan geleneksel prosedürel programlamanın tam tersidir.

OOP kodu planlama ve tasarımla ilgiliyse, bir arayüz mavikopyadır ve bir nesne tamamen inşa edilmiş evdir. Bu arada, özellikler, planın (arayüz) ortaya koyduğu evi inşa etmeye yardımcı olmanın bir yoludur.

Arayüzler

Peki, neden arayüzler kullanmalıyız? Oldukça basit, arayüzler kodumuzu daha az kırılgan hale getirir. Bu ifadeden şüpheleniyorsanız, arabirimlere karşı yazılmamış olan eski kodu korumak zorunda kalan herkesten isteyin.

Arayüz, programcı ve kodu arasında yapılan bir sözleşmedir. Arayüz, "Kurallara göre oynadığınız sürece beni istediğiniz gibi uygulayabilirsiniz ve söz veriyorum diğer kodunuzu kırmayacağım."

Bu nedenle, örnek olarak, gerçek dünya senaryosunu düşünün (araba veya widget yok):

Bir web uygulamasının sunucu yükünü azaltmak için bir önbellek sistemi uygulamak istiyorsunuz

APC kullanarak istek yanıtlarını önbelleğe almak için bir sınıf yazarak işe başlarsınız:

class ApcCacher
{
  public function fetch($key) {
    return apc_fetch($key);
  }
  public function store($key, $data) {
    return apc_store($key, $data);
  }
  public function delete($key) {
    return apc_delete($key);
  }
}

Ardından, HTTP yanıt nesnenizde, gerçek yanıtı oluşturmak için tüm işleri yapmadan önce bir önbellek isabetini kontrol edersiniz:

class Controller
{
  protected $req;
  protected $resp;
  protected $cacher;

  public function __construct(Request $req, Response $resp, ApcCacher $cacher=NULL) {
    $this->req    = $req;
    $this->resp   = $resp;
    $this->cacher = $cacher;

    $this->buildResponse();
  }

  public function buildResponse() {
    if (NULL !== $this->cacher && $response = $this->cacher->fetch($this->req->uri()) {
      $this->resp = $response;
    } else {
      // Build the response manually
    }
  }

  public function getResponse() {
    return $this->resp;
  }
}

Bu yaklaşım harika çalışıyor. Ancak birkaç hafta sonra APC yerine dosya tabanlı bir önbellek sistemi kullanmaya karar verdiniz. Artık denetleyicinizin kodunu değiştirmeniz gerekir, çünkü denetleyicinizi ApcCachersınıfın yeteneklerini ifade eden bir arabirim yerine sınıfın işlevselliğiyle çalışacak şekilde programladınız ApcCacher. Diyelim ki yukarıdakilerin yerine Controllersınıfı böyle bir CacherInterfacebeton yerine bağımlı hale getirdiniz ApcCacher:

// Your controller's constructor using the interface as a dependency
public function __construct(Request $req, Response $resp, CacherInterface $cacher=NULL)

Bununla devam etmek için arayüzünüzü şöyle tanımlarsınız:

interface CacherInterface
{
  public function fetch($key);
  public function store($key, $data);
  public function delete($key);
}

Buna karşılık hem sizin hem de ApcCacheryeni FileCachersınıflarınız uygular CacherInterfaceve Controllersınıfınızı arabirimin gerektirdiği yetenekleri kullanacak şekilde programlarsınız .

Bu örnek (umarım), bir arayüze programlamanın, değişikliklerin diğer kodunuzu kıracağı konusunda endişelenmeden sınıflarınızın dahili uygulamasını nasıl değiştirebildiğinizi gösterir.

özellikleri

Diğer yandan, özellikler basitçe kodu yeniden kullanmak için bir yöntemdir. Arayüzler, özelliklere karşılıklı olarak münhasır bir alternatif olarak düşünülmemelidir. Aslında, bir arayüzün gerektirdiği yetenekleri yerine getiren özellikler oluşturmak ideal kullanım örneğidir .

Yalnızca birden fazla sınıf aynı işlevselliği paylaştığında (muhtemelen aynı arabirim tarafından dikte edilen) özellikleri kullanmalısınız. Tek bir sınıf için işlevsellik sağlamak için bir özellik kullanmanın bir anlamı yoktur: bu sadece sınıfın yaptıklarını gizler ve daha iyi bir tasarım özelliğin işlevselliğini ilgili sınıfa taşır.

Aşağıdaki özellik uygulamasını düşünün:

interface Person
{
    public function greet();
    public function eat($food);
}

trait EatingTrait
{
    public function eat($food)
    {
        $this->putInMouth($food);
    }

    private function putInMouth($food)
    {
        // Digest delicious food
    }
}

class NicePerson implements Person
{
    use EatingTrait;

    public function greet()
    {
        echo 'Good day, good sir!';
    }
}

class MeanPerson implements Person
{
    use EatingTrait;

    public function greet()
    {
        echo 'Your mother was a hamster!';
    }
}

Daha somut bir örnek: hayal hem senin FileCacherve seninApcCacher arayüz tartışma kullanımından aynı yöntem bir önbellek girdisi bayat ve silinmesi gerektiğini belirlemek için (tabii ki bu gerçek hayatta durum böyle değil ama onunla gitmek). Bir özellik yazabilir ve her iki sınıfın da ortak arabirim gereksinimi için kullanmasına izin verebilirsiniz.

Son bir uyarı kelimesi: niteliklerle denize düşmemeye dikkat edin. Genellikle, benzersiz sınıf uygulamaları yeterli olduğunda, özellikler zayıf tasarım için bir koltuk değneği olarak kullanılır. En iyi kod tasarımı için özellikleri arayüz gereksinimlerini karşılamakla sınırlamalısınız.


69
Gerçekten yukarıda verilen hızlı basit cevabı arıyordum, ama ayrımı başkaları için daha net hale getirecek mükemmel bir derin cevap verdiğinizi söylemeliyim, kudos.
datguywhowanders

35
"[C] belirli bir sınıftaki bir arayüzün gerektirdiği yetenekleri yerine getiren özellikleri ideal bir kullanım durumudur". Tam olarak: +1
Alec Gorge

5
PHP'deki özelliklerin diğer dillerdeki karışımlara benzediğini söylemek adil olur mu?
Eno

5
@igorpan tüm bakılırsa bakılsın PHP'nin özellik uygulama söyleyebilirim olduğu çoklu miras ile aynı. PHP'de bir özellik statik özellikler belirtiyorsa, özelliği kullanan her sınıfın kendi statik özelliğinin bir kopyası olacağını belirtmek gerekir. Daha da önemlisi ... özellikleri sorgularken bu yazının SERP'lerde nasıl son derece yüksek olduğunu görmek için sayfanın üstüne bir kamu hizmeti duyurusu ekleyeceğim. Okumalısın.
rdlowrey

3
Ayrıntılı açıklama için +1. Mixins bir LOT kullanılan yakut bir arka plan geliyor; sadece benim iki sent eklemek için, kullandığımız iyi bir başparmak kuralı php içinde "özellikleri bu $ mutasyon yöntemleri uygulama" olarak çevrilebilir. Bu, bir sürü çılgın hata ayıklama oturumunu önler ... Bir mixin, karıştırılacağı sınıf hakkında herhangi bir varsayım yapmamalıdır (ya da çok açık hale getirmeli ve bağımlılıkları çıplak bir minimum seviyeye indirmelisiniz). Bu bağlamda, arayüzleri uygulayan özellikler fikrinizi güzel buluyorum.
m_x

67

A traitaslında PHP'nin a uygulamasıdır mixinve etkili bir şekilde eklenmesi yoluyla herhangi bir sınıfa eklenebilen bir dizi uzantı yöntemidir trait. Daha sonra yöntemler miras kullanmadan o sınıfın uygulanmasının bir parçası haline gelir .

Gönderen PHP Manual (vurgu benim):

Özellikler, PHP gibi tek miras dillerinde kodun yeniden kullanılması için bir mekanizmadır . ... Geleneksel mirasa bir ektir ve yatay davranış kompozisyonunu mümkün kılar; yani, mirasa gerek kalmadan sınıf üyelerinin uygulanması.

Bir örnek:

trait myTrait {
    function foo() { return "Foo!"; }
    function bar() { return "Bar!"; }
}

Yukarıdaki özellik tanımlandığında, şimdi aşağıdakileri yapabilirim:

class MyClass extends SomeBaseClass {
    use myTrait; // Inclusion of the trait myTrait
}

Ben sınıfının bir örneğini oluştururken bu noktada MyClass, adı iki yöntem vardır foo()ve bar()gelen - myTrait. Ve - traittanımlanmış yöntemlerin zaten bir yöntem gövdesine sahip olduğuna dikkat edin -Interface -defined yönteminin yapamadığı.

Ek olarak - PHP, diğer birçok dil gibi, tek bir miras modeli kullanır - yani bir sınıf birden çok arabirimden türetilebilir, ancak birden çok sınıftan türeyemez. Ancak, bir PHP sınıfı olabilir birden sahip traitbirden çok temel sınıfları da dahil olmak üzere eğer olabilir gibi - yeniden kullanılabilir parçaları içerecek şekilde programcı verir - kapanım.

Unutulmaması gereken birkaç nokta:

                      -----------------------------------------------
                      |   Interface   |  Base Class   |    Trait    |
                      ===============================================
> 1 per class         |      Yes      |       No      |     Yes     |
---------------------------------------------------------------------
Define Method Body    |      No       |       Yes     |     Yes     |
---------------------------------------------------------------------
Polymorphism          |      Yes      |       Yes     |     No      |
---------------------------------------------------------------------

Polimorfizm:

Önceki örnekte, nerede MyClass uzanır SomeBaseClass , MyClass olduğu örneğidir SomeBaseClass. Başka bir deyişle, gibi bir dizi SomeBaseClass[] basesörnekleri içerebilir MyClass. Benzer şekilde, MyClassuzatılmış IBaseInterface, bir dizi IBaseInterface[] basesörneklerini içerebilir MyClass. A ile böyle bir polimorfik yapı yoktur trait- çünkü a traitesasen sadece programlayıcının rahatlığı için onu kullanan her sınıfa kopyalanan bir koddur.

Öncelik:

Kılavuzda açıklandığı gibi:

Temel sınıftan devralınan bir üye, bir Özellik tarafından eklenen bir üye tarafından geçersiz kılınır. Öncelik sırası, geçerli sınıftaki üyelerin, devralınan yöntemleri geçersiz kılan Trait yöntemlerini geçersiz kılmasıdır.

Yani - aşağıdaki senaryoyu düşünün:

class BaseClass {
    function SomeMethod() { /* Do stuff here */ }
}

interface IBase {
    function SomeMethod();
}

trait myTrait {
    function SomeMethod() { /* Do different stuff here */ }
}

class MyClass extends BaseClass implements IBase {
    use myTrait;

    function SomeMethod() { /* Do a third thing */ }
}

MyClass örneğini oluştururken, yukarıda aşağıdakiler oluşur:

  1. Bu Interface IBase, SomeMethod()sağlanacak parametresiz bir işlev gerektirir .
  2. Temel sınıf BaseClass, bu yöntemin uygulanmasını sağlar - ihtiyacı karşılar.
  3. trait myTraitAdlı bir parametresiz fonksiyonu sağlarSomeMethod() , hem de önceliklidir üzerinde BaseClass-version
  4. class MyClassKendi versiyonunu sunar SomeMethod()- önceliklidir üzerinde trait-version.

Sonuç

  1. bir Interface , bir yöntem gövdesinin varsayılan uygulamasını sağlayamazken, bir traitkutu olabilir.
  2. An Interfacebir polimorfiktir , kalıtsal bir yapıdır - a traitise değildir.
  3. Birden fazla Interfacebu aynı sınıfta kullanılır ve teneke kutu birden çok olabilir traits.

4
"Bir özellik soyut bir sınıfın C # kavramına benzer" Hayır, soyut bir sınıf soyut bir sınıftır; bu kavram hem PHP hem de C # 'da mevcuttur. Ben sadece bir türü genişleten bir uzatma yöntemi aksine, bir özellik olarak kaldırılan tür temelli kısıtlama yerine, C # uzatma yöntemlerinden yapılmış statik bir sınıf için PHP bir özellik karşılaştırmak istiyorsunuz.
BoltClock

1
Çok iyi bir yorum - ve sana katılıyorum. Yeniden okumada bu daha iyi bir benzetmedir. Yine de, bunu bir olarak düşünmenin daha iyi olduğuna inanıyorum mixin- ve cevabımın açılışını tekrar gözden geçirirken, bunu yansıtacak şekilde güncelledim. Yorum yaptığınız için teşekkürler, @BoltClock!
Troy Alford

1
C # uzantısı yöntemleri ile herhangi bir ilişki olduğunu sanmıyorum. Genişletme yöntemleri tek sınıf türüne eklenir (tabii ki sınıf hiyerarşisine saygı duymak) amaçlarının bir türü ek işlevlerle geliştirmek, birden çok sınıf üzerinde "kodu paylaşmak" ve karışıklık yaratmak değildir. Karşılaştırılamaz! Bir şeyin yeniden kullanılması gerekiyorsa, genellikle ortak işlevselliğe ihtiyaç duyan sınıflarla ilişkili olan ayrı bir sınıf gibi kendi alanına sahip olması gerektiği anlamına gelir. Uygulama tasarıma bağlı olarak değişebilir, ancak kabaca budur. Özellikler kötü bir kod oluşturmanın başka bir yoludur.
Sofija

Bir sınıfın birden fazla arayüzü olabilir mi? Grafiğinizi yanlış anladığımdan emin değilim, ancak X sınıfı Y'yi uygular, Z geçerlidir.
Yann Chabot

26

bence traitsBirkaç farklı sınıfın yöntemi olarak kullanılabilecek yöntemler içeren sınıflar oluşturmanın faydalı .

Örneğin:

trait ToolKit
{
    public $errors = array();

    public function error($msg)
    {
        $this->errors[] = $msg;
        return false;
    }
}

Bu özelliği kullanan herhangi bir sınıfta bu "hata" yöntemini kullanabilir ve kullanabilirsiniz .

class Something
{
    use Toolkit;

    public function do_something($zipcode)
    {
        if (preg_match('/^[0-9]{5}$/', $zipcode) !== 1)
            return $this->error('Invalid zipcode.');

        // do something here
    }
}

İle iken interfaces , yalnızca yöntem imzasını bildirebilirsiniz, ancak işlevlerinin kodunu bildiremezsiniz. Ayrıca, bir arabirimi kullanmak için şunu kullanarak bir hiyerarşi izlemeniz gerekirimplements . Bu özelliklerle ilgili durum böyle değil.

Tamamen farklı!


Bunun kötü bir özellik örneği olduğunu düşünüyorum. to_integerdaha IntegerCastakıllıca bir arayüze dahil edilir çünkü sınıfları (akıllıca) bir tamsayıya dökmek için temel olarak benzer bir yol yoktur.
Matthew

5
"To_integer" i unutun - bu sadece bir örnek. Bir örnek. "Merhaba Dünya". Bir "example.com".
J. Bruni

2
Bu araç seti özelliği, bağımsız bir yardımcı program sınıfının sağlayamayacağı ne fayda sağlar? Yerine use Toolkitsize olabilir $this->toolkit = new Toolkit();ya da ben özelliğin kendisinin bazı yarar eksik?
Anthony

@Herhangi bir yerde Something 'S kapsayıcısında bir sen yapmakif(!$something->do_something('foo')) var_dump($something->errors);
TheRealChx101

20

Yukarıdaki yeni başlayanlar için cevap zor olabilir, Bunu anlamanın en kolay yolu:

özellikleri

trait SayWorld {
    public function sayHello() {
        echo 'World!';
    }
}

bu nedenle sayHello, tüm işlevi yeniden oluşturmadan diğer sınıflarda işlev sahibi olmak istiyorsanız, özellikleri kullanabilirsiniz,

class MyClass{
  use SayWorld;

}

$o = new MyClass();
$o->sayHello();

Sakin ol!

Özellikteki herhangi bir şeyi kullanabileceğiniz işlevler değil (işlev, değişkenler, yapı ..). ayrıca birden fazla özellik kullanabilirsiniz:use SayWorld,AnotherTraits;

Arayüz

  interface SayWorld {
     public function sayHello();
  }

  class MyClass implements SayWorld { 
     public function sayHello() {
        echo 'World!';
     }
}

bu yüzden arayüz özelliklerden farklıdır: Uygulanan sınıftaki arayüzdeki her şeyi yeniden oluşturmanız gerekir. arayüzünde uygulama yok. ve arayüzün sadece fonksiyonları ve sabitleri olabilir, değişkenleri olamaz.

Umarım bu yardımcı olur!


5

Özellikleri tanımlamak için sıklıkla kullanılan bir metafor Özellikler'in uygulama ile arayüzleridir.

Bu, çoğu durumda bunu düşünmenin iyi bir yoludur, ancak ikisi arasında bir dizi ince fark vardır.

Başlangıç ​​olarak, instanceof işleç özelliklerle çalışmaz (yani, özellik gerçek bir nesne değildir), bu nedenle bir sınıfın belirli bir özelliğe sahip olup olmadığını (veya alakasız iki sınıfın bir özelliği paylaşıp paylaşmadığını görmek için) ). Yatay kod yeniden kullanımı için bir yapı olmasıyla kastedilen budur.

Orada vardır sen özelliklerinin bir sınıf kullanır hepsi bir listesini almak sağlayacak şimdi PHP'de fonksiyonlar, ancak özellik-miras araçlarının güvenilir bir özyinelemeli denetimlerini yapmak noktada bir sınıf belirli bir özelliği olup olmadığını kontrol etmek gerekir (örnek var PHP doco sayfalarındaki kod). Ama evet, kesinlikle instanceof kadar basit ve temiz değil ve IMHO, PHP'yi daha iyi hale getirecek bir özellik.

Ayrıca, soyut sınıflar hala sınıflardır, bu nedenle çoklu kalıtımla ilgili kod yeniden kullanım sorunlarını çözmezler. Yalnızca bir sınıfı (gerçek veya soyut) genişletebileceğiniz, ancak birden fazla arabirim uygulayabileceğinizi unutmayın.

Ben özellikleri ve arayüzleri sözde çoklu kalıtım oluşturmak için el ele kullanmak gerçekten iyi buldum. Örneğin:

class SlidingDoor extends Door implements IKeyed  
{  
    use KeyedTrait;  
    [...] // Generally not a lot else goes here since it's all in the trait  
}

Bunu yapmak, belirli bir Door nesnesinin Anahtarlı olup olmadığını belirlemek için instanceof komutunu kullanabileceğiniz anlamına gelir, tutarlı bir yöntem kümesi vb. Elde edeceğinizi bilirsiniz ve tüm kod, KeyedTrait kullanan tüm sınıflarda tek bir yerde bulunur.


Bu cevabın son kısmı elbette @rdlowrey'in yazısında "Özellikler" altındaki son üç paragrafta daha ayrıntılı olarak ne söylediğidir; Sadece basit bir iskelet kod snippet'inin bunu göstermeye yardımcı olacağını hissettim.
Jon Kloske

Bence özellikleri kullanmanın en iyi OO yolu, yapabileceğiniz arayüzler kullanmaktır. Ve eğer bu arabirim için aynı tür kodu uygulayan birden fazla alt sınıfın olduğu ve bu kodu kendi (soyut) üst sınıflarına taşıyamayacağınız -> özelliklerle uygulayabileceğiniz bir durum varsa
oyuncu-one


3

Bir özelliği temel olarak otomatik bir "kopyala-yapıştır" kodu olarak düşünebilirsiniz.

Özellikleri kullanmak tehlikelidir, çünkü yürütmeden önce ne yaptığını bilmenin bir anlamı yoktur.

Bununla birlikte, kalıtım gibi sınırlamaların olmaması nedeniyle özellikler daha esnektir.

Özellikler, bir şeyi bir sınıfa kontrol eden bir yöntemi, örneğin başka bir yöntemin veya niteliğin varlığını enjekte etmek için yararlı olabilir. Bununla ilgili güzel bir makale (ama Fransızca, üzgünüm) .

Fransızca okuyabilen insanlar için GNU / Linux Magazine HS 54'ün bu konuda bir makalesi var.


Hala özelliklerin varsayılan uygulama ile arayüzlerden nasıl farklı olduğunu
anlamıyorum

@ denis631 Özellikleri kod parçacıkları, arayüzleri imza sözleşmeleri olarak görebilirsiniz. Eğer yardımcı olabilirse, herhangi bir şey içerebilecek sınıfın resmi olmayan bir parçası olarak görebilirsiniz. Yardımcı olursa haberim olsun.
Benj

PHP özellikleri daha sonra derleme zamanında genişletilen makrolar olarak görülebilir / sadece bu kod snippet'ini bu anahtarla diğer adlandırma. Bununla birlikte, pas özellikleri farklı görünür (veya yanılıyorum). Ama ikisinde de kelime özelliği bulunduğundan, onların aynı olduklarını, yani aynı kavram olduğunu varsayıyorum. Pas özellikleri bağlantısı: doc.rust-lang.org/rust-by-example/trait.html
denis631

2

İngilizce biliyor ve ne traitanlama geldiğini biliyorsanız , tam olarak ismin söylediği şey budur. Yazarak mevcut sınıflara eklediğiniz sınıfsız bir yöntem ve özellik paketidiruse .

Temel olarak, bunu tek bir değişkenle karşılaştırabilirsiniz. Kapatma fonksiyonları, usebu değişkenleri kapsamın dışından ve bu şekilde içindeki değere sahip olabilir. Güçlüdürler ve her şeyde kullanılabilirler. Aynı özellikler, eğer kullanılıyorlarsa da olur.


2

Diğer cevaplar, arayüzler ve özellikler arasındaki farklılıkları açıklamak için harika bir iş çıkardı. Yararlı bir gerçek dünya örneğine odaklanacağım, özellikle de özelliklerin örnek değişkenleri kullanabileceğini gösteren bir örnek üzerinde durulacak - en az kaynak plakalı bir sınıfa davranış eklemenize izin vereceğim.

Yine, başkaları tarafından belirtildiği gibi, özellikler arayüzle davranış sözleşmesini ve özelliğin uygulamayı yerine getirmesini belirtmesine izin vererek arayüzlerle iyi eşleşir.

Bir sınıfa olay yayınlama / abone olma yetenekleri eklemek bazı kod tabanlarında yaygın bir senaryo olabilir. 3 ortak çözüm var:

  1. Event pub / sub code ile bir temel sınıf tanımlayın ve ardından etkinlikler sunmak isteyen sınıflar, yetenekleri kazanmak için onu genişletebilir.
  2. Event pub / sub code ile bir sınıf tanımlayın ve daha sonra olaylar sunmak isteyen diğer sınıflar kompozisyon aracılığıyla kullanabilir, oluşturulan nesneyi sarmak için kendi yöntemlerini tanımlayabilir, yöntem çağrılarına proxy uygulayabilir.
  3. Event pub / alt koduyla bir özellik tanımlayın ve daha sonra etkinlik sunmak isteyen diğer sınıflar use, yetenekleri kazanmak için özellik olarak içe aktarabilir.

Her biri ne kadar iyi çalışıyor?

# 1 İyi çalışmıyor. Fark edeceğiniz güne kadar temel sınıfı genişletemezsiniz çünkü zaten başka bir şeyi genişletiyorsunuz. Bunun bir örneğini göstermeyeceğim çünkü böyle miras kullanmanın ne kadar sınırlayıcı olduğu açık olmalıdır.

# 2 & # 3 ikisi de iyi çalışıyor. Bazı farklılıkları vurgulayan bir örnek göstereceğim.

İlk olarak, her iki örnek arasında aynı olacak bazı kodlar:

Bir arayüz

interface Observable {
    function addEventListener($eventName, callable $listener);
    function removeEventListener($eventName, callable $listener);
    function removeAllEventListeners($eventName);
}

Ve kullanımı göstermek için bazı kodlar:

$auction = new Auction();

// Add a listener, so we know when we get a bid.
$auction->addEventListener('bid', function($bidderName, $bidAmount){
    echo "Got a bid of $bidAmount from $bidderName\n";
});

// Mock some bids.
foreach (['Moe', 'Curly', 'Larry'] as $name) {
    $auction->addBid($name, rand());
}

Tamam, şimdi, Auction özellikleri kullanırken sınıf farklı olacağını .

İlk olarak, # 2 (kompozisyonu kullanarak) şöyle görünecektir:

class EventEmitter {
    private $eventListenersByName = [];

    function addEventListener($eventName, callable $listener) {
        $this->eventListenersByName[$eventName][] = $listener;
    }

    function removeEventListener($eventName, callable $listener) {
        $this->eventListenersByName[$eventName] = array_filter($this->eventListenersByName[$eventName], function($existingListener) use ($listener) {
            return $existingListener === $listener;
        });
    }

    function removeAllEventListeners($eventName) {
        $this->eventListenersByName[$eventName] = [];
    }

    function triggerEvent($eventName, array $eventArgs) {
        foreach ($this->eventListenersByName[$eventName] as $listener) {
            call_user_func_array($listener, $eventArgs);
        }
    }
}

class Auction implements Observable {
    private $eventEmitter;

    public function __construct() {
        $this->eventEmitter = new EventEmitter();
    }

    function addBid($bidderName, $bidAmount) {
        $this->eventEmitter->triggerEvent('bid', [$bidderName, $bidAmount]);
    }

    function addEventListener($eventName, callable $listener) {
        $this->eventEmitter->addEventListener($eventName, $listener);
    }

    function removeEventListener($eventName, callable $listener) {
        $this->eventEmitter->removeEventListener($eventName, $listener);
    }

    function removeAllEventListeners($eventName) {
        $this->eventEmitter->removeAllEventListeners($eventName);
    }
}

# 3 (özellik) şöyle görünecektir:

trait EventEmitterTrait {
    private $eventListenersByName = [];

    function addEventListener($eventName, callable $listener) {
        $this->eventListenersByName[$eventName][] = $listener;
    }

    function removeEventListener($eventName, callable $listener) {
        $this->eventListenersByName[$eventName] = array_filter($this->eventListenersByName[$eventName], function($existingListener) use ($listener) {
            return $existingListener === $listener;
        });
    }

    function removeAllEventListeners($eventName) {
        $this->eventListenersByName[$eventName] = [];
    }

    protected function triggerEvent($eventName, array $eventArgs) {
        foreach ($this->eventListenersByName[$eventName] as $listener) {
            call_user_func_array($listener, $eventArgs);
        }
    }
}

class Auction implements Observable {
    use EventEmitterTrait;

    function addBid($bidderName, $bidAmount) {
        $this->triggerEvent('bid', [$bidderName, $bidAmount]);
    }
}

Not içindeki kodun sınıftaki EventEmitterTraitile tam olarak aynı olduğuna dikkat edin . Dolayısıyla, bakmanız gereken tek fark sınıfın uygulanmasıdır .EventEmittertriggerEvent()Auction

Ve fark büyük. Kompozisyonu kullanırken harika bir çözüm elde ediyoruz ve bizim EventEmitteristediğimiz kadar sınıf kullanmamıza izin veriyoruz. Ancak, asıl dezavantaj, yazmamız ve sürdürmemiz gereken çok sayıda kazan plakası kodumuz olması, çünkü Observablearayüzde tanımlanan her yöntem için , onu uygulamamız ve argümanları karşılık gelen yönteme ileten sıkıcı kazan plakası kodunu yazmamız gerekiyor. bizim EventEmitternesneyi besteledi . Bu örnekteki özelliği kullanmak , bundan kaçınmamızı sağlar plaka kodunu azaltmamıza ve sürdürülebilirliği artırmamıza yardımcı olmaktan .

Ancak, istemediğiniz zamanlar olabilir. Auction sınıfınızın tam olarak uygulanmasınıObservable arabirimi - belki sadece 1 veya 2 yöntemi ortaya çıkarmak istersiniz, hatta belki de hiçbiri kendi yöntem imzalarınızı tanımlayabilirsiniz. Böyle bir durumda, yine de kompozisyon yöntemini tercih edebilirsiniz.

Ancak, özellik çoğu senaryoda çok caziptir, özellikle arayüzde çok sayıda yöntem varsa, bu da çok sayıda kazan plakası yazmanıza neden olur.

* Aslında her ikisini de yapabilirsiniz - EventEmitterkompozisyonu kullanmak istediğinizde sınıfı tanımlayın ve EventEmitterTraitözellik EventEmitteriçindeki sınıf uygulamasını kullanarak özelliği de tanımlayın :)


1

Özellik, birden fazla miras amacıyla kullanabileceğimiz bir sınıfla aynıdır ve ayrıca yeniden kullanılabilirliği kodlar.

Sınıf içindeki özelliği kullanabiliriz ve 'sınıf anahtar kelimesini kullan' ile aynı sınıfta birden fazla özellik kullanabiliriz.

Arayüz, bir özellik ile aynı kod yeniden kullanılabilirliği için kullanıyor

arayüz çoklu arayüzleri genişletir, böylece çoklu kalıtım problemlerini çözebiliriz ancak arayüzü uyguladığımızda sınıftaki tüm yöntemleri yaratmalıyız. Daha fazla bilgi için aşağıdaki linke tıklayın:

http://php.net/manual/en/language.oop5.traits.php http://php.net/manual/en/language.oop5.interfaces.php



0

Temel fark, arabirimlerle, söz konusu arabirimi uygulayan her sınıftaki her yöntemin gerçek uygulamasını tanımlamanız gerektiğidir, böylece birçok sınıf aynı arabirimi ancak farklı davranışlarla uygulayabilirken, özellikler yalnızca kodların kod parçalarıdır. Bir sınıf; bir başka önemli fark, özellik yöntemlerinin aynı zamanda (ve genellikle de) olabilen arabirim yöntemlerinin aksine, özellik yöntemlerinin yalnızca sınıf yöntemleri veya statik yöntemler olabilmesidir.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.