Thread overview
Abstract ile miras alma olayları (OOP)
Dec 15, 2021
Salih Dincer
Dec 15, 2021
Ali Çehreli
Dec 16, 2021
Salih Dincer
Dec 16, 2021
Ali Çehreli
Dec 18, 2021
Salih Dincer
Dec 19, 2021
Ali Çehreli
December 15, 2021

Merhaba Ali Hocam,

İstanbul buluşmasında, yaptığımız uygulamayı (çift taraflı alt/üst sınıf haberleşmesini) kitaptaki örnekle basitleştirdim. Aslında her şey istediğim gibi çalışıyor ve bir sorum yok gibi. Ama aşağıda örnek kodda, her iki sınıf kurulmuşken birbirinden bağımsız hareket etmelerini sağlayan şey dikkatimi çekti...

Şöyle diyebilir miyiz (açıklama kodun sonunda):

class SesliAlet {
  string telefonSesi;
  string saatSesi;

  abstract string sesVer();
  abstract string türüNe();

  void init() {
    this.telefonSesi = "zırrr zırrr";
    this.saatSesi = "bi bi biiip";
    ".".write;
  }

  void kombine() {
    init();
    sesVer.write(": ");
    foreach(t; türüNe) t.write;
    writeln;
  }
}

class Telefon : SesliAlet {
  string türü;

  this() {
    this.türü = "AKILLI TELEFON";
  }

  override string sesVer() {
    return super.telefonSesi;
  }

  override string türüNe() {
    return this.türü;
  }
}

class Saat : SesliAlet {
  string türü;

  this() {
    this.türü = "MASA SAATİ";
  }

  override string sesVer() {
    return super.saatSesi;
  }

  override string türüNe() {
    return this.türü;
  }
}

import std.stdio;
void main() {
    SesliAlet[] aletler;

    aletler ~= new Telefon();
    aletler ~= new Saat();

    aletler[0].kombine();
    aletler[1].sesVer().write(": ");/*
    foreach(alet; aletler) {
      alet.kombine();
    }//*/

}

SesliAlet ana sınıfın içinde olan ve alt sınıfların seslerini tutan değişkenler (telefonSesi, saatSesi) birlikte yer alsa da kod derlenirken aslında birbirinden bağımsız (ama ana sınıfın kopyası) 2 sınıf var. Bunu şu 2 satırda kanıtlıyoruz:

aletler[0].kombine();
aletler[1].sesVer().write(": ");

Çünkü, ikinci satırda saatin sesi kombine() içinde eşitlenseydi bunu görebilecektik!

Özetle, aslında 2 sınıf var ve üst sınıftan kullandığı öğeleri kendine kopyalıyor. Teorik olarak üst sınıf yok çünkü onu doğrudan kurmadık.

Sevgiler, saygılar...

December 15, 2021
On 12/14/21 11:40 PM, Salih Dincer wrote:

