23 Ocak 2018 Salı

Delphi Sınıf Yapısı - Sanal (Virtual) metotlar

Sanal (virtual) Metotlar

Delphi'de bir metot virtual olarak belirtilmediğinde o metot sanal olmaz statik olur. Peki virtual'ın farkı nedir? Bunu şu şekilde anlatabiliriz;
Bir ata sınıfın tanımlanmış bir sanal metodunu onun çocuğu olan bir sınıfta ezdiğimiz (override) zaman, ata sınıfı göstererek bu metodu çalıştırdığımızda, çocuk sınıfın metodu çalışacaktır.

Örnekleyelim;
uses
  System.SysUtils,
  System.Contnrs;

type
  TMeyve = class
  strict private
    FMeyveCinsi : string;
    FMeyveMiktari : string;
  public
    property MeyveCinsi : string
                 read FMeyveCinsi write FMeyveCinsi;
    property MeyveMiktari: string
                 read FMeyveMiktari write FMeyveMiktari;
    procedure Topla; virtual;
  end;

  TAgaclar = class
  private
    FMeyveAgaclari : TObjectList;
  public
    constructor Create;
    destructor Destroy; override;
    procedure AgacEkle(agac : TMeyve);
    procedure MeyveleriTopla;
  end;

  TElma = class(TMeyve)
    constructor Create;
    procedure Topla; override;
  end;

  TArmut = class(TMeyve)
    constructor Create;
    procedure Topla;
  end;



Burada önce bir TMeyve ata sınıfı tanımlıyoruz, bundan türetilmiş TElma ve TArmut meyveleri var. Bunların ikisinin de Create yapılandırıcısı (constructor) ve Topla metodu var. Ancak TElma'nın Topla metodu öncekini ezmiş TArmut'un Topla metodu bilindik statik metot. Şimdi bunların kodlarını da şu şekilde yazarsak,

 { TMeyve }

procedure TMeyve.Topla;
begin
  Writeln(MeyveCinsi+' toplanmadı');
end;

{ TAgac }

procedure TAgaclar.AgacEkle(agac: TMeyve);
begin
  FMeyveAgaclari.Add(agac);
end;

constructor TAgaclar.Create;
begin
  FMeyveAgaclari := TObjectList.Create;
end;

destructor TAgaclar.Destroy;
begin
  FMeyveAgaclari.Destroy;
  inherited;
end;

procedure TAgaclar.MeyveleriTopla;
var
  meyve: TMeyve;
begin
  for meyve in FMeyveAgaclari do
  begin
    meyve.Topla;
  end;
end;

{ TElma }

constructor TElma.Create;
begin
  MeyveCinsi := 'Elma';
  MeyveMiktari := '15 adet';
end;

procedure TElma.Topla;
begin
  Writeln(MeyveMiktari+' '+MeyveCinsi+' toplandı...');
end;

{ TArmut }

constructor TArmut.Create;
begin
  MeyveCinsi := 'Armut';
  MeyveMiktari := '81 adet';
end;

procedure TArmut.Topla;
begin
  Writeln(MeyveMiktari+' '+MeyveCinsi+' toplandı...');
end;

var
  agaclar : TAgaclar;
begin
  try
    agaclar := TAgaclar.Create;
    try
      agaclar.AgacEkle(TElma.Create);
      agaclar.AgacEkle(TArmut.Create);
      agaclar.MeyveleriTopla;
      Readln;
    finally
      agaclar.Free;     
    end;
  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;
end.

Bu örnekte agaclar.MeyveleriTopla metodunda her bir meyvenin Topla metodunu çağırdığımızda elma'nın Topla metodu çalışır ama armutun Topla metodu çalışmaz. Onun yerine TMeyve sınıfının Topla metodu çalışır ve o da armutun toplanamaması olacaktır.

Burada meyve listesini tutmak için TObjectList (geleneksel list, generic değil) kullandım. TList te kullanabilirdim. Ancak TList pointer listesi için kullanıldığından dolayı listenin işi bitince free yapıldığında nesneleri free yapamaz. TObjectList ise free edildiğinde listedeki tüm nesneleri de yok edecektir.

