Genişletme Türleri (Extension Types)

Genişletme türleri, mevcut bir türü farklı, yalnızca derleme zamanında bir arayüzle saran bir derleme zamanı soyutlamasıdır. Bu soyutlama, statik JavaScript etkileşiminde kritik bir rol oynar çünkü gerçek bir sarmalayıcı nesnenin maliyetini gerektirmeden mevcut bir türün arayüzünü değiştirmek için kullanılabilirler.

Genişletme türleri, temsil türü olarak adlandırılan mevcut bir türle birlikte kullanılır. Bir genişletme türünün arayüzünü tanımlarken, temsil türünün disiplin altına alınmasını sağlarlar. Genişletme türünün arayüzünü tanımlarken, temsil türünün arayüzünden bazı üyeleri yeniden kullanabilir, bazılarını atlayabilir, bazılarını değiştirebilir ve yeni işlevsellik ekleyebilirsiniz.

Aşağıdaki örnek, int türünü sadece ID numaraları için anlamlı olan işlemlere izin veren bir genişletme türü oluşturur:

extension type IdNumber(int id) {
  // 'int' türünün '<' operatörünü genişletir:
  operator <(IdNumber diger) => id < diger.id;
  // '+' operatörünü bildirmez, çünkü toplama ID numaraları için anlamsızdır.
}

void main() {
  // Bir genişletme türünün disiplini olmadan, 'int' ID numaralarını güvensiz işlemlere açar:
  int guvensizId = 42424242;
  guvensizId = guvensizId + 10; // Bu çalışır, ancak ID'ler için izin verilmemeli.

  var guvenliId = IdNumber(42424242);
  guvenliId + 10; // Derleme zamanı hatası: '+' operatörü yok.
  guvensizId = guvenliId; // Derleme zamanı hatası: Yanlış tip.
  guvensizId = guvenliId as int; // Tamam: Temsil türüne run-time dönüşüm.
  guvenliId < IdNumber(42424241); // Tamam: Genişletilmiş '<' operatörü kullanılır.
}

Not

Genişletme türleri, sarmalayıcı sınıfların aksine ek bir çalışma zamanı nesnesi oluşturmayı gerektirmez, bu da birçok nesneyi genişletme ihtiyacı olduğunda maliyeti düşürür. Genişletme türleri, statik olarak belirlenmiş yapılar olup çalışma zamanında silinir, bu nedenle maliyet açısından neredeyse sıfır olarak kabul edilebilir.

Sözdizimi (Syntax)

Deklarasyon (Declaration)

Yeni bir genişletme türünü tanımlamak için, genişletme türü bildirimi ve bir ad ile temsil türü bildirimi kullanılır:

extension type E(int i) {
  // İşlemleri tanımla.
}

Temsil türü bildirimi (int i) ifadesiyle temsil türünü int olarak belirtir ve bu temsil nesnesine referans için i adını tanımlar. Ayrıca, temsil nesnesine erişim sağlamak için i adında bir özel getirici ve E(int i) adında bir varsayılan olmayan kurucu bildirimi de ekler.

Temsil nesnesi, genişletme türü gövdesinde i kullanılarak erişilebilir (veya kurucu içinde this.i olarak).

Genişletme türü bildirimleri, sınıf veya genişletme bildirimlerinde olduğu gibi tür parametrelerini içerebilir:

extension type E<T>(List<T> elemanlar) {
  // ...
}

Yapıcılar (Constructors)

Genişletme türü gövdesinde isteğe bağlı olarak kurucuları bildirebilirsiniz. Temsil bildirimi zaten örtük bir kurucu görevi gördüğü için, adlandırılmış olmayan kurucu yerine kullanılır. Ek olmayan yönlendiren üretilmemiş kurucular, bu nesnenin başlatılmasını sağlar.

extension type E(int i) {
  E.n(this.i);
  E.m(int j, String foo) : i = j + foo.length;
}