> class SesliAlet {
>    string telefonSesi;
>    string saatSesi;

Orada OOP'ye aykırı bir durum farkediyorum: Eğer SesliAlet ses çıkartan bütün aletreli ifade eden genel bir sınıfsa, içinde telefonSesi ve saatSesi gibi özel kavramlar içermemeli.

>    abstract string sesVer();

O tamam.

>    abstract string türüNe();

O da biraz garip çünkü OOP'nin sağladığı rahatlıklardan biri, tam olarak türlerden bağımsız kod yazabilmektir. Örneğin, türünü hiç merak etmeden nesne.sesVer() deriz.

Ama sen onu herhalde yardımcı bir işlev olarak yazdın. (?)

> class Telefon : SesliAlet {
>    string türü;
>
>    this() {
>      this.türü = "AKILLI TELEFON";
>    }

Yukarıdakiyle ilgili olarak o da çok karşılaşılan bir durum değildir çünkü eğer öyle farklı türler varsa Telefon'un alt sınıfı olarak tanımlanabilirler:

class AkıllıTelefon : Telefon {
  // ...
}

> import std.stdio;
> void main() {
>      SesliAlet[] aletler;
>
>      aletler ~= new Telefon();
>      aletler ~= new Saat();
>
>      aletler[0].kombine();
>      aletler[1].sesVer().write(": ");/*
>      foreach(alet; aletler) {
>        alet.kombine();
>      }//*/
>
> }
> ```
>
> SesliAlet ana sınıfın içinde olan ve alt sınıfların seslerini tutan
> değişkenler (telefonSesi, saatSesi) birlikte yer alsa da kod derlenirken
> aslında birbirinden bağımsız (ama ana sınıfın kopyası) 2 sınıf var.

Tam olarak anlayamıyorum. SesliAlet, üst sınıf; ve ondan türemiş olan başka sınıflar var. Tamam. ("Kopyası" derken sanırım "üst sınıfın üyelerini içeriyorlar" diyoruz; doğru.)

> Bunu
> şu 2 satırda kanıtlıyoruz:
>
> ```d
> aletler[0].kombine();

kombine()'nin içindeki init() çağrısı tam olarak ne görmek istediğini anlamamı çok güçleştiriyor çünkü üyelerin üzerine yazıyoruz ve tabii sonraki sesVer() çağrısı hep aynı çıkıyor.

> aletler[1].sesVer().write(": ");
> ```
>
> Çünkü, ikinci satırda saatin sesi kombine() içinde eşitlenseydi bunu
> görebilecektik!

Anladım! aletler[0]'ın ve aletler[1]'in farklı nesneler olduğunu söylüyoruz. Tabii ki doğru. Yani, aletler[0].kombine(), aletler[1]'i etkilememeli. Tamam.n

> Özetle, aslında 2 sınıf var

Bu kodda ikiden fazla sınıf olduğundan kafam karışıyor. "Sınıf"ı nesne anlamında mı yazdın acaba?

> ve üst sınıftan kullandığı öğeleri kendine
> kopyalıyor.

Eğer "nesne" demek istediysen, her nesnenin hem üst sınıfından gelen üye değişkenleri olur, hem de kendi tanımladıkları. Evet, isterse üst sınıftan değer kopyalayabilir.

> Teorik olarak üst sınıf yok çünkü onu doğrudan kurmadık.

Yine de üst sınıfın üyeleri aynen bütün alt sınıflarda bulunur. Örneğin, Telefon'un içinde üç üye vardır:

  string telefonSesi;  // SesliAlet'ten gelir
  string saatSesi;     // SesliAlet'ten gelir
  string türü;         // Kendisi tanımlar

Ali


December 16, 2021

On Wednesday, 15 December 2021 at 14:39:39 UTC, Ali Çehreli wrote:

>

Bu kodda ikiden fazla sınıf olduğundan kafam karışıyor. "Sınıf"ı nesne anlamında mı yazdın acaba?

Hocam teorik açıdan. Yani demek istiyorum ki kurulabilen 2 sınıf var.

>

Teorik olarak üst sınıf yok çünkü onu doğrudan kurmadık.

Çünkü pratikte de abstract class kurulamaz... 😀

On Wednesday, 15 December 2021 at 14:39:39 UTC, Ali Çehreli wrote:

>

On 12/14/21 11:40 PM, Salih Dincer wrote:

>

class SesliAlet {
string telefonSesi;
string saatSesi;

Orada OOP'ye aykırı bir durum farkediyorum

Ohh tamam şimdi hemen internetten bulduğum klasik örneğe geçiyorum. Zaten yukardaki her şey kodu yakmak, derleyiciyi ısıtmak için. Tür, ilgi/alaka düzeyi önemsiz; Dlang Tour'da bile değişik bir örnek sunulmuşken...

abstract class Animal {
  private string status;

  this() {
    this.status = "idle";
  }

  string get() {
    return this.status;
  }

  void set(string idle) {
    this.status = idle;
  }

  string say();
}

class Cat : Animal {
  public:
     const string name, noise;

  this() {
    super();
    this.name = "The Cat ";
    this.noise = "meow";
  }

  override:
    string say() {
      return this.noise;
    }
}

class Dog : Animal {
  public:
     const string name, noise;

  this() {
    super();
    this.name = "The dog ";
    this.noise = "bark";
  }

  override:
    string say() {
      return this.noise;
    }
}

class Horse : Animal {
  public:
    const string name, noise;

  this() {
    super();
    this.name = "The Horse ";
    this.noise = "neigheding";
  }

  override:
    string say() {
      return this.noise;
    }
}

import std.stdio;

void main()
{
  auto cat = new Cat();
       cat.set("licking"); // The cat is licking.

  auto dog = new Dog();
       dog.set("sleeping"); // The dog is sleeping.

  auto arr = [ cat, dog ];

  "Status:".writeln;

  foreach(a; arr) with(a) {
    //write(name);  //82. satır açıklaması altta
    writeln("is ", get);
    "...".writeln(say);
  }
}/* ÇIKTISI:
Status:
The Cat is licking
... meow
The dog is sleeping
... bark
*/

AÇIKLAMALAR:

  • abstract class'ı iptal edip interface ile yaptığımda aynı sonuçu aldım.
  • string name için kapsülleme yapmak zorundayız çünkü Animal içinde bu üye yok.
  • Node.js'de bu soruna rastlamadım, bakmak isteyen olursa işte örnek kod:

https://code.dcoder.tech/files/code/61ba79634eca9d067980d62a/classical-ınheritance

Not: Node.js'de özel olarak abstract keyword'ü yok ama varmış gibi eksik tanımlama için hatta atması sağlanmış.

D'nin gözünü seveyim!

December 16, 2021
On 12/15/21 10:37 PM, Salih Dincer wrote:
> On Wednesday, 15 December 2021 at 14:39:39 UTC, Ali Çehreli wrote:
>> On 12/14/21 11:40 PM, Salih Dincer wrote:
>>
>> > class SesliAlet {
>> >    string telefonSesi;
>> >    string saatSesi;
>>
>> Orada OOP'ye aykırı bir durum farkediyorum
>
> Ohh tamam şimdi hemen internetten bulduğum klasik örneğe geçiyorum.

[...]

> ```d
> abstract class Animal {
>    private string status;
>
>    this() {
>      this.status = "idle";
>    }
>
>    string get() {
>      return this.status;
>    }
>
>    void set(string idle) {
>      this.status = idle;
>    }

Event, Animal sıradüzeni klasiktir ama nerede get+set, orada çokluk! :p Tür niteliklerinin get+set ile erişildiği durumlar nadirdir çünkü çoğunlukla set() yerine bir üye işlevin içindeki bir yan etki kullanılır:

  void uyu() {
    // ...
    this.status = "idle";
  }

Hele buradaki gibi üyeye doğrudan atama yapılacaksa neden public bir üye değişken olmasın?

abstract class Animal {
  public string status;
  // ...
}

Hiçbir şey; çünkü atanan değerle ilgili bir kısıtlama gerektiğinde bunları şöyle gerçekleştirebiliriz:

abstract class Animal {
  string status() const {
    // status'u belirleyen her ne ise...
    // Belki de adı status_ olan bir üye değişken
  }
}

Durumumumz internetteki örnekten çok daha iyi çünkü körlemesine set() eşdeğeri sağlamadık. Gerekirse:

abstract class Animal {
  void status(string değer) {
    // Durumu değiştirmek için değer'i kullanırız
  }
}

Bunlara rağmen, hem kendi deneyimlerim doğrultusunda hem de okuduklarıma dayanarak, üst sınıfta üye değişken çok nadiren bulunmalıdır. Ben olsam şöyle yapardım:

abstract class Animal {
  void uyu();
  void sesÇıkart();
  // vs.
}

Yani, üst sınıfta yalnızca davranış bulunmalı. Ve eğer öyle olacaksa, üst sınıf yerine bir interface kullanılmalı:

interface Animal {
  void uyu();
  void sesÇıkart();
  // vs.
}

Gerçekten ortak değişken varsa, o değişkenler bir ara sınıfta bulunmalı. Örneğin:

class DurumluAnimal : Animal {
  string status;
}

class Cat : DurumluAnimal {
  // ...
}

(Not: Yazım hatalarım olabilir. Hatta, bu durumda status'un protected olması da iyi olabilir, vs.)

Bütün bunların üstüne, OOP'nin, baştan verdiği sözlerle karşılaştırıldığında çok başarısız olmuş bir çözüm olduğunun altını çizmekte yarar var. Kendi adıma, OOP yalnızca son gösterdiğim basit sıradüzenler kullanıldığında işe yarıyor. O basitlikten uzaklaşıldıkça içinden çıkılmaz bir hal alıyor. Sonuçta Java ve D gibi diller çoklu sınıf kalıtımını yasaklıyorlar, vs.

İlgili olarak, bu gibi örneklerde gördüğümüz OOP'nin pek işe yaramadığını şöyle bir örnekle gösterebiliriz: Borsa (Monopoly) oyununu sınıflarla tasarladığımızı düşünelim. Oyun, Oyuncu, Sokak, Ev, Otel, vs. gibi sınıflar düşünebiliriz... Peki, "bu oyuncu bu sokağı alabilir" kararı hangi sınıfın nasıl bir üye işlevine ait olmalı?

Bu soruyla ilginç geliyorsa "anemic design model" ve "rich design model" karşılaştırmalarına bakarak kendi kararınızı verebilirsiniz.

Her ne kadar OOP tanrıları "anemic design model"a karşı olsalar da, kendim Borsa örneğine bakarak "anemic design model"ın üstün olduğunda karar kılmıştım. Ama dediğim gibi, artık "şu pattern", "bu pattern", "şu model", vs. gibi mantıklara bakmadan basit tasarımlar bulmaya çalışıyorum. Kendi koduma baktığımda 'class'ın çok az geçtiğini görüyorum.

D'nin de aralarında bulunduğu bütün yaygın programlama dillerinin karar kıldığı OOP yönteminin karşıtı olarak hep tekrarladığım bir örnek de "Open Methods"dır:

  https://dconf.org/2018/talks/leroy.html

Hem o sunumu hem de "Open Methods"ın gerçekleştirilmesini çok etkileyici bulmuşumdur.

> Dlang Tour'da bile değişik bir
> [örnek](https://tour.dlang.org/tour/en/basics/classes) sunulmuşken...

O örnek, çalışma zamanında her türden değer tutabilen bir Any türünü gösteriyor. Öyle bir sınıfta tutulmak olan türün ne olduğunun gösterilmesi kabul edilebilir. Üstelik, string yerine Phobos'un Variant'ının yaptığı gibi TypeInfo da döndürülebilir:

  https://dlang.org/phobos/std_variant.html#.VariantN.type

(Ne yazık ki, std.variant çoğu başka Phobos modülüne göre fazla karmaşık. Hatta, Variant bile VariantN gibi bir türün alias'ı olarak tanımlanmış! Yeni başlayanlara kolay gelsin. :) )

DLang Tour'daki örneği kendimce geliştirdim. Özellikle kod tekrarını azaltmaya çalıştım:

import std.stdio : writeln;

// Bunu 'interface' yapmak isterdim ama main içinde
// örneğin Integer'ın Any'ye otomatik olarak dönüşemedeğini
// görüyorum. Galiba bu ya bir hata ya da D'den beklenen yeni bir
// olanak. O yüzden 'class' seçiyorum ve daha önceden önerdiğim
// "en yukarıda interface olsun" önerimi geri çekiyorum.
class Any {
  abstract string getType();
  abstract string convertToString();
}

// Bu sınıf getType'ı hallediyor.
// Not: Burada asıl programdan ayrılıp "integer"
// yerine "int" döndürüyorum. Düzeltilebilir ama
// bence sorun değil.
class AnyImpl(T) : Any {
  override string getType() {
    return typeid(T).toString;
  }
}

// Bu, değer taşıyan türleri temsil ediyor.
class AnyValue(T, string formatStr = "%s") : AnyImpl!T {
  T value;
  this(T value) {
    this.value = value;
  }

  override string convertToString() {
    import std.format : format;
    return format!formatStr(value);
  }
}

// Bunları yazmak artık çok kolay:
alias Integer = AnyValue!int;
alias Float = AnyValue!(float, "%.1f");

void main()
{
  Any[] anys = [
    new Integer(10),
    new Float(3.1415f)
                ];

  foreach (any; anys) {
    writeln("any's type = ", any.getType());
    writeln("Content = ",
            any.convertToString());
  }
}

Ali


December 18, 2021

On Thursday, 16 December 2021 at 14:05:38 UTC, Ali Çehreli wrote:

> >
abstract class Animal {
   private string status;

   this() {
     this.status = "idle";
   }

   string get() {
     return this.status;
   }

   void set(string idle) {
     this.status = idle;
   }

Event, Animal sıradüzeni klasiktir ama nerede get+set, orada çokluk! :p Tür niteliklerinin get+set ile erişildiği durumlar nadirdir çünkü çoğunlukla set() yerine bir üye işlevin içindeki bir yan etki kullanılır:

void uyu() {
// ...
this.status = "idle";
}

Hele buradaki gibi üyeye doğrudan atama yapılacaksa neden public bir üye değişken olmasın?

abstract class Animal {
public string status;
// ...
}

Hiçbir şey; çünkü atanan değerle ilgili bir kısıtlama gerektiğinde bunları şöyle gerçekleştirebiliriz:

abstract class Animal {
string status() const {
// status'u belirleyen her ne ise...
// Belki de adı status_ olan bir üye değişken
}
}

Durumumumz internetteki örnekten çok daha iyi çünkü körlemesine set() eşdeğeri sağlamadık. Gerekirse:

abstract class Animal {
void status(string değer) {
// Durumu değiştirmek için değer'i kullanırız
}
}

Hocam 2 gündür yazmaya çalışıyorum ama henüz hepsini hazmedemedim :)

Ama şu yukarda alıntıladığım sizin anlattığınız model, özellikle C#'da da otomatik getter&setter olayı değil mi? Minimalist bir model sanırım:

https://www.martinfowler.com/bliki/AnemicDomainModel.html

Okumalara başladım, teşekkkürşler...

December 19, 2021
On 12/18/21 11:31 AM, Salih Dincer wrote:

> otomatik getter&setter olayı değil mi?

Evet. D'nin başka işlevler için de geçerli olan iki olanağından yararlanır:

1) UFCS. Bunu zaten hep kullanıyoruz.

2) İlk parametre atama ile de geçirilebilir. Bunu yalnızca setter'larda kullanıyoruz ama başka yerde de kullanılabilir:

void foo(int i) {
  import std.stdio;
  writeln(i);
}

void main() {
  foo = 42;  // NE??? :)
}

> Minimalist bir model sanırım:

Evet ama D'nin bu olanağının yetersizliğinden de şikayet edilir. Örneğin, setter'lar yalnızca atama işlemiyle kullanılabilir. Ne yazık ki, örneğin ++foo gibi bir kullanım bile otomatik olarak `foo = foo + 1` diye varolan getter'dan yararlanmaz.

> https://www.martinfowler.com/bliki/AnemicDomainModel.html

Ha ha! O yazı, anemic design model'ın yanlış olduğunu öne sürüyor. :) Bütün hayatını OOP ve etrafındaki yöntemler üzerine kurmuş olan Martin Fowler'dan da başka türlüsünü bekleyemeyiz...

Ali