Bu örnek konsol uygulaması olarak çalıştırılabilir. Bir console uygulaması oluşturup içindekiler yerine bunları kopyalarsanız test edebilirsiniz.


Not : Bu örnekler Delphi 10.1 Berlin Delphi 10.2 Tokio için denenmiştir.



Delphi Sınıf Yapısı - Overload metotlar

Sınıflarda overload metotlar

Delphi'de aynı isimde metotların farklı parametrelerle tanımlanması söz konusu olabilmektedir. Bunu metodun sonuna overload; direktifi ekleyerek sağlamaktayız;

Type
 TOrnek = class
   function ToplaminiAl(i1, i2 : integer): integer; overload;
   function ToplaminiAl(r1, r2 : double): double; overload
    ... 
 end;
 ...
function TOrnek.ToplaminiAl(i1, i2 : integer): integer;
begin
  result := i1 + i2;
end;
function TOrnek.ToplaminiAl(r1, r2 : double): double;
begin
  result := r1 + r2;
end;
B
Burada overload metodunun bir örneğini görüyoruz.

20 Ocak 2018 Cumartesi

Delphi Sınıf yapısı - Başlangıç

Class (sınıf) : Giriş
Temel olarak bir sınıf içindeki şu üyeleri taşıyan bir yapıdır;
-Alanlar (fields), ya da sınıf içinde tanımlanmış değişkenlerdir.
-Metotlar (methods), ya da sınıf içinde tanımlanmış prosedür veya fonksiyonlardır.
-Öznitelikler (properties), sınıf içinde tanımlanmış alanlara değer atamak veya bazı koşullara göre belli bir alandan ya da hesaplanmış bir değeri dışarı vermek için kullanılan yapıtaşıdırlar.
-Ve bunların dışında, daha sonraki yazılarda yer alacak farklı şeyler de söz konusudur.

type
  TMeyve = class(TObject)
  strict protected
    FAdi : string;
    FCekirdekMiktari : Integer;
    FAgirlik : Double;
    FRenk : string;
    FTat : string;
    FKoku : string;
  public
    property Adi : string read FAdi;
    property CekirdekMiktari : integer read FCekirdekMiktari;
    property Agirlik : Double read FAgirlik write FAgirlik;
    property Renk : string read FRenk write FRenk;
    property Tat : string read FTat write FTat;
    property Koku : string read FKoku write FKoku;

    procedure Ozellikler; virtual;
  end;

procedure TMeyve.Ozellikler;
begin
  WriteLn('Adı '+Adi);
  WriteLn('--------------------------------------------------');
  WriteLn('Çekirdek miktarı (ortalama adet) : '+IntToStr(CekirdekMiktari));
  WriteLn('Ağırlık (ortalama gram) : '+FloatToStr(Agirlik));
  WriteLn('Renk (en bilinen) : '+Renk);
  WriteLn('Tat : '+Tat);
  WriteLn('Koku : '+Koku); 
end;

Burada bir nesne tanımlaması ve bir metot görülüyor. Farkettiğiniz gibi normal prosedürden, değişkenlerden farkı yok. Ancak en büyük fark, bunlardan 2 adet Create etsek (creating an instance), yani 2 adet nesne oluştursak, her ikisindeki metot ve alanlar kendi nesnelerine ait özellikler-nitelemeler olacaktır ve ikisininkiler birbirinden farklı olacaktır. Yani birinin özelliğini değiştirince diğerinin özelliği değişmeyecektir.

Bir metot varsayılan olarak statik metotdur yani sanal (virtual) değildir.
Bu örnekte Ozellikler metodu kalıtım örneğidir ve sanaldır. TMeyve'den türeyen başka bir sınıf oluşturduğumuzda bu metodu ezebiliriz ya da bunu olduğu gibi kullanabiliriz. Örneğin;

  TAyva = class(TMeyve)
    constructor Create;
  end;

  TChiquitaMuz = class(TMeyve)
  strict private
    FMensei : string;
  public
    property Mensei: string read FMensei write FMensei;
    constructor Create;
    procedure Ozellikler; override;
  end;