void main() {
  E(4); // Adlandırılmamış kurucu.
  E.n(3); // Adlandırılmış kurucu.
  E.m(5, "Merhaba!"); // Adlandırılmış kurucu ek parametrelerle.
}

Veya temsil bildirimi adını taşıyan bir kurucu olabilir ve bu durumda adlandırılmamış kurucu eklenmez:

extension type const E._(int it) {
  E(): this._(42);
  E.digerAd(this.it);
}

void main2() {
  E();
  const E._(2);
  E.digerAd(3);
}

Ya da tamamen bir kurucu gizleyebilir ve aynı özel kurucu sözdizimini sınıflarda kullanabilirsiniz:

extension type E._(int i) {
  E.fromString(String foo) : i = int.parse(foo);
}

Üyeler (Members)

Genişletme türü gövdesindeki üyeleri bildirerek arayüzü tanımlayabilirsiniz. Genişletme türü üyeleri, metodlar, getter'lar, setter'lar veya operatörler olabilir. non-external instance variables veya abstract üyeleri içeremezler.

extension type SayiE(int deger) {
  // Operatör:
  SayiE operator +(SayiE diger) =>
      SayiE(deger + diger.deger);
  // Getter:
  SayiE get benimSayim => this;
  // Metod:
  bool gecerliMi() => !deger.isNegative;
}

Varsayılan olarak, temsil türünün arayüz üyeleri, genişletme türü arayüzüne doğal olarak dahil edilmez. Bir temsil türü üyesini genişletme türü arayüzüne dahil etmek için, genişletme türü tanımında onu açıkça bildirmelisiniz, örneğin, NumberE'deki '+' operatörü gibi.

Uygulamak (Implements)

İsterseniz implements ifadesini kullanarak uygulamaları dahil edebilirsiniz:

extension type SayiI(int i) 
  implements int {
  // 'SayiI', 'int' türünün tüm üyelerini çağırabilir,
  // ve burada başka şeyler de bildirebilir.
}

Veya temsil türünün bir üst türünü kullanabilirsiniz:

extension type Sira<T>(List<T> _) implements Iterable<T> {
  // Liste üzerindeki operasyonlar.
}

extension type Kimlik(int _id) implements Object {
  // Uzantı türünü null olamayan yapar.
  static Kimlik? tryParse(String kaynak) => int.tryParse(kaynak) as Kimlik?;
}

Aynı temsil türü üzerinde geçerli olan başka bir genişletme türünü de bildirebilirsiniz, bu da size çoklu kalıtımın bir türün üzerinde tekrar kullanılmasına benzer bir etki sağlar:

extension type const Secenek<T>._(({T deger})? _) { 
  const factory Secenek(T deger) = Deger<T>;
  const factory Secenek.yok() = Yok<T>;
}

extension type const Deger<T>._(({T deger}) _) implements Secenek<T> { 
  const Deger(T deger) : this._((deger: deger));
  T get deger => _.deger;
}

extension type const Yok<T>._(Null _) implements Secenek<Never> {
  const Yok() : this._(null);
}

Bu, farklı uzantı türleri arasında işlemleri yeniden kullanmanıza olanak tanır ve çoklu kalıtımın bir türde tekrar kullanılmasına benzer bir etki sağlar.

@redeclare

Bir genişletme türü üyesini bildiren ad, sınıflar arasında olduğu gibi bir yeniden bildirim ilişkisi değil, yeniden bildirimdir. Bir genişletme türü üye bildirimi, aynı adı taşıyan bir üst tür üyesini tamamen değiştirir. Bu işlevi aynı fonksiyon için alternatif bir uygulama sağlamak mümkün değildir.

Aynı adı taşıyan bir üst tür üyesini bilerek kullanıyorsanız, derleyiciye bunun gerçekten doğru olduğunu bildirmek için @redeclare açıklamasını kullanabilirsiniz. Derleyici, bir adın yanlış yazıldığı durumları size bildirecektir.

