Dart Dilinde Nesne Tabanlı Programlama(OOP) — Sınıf (Class) Kavramı |1.Bölüm
Merhaba değerli okurlar ve geliştirici arkadaşlar. Bu yazımda Dart dilinde ve programlamada önemli bir yeri olan Nesne Tabanlı Programlama, ingilizce kısaltmasıyla OOP kavramına giriş yapacağız.
Bu konuda makale yazmanın sebeblerinden biride Flutter ve Dart diline hızlı bir giriş yapıp, yazdığı kodları derlemek, anlamlandırmak ve olabildiğince iyi kod yazmak isteyen arkadaşlar için. Flutter widget tree yapısı sayesinde öğrenme eğrisinin gayet yumuşak olduğu bir framework olduğu için mobil uygulama geliştirme konusunda tercih edeni bir hayli fazla. Hızlıca ara yüzde istenilen yapıyı ortaya çıkaran arkadaşlarımız bir süre sonra Clean Code, OOP, SOLID vb kavramlarla karşılaşıp kendi yazdıkları kodların kalitesinden şüphe eder konuma geliyor. Bu yazımdan maksadım odur ki bu şekilde düşünen ve kendisini geliştirmek isteyen arkadaşlara anlaşılır şekilde kavramları ve yapıları öğretmek. O halde lafı daha fazla uzatmadan ilk konumuza girelim.
Nesne Tabanlı Programlama(OOP) Nedir?
Yazılım tasarımını işlevler ve mantık yerine, veri veya nesneler etrafında düzenleyen bir programlama dili modelidir.1960’lı yılların sonuna doğru ortaya çıkan bu programlama şekli o dönemlerde yazılım dünyasının yaşadığı sıkıntıların sonucunda ortaya çıkmıştır.
Yazılımların karmaşıklığı ve boyutları sürekli artıyor, ancak belli bir nitelik düzeyi korumak için gereken bakımın maliyeti zaman ve çaba olarak daha da hızlı artıyordu. NTP’yi bu soruna karşı bir çözüm haline getiren başlıca özelliği, yazılımda birimselliği (modularity) benimsemesidir. NTP ayrıca, bilgi gizleme (information hiding), veri soyutlama (data abstraction), çok biçimlilik (polymorphism) ve kalıtım (inheritance) gibi yazılımın bakımını ve aynı yazılım üzerinde birden fazla kişinin çalışmasını kolaylaştıran kavramları da yazılım literatürüne kazandırmıştır. Sağladığı bu avantajlardan dolayı, NTP günümüzde geniş çaplı yazılım projelerinde yaygın olarak kullanılmaktadır.
1. Sınıflar (Class)
Teorik açıklamadan sonra senaryolarla bu Dart tarafında bu konuyu nasıl ele aldığımıza gelelim. İlk olarak öğreneceğimiz konu Sınıf(Class) konusu. Dart dilindeki değişkenleri, fonksiyonları biliyoruz. Fakat iş veriler konusuna geldiğinde biraz karmaşıklaşıyor.
Örneğin bir sosyal medya uygulaması düşünelim. “Kullanıcı” diye bir kavramımız var. Bir kullanıcının nesi olur? Adı, soyadı, e-postası, takipçi sayısı gibi özellikleri (verileri) olur. Peki bir kullanıcı ne yapabilir? Giriş yapabilir, gönderi paylaşabilir, profilini güncelleyebilir. Bunlar da kullanıcının davranışlarıdır (fonksiyonlarıdır).
Şimdiye kadar öğrenmiş olduğunuzu varsaydığım yöntemlerle bu bilgileri nasıl tutardınız?
String kullaniciAdi = "ali";
int kullaniciTakipci = 150;
Map<String, dynamic> kullanici = {"ad": "ali", "takipci": 150};
void girisYap(String kullaniciAdi) { … }
Gördüğünüz gibi, bir kullanıcıya ait olan veriler ve o kullanıcının yapabildiği işlevler birbirinden ayrı, dağınık bir şekilde duruyor. 50 tane kullanıcımız olduğunda bu yapı tam bir kaosa dönüşür. İşte Class, birbiriyle ilişkili verileri (özellikleri) ve bu veriler üzerinde işlem yapan fonksiyonları (davranışları) tek bir çatı altında, temiz bir pakette toplamamızı sağlar.
Şimdi gelin sınıf nasıl oluşturulur nelerden oluşur bunlara göz atalım.
a) Sınıf Oluşturma
Dart dilinde bir sınıf oluşturmak için class anahtar kelimesini kullanırız. Ardından sınıfa, genellikle PascalCase isimlendirme kuralına (yani her kelimenin ilk harfi büyük) uygun bir isim veririz. Son olarak, sınıfın gövdesini süslü parantezler {} arasına yazarız.
Yukarıda bahsettiğimiz “Kullanıcı” senaryosunu koda dökelim:
class Kullanici {
// Sınıfın gövdesi buraya gelecek.
// 1. Özellikler (Properties / Fields)
// 2. Davranışlar (Methods)
}
Sınıf oluşturmak bu kadar kolay. Sınıfları bir blueprint bir şablon gibi düşünebilirsiniz arkadaşlar. Yani bir ev yapacağınızı hayal edin. Sınıflar bu evin şablonudur diyebiliriz… Yazının ilerleyen kısımlarında bu kavram kafanızda daha iyi yer edinecektir. Lütfen okumaya devam edin.
a-1) Özellikler (Properties / Fields)
Özellikler dediğimiz kavram sınıfın içinde tanımlanan değişkenlerdir. Yani bizim üzerinden gittiğimiz kullanıcı senaryosunda kullanıcıların adı, soyadı, yaşı, takipçi sayısı gibi yapılar sınıf içinde özellik olarak tanımlanıyor.
class Kullanici {
// Özellikler (Properties)
String ad = '';
String soyad = '';
String eposta = '';
int takipciSayisi = 0;
int takipEdilenSayisi = 0;
}
Gördüğünüz gibi, daha önce dağınık duran kullaniciAdi, kullaniciTakipci gibi değişkenleri Kullanici çatısı altında topladık.
a-2) Davranışlar (Methods)
Sınıf sadece özellikleri değil aynı zamanda davranışlarıda yani metotlarıda barındırır. Metot dediğimiz şey kullanıcının yapabileceği eylemlerdir diyebiliriz. Klasik olarak uygulamadan çıkma ve giriş yapma, butonlara tıklama, görsellere vb yapılara tıklama gibi örnekler verebiliriz.
class Kullanici {
// Özellikler (Properties)
String ad = '';
String soyad = '';
String eposta = '';
int takipciSayisi = 0;
int takipEdilenSayisi = 0;
// Metotlar (Methods / Davranışlar)
void girisYap() {
print('$ad $soyad adlı kullanıcı giriş yaptı.');
}
void gonderiPaylas(String gonderiIcerigi) {
print('$ad adlı kullanıcı şu gönderiyi paylaştı: "$gonderiIcerigi"');
}
void profilBilgileriniGoster() {
print("""
--- Kullanıcı Profili ---
Ad Soyad: $ad $soyad
E-posta: $eposta
Takipçi Sayısı: $takipciSayisi
Takip Edilen Sayısı: $takipEdilenSayisi
-------------------------
""");
}
}
Buraya kadar yinede iyi ilerledik.. Artık sınıf oluşturmayı ve o sınıfa özellik ve metot eklemeyi biliyoruz. Peki artık bitti mi? Hayır bitmedi. Sınıfları oluşturabiliyoruz fakat sınıflar bütünüyle bir kalıp gibidir. Bu kalıpları kullanarak üretim mekanizmasını tetikleyebiliriz. Üretim mekanizması tetiklenmediği sürece kalıpların bir anlamları olmuyor.
Yani biz oluşturduğumuz bu classlardan üretim hattında nesne oluşturmalıyız.. Nesne nedir konusuna gelin hep beraber bakalım
b) Sınıftan Nesne Oluşturma (Instantiation)
Muhtemelen aklınızda nesne nedir ve neden ihtiyacımız var soruları dolanıyor.. Değerli arkadaşlar yukarıdaki satırlarda bahsettiğim gibi sınıflar bizim kalıplarımız. Bu kalıplardan nesneler oluştururuz. Doğrudan kalıpları kullanamayız bu işlemin adına Çokluk (Multiplicity) denir. Bu çokluk aslında bağımsızlığı temsil eder diyebiliriz.
Hemen örnek verelim;
Kurabiye Kalıbı
- Sınıf (Kullanici): Elinizdeki yıldız şeklinde bir kurabiye kalıbıdır. Bu kalıp, her kurabiyenin yıldız şeklinde olacağını, yaklaşık ne kadar un alacağını (özelliklerini) tanımlar. Kalıbın kendisi yenmez, o sadece bir tariftir, bir plandır.
- Nesne (kullanici1, kullanici2): O kalıbı kullanarak hamurdan kestiğiniz her bir gerçek kurabiyedir.
Şimdi düşünün:
- İlk kurabiyeyi (nesneyi) yapıp üzerine çikolata sosu dökersiniz. (kullanici1.ad = ‘Ali’)
- İkinci kurabiyeyi (nesneyi) yapıp üzerine fındık parçaları serpersiniz. (kullanici2.ad = ‘Ayşe’)
İkinci kurabiyeye fındık serpmeniz, ilk kurabiyedeki çikolata sosunu etkiler mi? Asla.. Çünkü onlar artık birbirinden bağımsız, kendi verilerine sahip nesnelerdir.
Eğer nesne oluşturmasaydık ve doğrudan kalıbın kendisini (sınıfı) değiştirmeye çalışsaydık, Kullanici.ad = ‘Ali’ yaptıktan sonra Kullanici.ad = ‘Ayşe’ dediğimiz anda Ali’nin bilgisi sonsuza dek kaybolurdu. Sistemde aynı anda sadece tek bir kullanıcı bilgisi tutabilirdik. Bu da bir sosyal medya uygulaması için felaket olurdu.
Nesne oluşturmak, bir kalıptan her biri kendi hafızasına, kendi verilerine sahip, bağımsız kopyalar yaratmaktır.
Hadi şimdi koda dönelim ve bu “bağımsız kopyaları” nasıl oluşturduğumuza bakalım.Bir sınıftan nesne oluşturmak için sınıfın adını bir fonksiyon gibi çağırırız. Bu işleme “Instantiation” (Örnekleme/Nesne Yaratma) denir. Instance, object olarakta duyabilirsiniz.
void main() {
// Kullanici kalıbından 'kullanici1' adında, kendine ait bir hafıza alanı olan
// ilk nesnemizi (ilk kurabiyemizi) oluşturuyoruz.
var kullanici1 = Kullanici();
// Kullanici kalıbından 'kullanici2' adında, tamamen farklı bir hafıza alanına
// sahip ikinci nesnemizi oluşturuyoruz.
var kullanici2 = Kullanici();
}
Artık kullanici1 ve kullanici2, aynı yapıya sahip (Kullanici sınıfı) ama içerecekleri veriler açısından tamamen birbirinden bağımsız iki ayrı nesnedir. Birine “Ali” dediğimizde diğeri etkilenmeyecek.
Hadi gelin bir kod örneğine daha bakalım
void main() {
// 1. Nesneyi oluştur.
var kullanici1 = Kullanici();
// 2. Nesnenin özelliklerine değer ata.
kullanici1.ad = 'Ali';
// Aynı kalıptan bambaşka bir nesne daha oluşturalım.
var kullanici2 = Kullanici();
kullanici2.ad = 'Ayşe';
}
c) Nesnenin Özelliklerine ve Metotlarına Erişme
Yukarıdaki kod örneğinde nesnenin özelliklerine değer atama işlemini görüyorsunuz. Peki bu işlem nasıl oluyor ve ne anlama geliyor şimdi ona bakalım.
Artık elimizde bir kalıp var ve o kalıbı kullanarak üretim yapabiliyoruz. Fakat üretim noktasında ona özellikleri nasıl vereceğiz? Öncelikle bu soruyu yanıtlamadan önce vereceğimiz özelliklerin hepsinin class yani kalıbımızda tanımlı olduğundan emin olmalıyız. Hatırlatmak maksadıyla tekrardan sınıfımızı burada paylaşıyorum
class Kullanici {
// Özellikler (Properties)
String ad = '';
String soyad = '';
String eposta = '';
int takipciSayisi = 0;
int takipEdilenSayisi = 0;
// Metotlar (Methods / Davranışlar)
void girisYap() {
print('$ad $soyad adlı kullanıcı giriş yaptı.');
}
void gonderiPaylas(String gonderiIcerigi) {
print('$ad adlı kullanıcı şu gönderiyi paylaştı: "$gonderiIcerigi"');
}
void profilBilgileriniGoster() {
print("""
--- Kullanıcı Profili ---
Ad Soyad: $ad $soyad
E-posta: $eposta
Takipçi Sayısı: $takipciSayisi
Takip Edilen Sayısı: $takipEdilenSayisi
-------------------------
""");
}
}
Bu sınıftaki özellikleri yani değişkenleri kullanarak nesnelerimize hayat vereceğiz. Kurabiye örneğini düşünürsek kalıplarını belirledik ancak paketleme ve paket bilgileri konusunda eksikleri var. İşte kurabiyelerimizi paketlemek ve paket bilgilerini eklemek adına sınıfımızın yani kalıbımızın özelliklerini kullanırız. Bir anlamda o nesneyi canlandırırız.
Bir nesneyi canlandırmak demek, onun özelliklerine (değişkenlerine) değerler atamak ve davranışlarını (metotlarını) harekete geçirmek demektir.
Peki, kullanici1 nesnesine “Senin adın ‘Ali’ olacak” veya “Haydi şimdi giriş yap!” komutunu nasıl veririz? İşte bu iletişim için programlama dillerinde evrensel bir operatör kullanılır: Nokta (.) Operatörü.
Nokta operatörü, “içine gir ve eriş” anlamına gelen bir anahtar gibidir. nesneAdi.ozellikAdi dediğinizde o nesnenin içindeki özelliğe, nesneAdi.metotAdi() dediğinizde ise o nesnenin içindeki metoda ulaşırsınız.
c-1) Özelliklere Değer Atama ve Değer Okuma
Oluşturduğumuz kullanici1 nesnesi şu an bomboş. Tıpkı yeni doldurulacak bir kayıt formu gibi. Nokta operatörünü kullanarak bu formu dolduralım:
void main() {
// 1. Adım: Bağımsız bir 'kullanici1' nesnesi oluştur.
var kullanici1 = Kullanici();
// 2. Adım: Nokta operatörü ile 'kullanici1' nesnesinin İÇİNE gir ve özelliklerini doldur.
print('Nesne oluşturuldu, adı: "${kullanici1.ad}"'); // Çıktı: Nesne oluşturuldu, adı: ""
kullanici1.ad = 'Ali';
kullanici1.soyad = 'Veli';
kullanici1.eposta = 'ali.veli@email.com';
kullanici1.takipciSayisi = 150;
// Şimdi 'kullanici1' nesnesinin içindeki değeri okuyalım.
print('${kullanici1.ad} adlı kullanıcının e-postası: ${kullanici1.eposta}');
// Çıktı: Ali adlı kullanıcının e-postası: ali.veli@email.com
}
Burada kritik nokta şudur: kullanici1.ad = ‘Ali’ ataması, Kullanici sınıfını (kalıbı) değil, sadece ve sadece kullanici1 nesnesinin hafızasındaki ad alanını değiştirir.
Bunu kanıtlayalım. Aynı kalıptan başka bir nesne oluşturalım ve onun ad özelliğine bakalım:
void main() {
var kullanici1 = Kullanici();
kullanici1.ad = 'Ali';
var kullanici2 = Kullanici();
print('Kullanıcı 1 adı: ${kullanici1.ad}'); // Çıktı: Kullanıcı 1 adı: Ali
print('Kullanıcı 2 adı: ${kullanici2.ad}'); // Çıktı: Kullanıcı 2 adı:
}
Gördünüz mü? kullanici1'e yaptığımız değişiklik, kullanici2'yi zerre kadar etkilemedi. Çünkü onlar artık kendi kimlikleri ve verileri olan iki ayrı varlık.
Özellikleri çağırmayı öğrendik peki metotlarımızı nasıl çağırıyoruz? Hemen gelin onada bakalım
c-2) Metotları Çağırma (Davranışları Sergileme)
Bir nesnenin özelliklerini doldurduktan sonra, onun yapabildiği eylemleri, yani metotlarını da yine nokta operatörü ile tetikleyebiliriz. Metot çağırmak, bir nesneye “Haydi, şu işi yap!” demek gibidir.
void main() {
// Nesneyi oluştur ve özelliklerini ata.
var kullanici1 = Kullanici();
kullanici1.ad = 'Ali';
kullanici1.soyad = 'Veli';
kullanici1.eposta = 'ali.veli@email.com';
kullanici1.takipciSayisi = 150;
kullanici1.takipEdilenSayisi = 49;
// Şimdi 'kullanici1' nesnesine komutlar verelim.
// "kullanici1, git ve giriş yap!"
kullanici1.girisYap(); // Çıktı: Ali Veli adlı kullanıcı giriş yaptı.
// "kullanici1, git ve profil bilgilerini göster!"
kullanici1.profilBilgileriniGoster();
/* Çıktı:
--- Kullanıcı Profili ---
Ad Soyad: Ali Veli
E-posta: ali.veli@email.com
Takipçi Sayısı: 150
Takip Edilen Sayısı: 49
-------------------------
*/
// "kullanici1, git ve bu gönderiyi paylaş!"
kullanici1.gonderiPaylas('OOP öğrenmek çok keyifli!');
// Çıktı: Ali adlı kullanıcı şu gönderiyi paylaştı: "OOP öğrenmek çok keyifli!"
}
Dikkat ettiyseniz, girisYap() metodu çalışırken print(‘$ad $soyad adlı kullanıcı giriş yaptı.’); kodunu kullanmıştık. Metot, hangi nesne üzerinden çağrıldıysa (kullanici1), o nesnenin kendi ad ve soyad özelliklerini kullanır. Eğer aynı metodu kullanici2 üzerinden çağırsaydık, bu sefer kullanici2'nin özelliklerini kullanacaktı.
Değerli arkadaşlar çok kısaca özetlemek gerekirse buraya kadar aslında şunları öğrendik:
- Sınıf (Class): Bir nesnenin nasıl görüneceğini ve ne yapacağını tanımlayan bir kalıp veya şablondur. “Bir kullanıcı neye benzer ve ne yapabilir?” sorusunun cevabıdır.
- Nesne (Object): O kalıptan üretilmiş, canlı, kullanılabilir bir örnektir. “İşte bu Ali, e-postası bu, takipçisi şu.” gibi somut bir varlıktır.
- Nokta Operatörü (.): Nesne ile iletişim kurmamızı sağlayan köprüdür. Nesnenin içindeki verilere (özelliklere) ulaşmamızı ve onun yeteneklerini (metotlarını) kullanmamızı sağlar.
Buraya kadar umarım sizler için her şey yolunda gitmiştir ve anlaşılır olmuştur. OOP temeli adına önemli bir basamak olan Sınıflar(Class) konusun ilk bölümünü anlatmaya çalıştım. Sonraki konumuz Constructor konusu olacaktır. Dikkat ettiyseniz nesnelere sürekli elle tek tek atıyoruz, Constructor kavramıyla bu nesnelere başlangıç değerleri vermeyi öğreneceğiz.
Bu konu bir seri halinde gelecektir. Takipte kalın =)
Github: www.github.com/abdullah017
Linkedin: www.linkedin.com/in/abdullahtas
Stackoverflow: https://stackoverflow.com/users/13807726/abdullah-t