{ TAyva }

constructor TAyva.Create;
begin
  inherited;
  FAdi := 'Ayva';  // Adi alanını değiştiremiyorum. Çünkü //setter'i yok...
  FCekirdekMiktari := 20;     // Aynı şekilde
  Agirlik := 235;
  Renk := 'Sarı';
  Tat := 'Ekşi, boğaza yapışan';
  Koku := 'Bilindik özellikli bir kokusu yok';
end;

{ TChiquitaMuz }

constructor TChiquitaMuz.Create;
begin
  FAdi := 'Çikita Muz';  // Adi alanını değiştiremiyorum. Çünkü setter'i yok...
  FCekirdekMiktari := 0;     // Aynı şekilde
  Agirlik := 110;
  Renk := 'Sarı';
  Tat := 'Tatlı';
  Koku := 'Az koku vardır';
  Mensei := 'Nikaragua';
end;

procedure TChiquitaMuz.Ozellikler;
begin
  inherited;
  Writeln('Menşei : '+Mensei);
end;



TAyva'nın TMeyve'dekinden başka bir özniteliği yoktur, bu nedenle sadece constructor metodu bulunur. Constructor metodunun bulunması sebebi ise özniteliklerini tanımlamaktır. TChiquitaMuz'un fazladan Mensei özniteliği vardır. Bu nedenle Ozellikler metodunu ezmek ve onun içinde de Mensei'ni yazdırmak gereklidir. inherited ile TMeyve'de olan özellikler yazılır, hemen altında ise TChiquitaMuz'un özelliği, Mensei yazılır.

Ancak bu örnek tipik bir sanal (virtual) metot örneği değil. Neden değil, çünkü bu işi biraz farklı şekilde de olsa sanal metot kullanmadan da yapabiliriz. Ancak tipik örneklerde, yani nesneyi kullanırken sınıf olarak sadece TMeyve'den türediğini biliyorsak ve hangi meyve olduğunu bilmiyorsak, işte o zaman kalıtımın saf haliyle tipik örneğini uyguluyoruz demektir. Sonraki yazıda en tipik haliyle kalıtımı inceleyeceğiz.





Delphi sınıf yapısı : Class - Görünürlük

Class - Görünürlük

Private, Protected, Public ve Published erişim kısıtlama bildirimleri;

Type
  TSinifAdi = Class(TObject)
    public
      {public alanlar-property (öznitelik)'ler}
      {public metotlar}
    protected
      {protected alanlar-öznitelikler}
      {protected metotlar}
    private
      {private alanlar-öznitelikler}
      {private metotlar}
  end;

-Public bildirimi ile bu alan ya da metoda her yerden erişilebilir. Yani başka bir Unit'deki başka kodun herhangi bir yerinde bu nesnenin yaratılmış örneğinden bu alan ya da metotlar çağrılabilir. Ya da bu metottan türetilmiş başka bir metot içinden direkt olarak (herhangi bir örneğini yaratmadan) kullanılabilir.
-Protected bildirimi bazı erişim kısıtlamaları koyar. Protected üyelere ya aynı Unit içinden, ya da aynı sınıf veya bundan türetilmiş bir sınıf içinden (başka bir Unit içinde olması farketmez) erişilebilir. Bunun dışında erişim yoktur.
-Private bildirimi yapılan bir üyeye yalnızca deklare edildiği Unit içinden erişilebilir.

Bunların yanısıra bir de strict bildirimi vardır. Bu tek başına kullanılmaz. strict private veya strict protected olarak kullanılır. strict private'de aynı sınıf içinden erişilebilir (ondan türetilmiş sınıflardan erişilemez), strict protected'de aynı sınıf ve türetilmiş sınıflar içinden erişilebilir (aynı unit içinde başka sınıflardan erişilemez).

Kaynaklar :
1-http://docwiki.embarcadero.com/RADStudio/Tokyo/en/Private,_Protected,_Public,_and_Published_Declarations
2-http://castle-engine.io/modern_pascal_introduction.html#_visibility_specifiers