extension type BenimString(String _) implements String {
  // 'String.operator[]'ı değiştirir.
  @yenidenTanimla
  int operator [](int index) => codeUnitAt(index);
}

Ayrıca, eğer bir genişletme türü yöntemi bir üst tür üyesini gizliyorsa ve bu gizli birim yanlışlıkla yapılırsa, @redeclare açıklamasını kullanarak derleyiciye bir uyarı vermesini sağlamak için lint annotate_redeclares'ı etkinleştirebilirsiniz.

Kullanım (Usage)

Bir genişletme türünü kullanmak için, bir inşa metodu aracılığıyla bir örneği oluşturarak sınıf gibi davranabilirsiniz:

extension type SayiE(int deger) {
  SayiE operator +(SayiE diger) =>
      SayiE(deger + diger.deger);

  SayiE get sonraki => SayiE(deger + 1);
  bool gecerliMi() => !deger.isNegative;
}

void testE() { 
  var num = SayiE(1);
}

Sonra nesne üzerinde sınıf nesneleri gibi üyeleri çağırabilirsiniz.

Genişletme türleri için iki eşit ancak önemli ölçüde farklı ana kullanım durumu vardır:

  • Mevcut bir türe genişletilmiş bir arayüz sağlama

  • Mevcut bir türe farklı bir arayüz sağlama

Mevcut bir türe genişletilmiş bir arayüz sağlama

Bir genişletme türü, temsil türünü uygularken "şeffaf" olarak düşünülebilir, çünkü genişletme türü, temsil türünü "görebilir". Bu, bir genişletme türünün temsil türünün tüm üyelerini (yeniden bildirilmemiş olanlar) çağırabilmesi anlamına gelir. Bu durumda, genişletme türü, temsil türünün arayüzüne genişletilmiş bir arayüz sağlar.

Bu, örneğin, bir üst sınıfın bazı parametrelerinin daha güvenli olacak şekilde yeniden tanımlandığı bir "çoğunlukla şeffaf" bir genişletme türü olabilir.

extension tipi SayiT(int deger) 
  implements int {
  // 'int' türünün herhangi bir üyesini açıkça bildirmez.
  SayiT get i => this;
}

void main () {
  // Tamam: Şeffaflık, genişletme türü üzerinde 'int' üyelerini çağırmayı sağlar:
  var v1 = SayiT(1); // v1 türü: SayiT
  int v2 = SayiT(2); // v2 türü: int
  var v3 = v1.i - v1;  // v3 türü: int
  var v4 = v2 + v1; // v4 türü: int
  var v5 = 2 + v1; // v5 türü: int
  // Hata: Genişletme türü arayüzü, temsil türü için kullanılamaz
  v2.i;
}

Ya da, bir yöntemin bazı parametrelerine daha katı türleri kullanarak veya farklı varsayılan değerlerle adapte edilen bir "çoğunlukla şeffaf" bir genişletme türü olabilir.

Mevcut bir türe farklı bir arayüz sağlama

Şeffaf olmayan (temsil türünü uygulamayan yani implement olmayan) bir genişletme türü, tamamen yeni bir tür olarak ele alınır ve temsil türünün üyelerini açıkça göstermez.

Örneğin, kullanılan genişletme türü, temsil türü olan int'ten tamamen farklı bir tür olarak kabul edilir. Bu durumda, onu int türüne atayamazsınız ve temsil türünün üyelerine açıkça erişemezsiniz.

void testE() { 
  var num1 = SayiE(1);
  int num2 = SayiE(2); // Hata: 'SayiE' tipi 'int' tipine atanamaz.
  
  num1.gecerli(); // Tamam: Uzantı üye çağrısı.
  num1.negatifMi(); // Hata: 'SayiE' tipi 'int' tipinde 'negatifMi' üyesini tanımlamaz.
  
  var toplam1 = num1 + num1; // Tamam: 'SayiE' tipi '+' işlemini tanımlar.
  var fark1 = num1 - num1; // Hata: 'SayiE' tipi 'int' tipinde '-' üyesini tanımlamaz.
  var fark2 = num1.deger - 2; // Tamam: Referans ile temsil nesnesine erişilebilir.
  var toplam2 = num1 + 2; // Hata: 'int' tipini 'SayiE' tipine atayamazsınız.
  
  List<SayiE> sayilar = [
    SayiE(1), 
    num1.siradaki, // Tamam: 'siradaki' getter'ı 'SayiE' tipini döndürür.
    1, // Hata: 'int' elemanını 'SayiE' tipine atanamaz.
  ];
}

Genişletme türlerini kullanırken, bir türün arayüzünü değiştirmek için güçlü bir araçla performans ve kolaylık avantajına sahip olursunuz, ancak temsil türünün arayüzü asla alt türü olmadığı için bir temsil türünü genişletme türüne ihtiyaç duyulan yerde değiş tokuş yapamazsınız.

Tür İle İlgili Hususlar (Type Considerations)

Genişletme türleri, derleme zamanında bir sarma yapısıdır. Çalışma zamanında, genişletme türünün hiçbir izi yoktur. Herhangi bir tür sorgusu veya benzeri çalışma zamanı operasyonları, temsil türü üzerinde çalışır.

Bu, genişletme türlerini güvensiz bir soyutlama yapar, çünkü her zaman çalışma zamanında temsil türünü bulabilir ve altındaki nesneye erişebilirsiniz.

Dinamik tür testleri (e is T), dönüşümler (e as T) ve diğer çalışma zamanı tür sorguları (switch (e) ... veya if (e case ...)) tümü temsil nesnesine değerlendirilir ve bu nesnenin çalışma zamanı türüne karşı kontrol eder. Bu durum, e'nin statik türü bir genişletme türü olduğunda ve genişletme türüne karşı test edildiğinde geçerlidir (case MyExtensionType(): ...).

void main() {
  var n = SayiE(1);

  // 'n'nin çalışma zamanı türü, temsil türü 'int' olarak değerlendirilir.
  if (n is int) print(n.deger); // 1 yazdırır.

  // 'n' üzerinde çalışma zamanında 'int' metotları kullanabiliriz.
  if (n case int x) print(x.toRadixString(10)); // 1 yazdırır.
  switch (n) {
    case int(:var isEven): print("$n (${isEven ? "çift" : "tek"})"); // 1 (tek) yazdırır.
  }
}

Benzer şekilde, eşleşen değerin statik türü bu örnekte genişletme türüdür:

void main() {
  int i = 2;
  if (i is SayiE) print("Bu"); // 'Bu' yazdırır.
  if (i case SayiE v) print("değer: ${v.deger}"); // 'değer: 2' yazdırır.
  switch (i) {
    case SayiE(:var value): print("değer: $value"); // 'değer: 2' yazdırır.
  }
}

Genişletme türlerini kullanırken bu özelliği akılda bulundurmak önemlidir. Her zaman genişletme türünün derleme zamanında var olduğunu ve önemli olduğunu, ancak derleme sırasında silindiğini unutmayın.

Bir ifade, genişletme türü E ve statik türü R olarak çalışma zamanında R türündeki bir nesne olacaktır. Hatta tür kendisi de silinir; List<E>, çalışma zamanında tam olarak List<R> ile aynı şeydir.

Başka bir deyişle, gerçek bir sarma sınıfı bir sarılı nesneyi kapsayabilirken, bir genişletme türü yalnızca sarılı nesnenin derleme zamanındaki bir görünümüdür. Gerçek bir sarma daha güvenlidir, ancak genişletme türleri size sarma nesnelerini önlemek için bir seçenek sunar, bu da bazı senaryolarda performansı büyük ölçüde artırabilir.

Last updated