Unity 3D ile Nesne Yönelimli Programlama 02: Class Nedir?

Unity 3D ile Nesne Yönelimli Programlama yazı serisinde class nedir, class’ın temel özellikleri nelerdir sorularını yanıtlıyoruz.
unity 3d class nedir
Unity 3D geliştiricilerine özel içerik: Class nedir?

Class, Object (Nesne) ile birlikte, nesne yönelimli programlamanın (OOP) en temel öğesidir. Class (Sınıf), nesnelerimizin özelliklerini ve işlevlerini içinde barındırır. Her sınıf bir nesnedir. Sınıflar sayesinde yazdığımız programlar parçalara ayrılır ve defalarca kullanabiliriz. Bir sınıf, çalışma esnasında (Run Time) bellekte kendine ait bir yer oluşturur ve o nesneye, o nesnenin alanlarına bellekte oluşan adresi üzerinden erişip değiştirebiliriz.

(Araştırma için anahtar kelimeler: Memory Management, Garbage Collector, Stack, Heap)

OOP şeması
OOP şeması

Her sınıfın kendi sorumluluk alanı vardır. Başka bir nesnenin yapacağı işlerin sonucuyla ilgilenebilir fakat onun işlerini kendi içerisinde yapmamalıdır. Bir sınıf, içerisinde aşağıdaki öğeleri barındırabilir:

  • Constructor
  • Fileds
  • Methods
  • Properties
  • Events
  • Delegates

Bir sınıfı en basit şekliyle aşağıdaki gibi yazarız:

public class Cat
{
}

Access Modifiers (Erişim Belirleyicileri)

Class (Sınıf) içerisindeki bu öğelerin erişim belirleyicileri (Access Modifiers) vardır. Bu erişim belirleyicileri beşe ayrılır:

  • Private
  • Public
  • Protected
  • Internal
  • Protected Internal

Bunlardan şimdilik sadece “Public” ve “Private”ı bilip diğerlerine ileride değineceğiz.

Public Access Modifier

Sınıf içerisindeki bir “field” veya metodun (method) “public” olması, o field veya metodun diğer sınıfların erişimine açık olduğunu ifade eder. Kısacası erişim dışarıya açık demektir.

Private Access Modifier

Sınıf içerisindeki bir alan “field” veya metodun (method) “private” olması, o field veya metodun diğer sınıfların erişimine kapalı olduğunu ifade eder. Kısacası erişim dışarıya kapalı demektir.

Field

Sınıf (Class) içerisinde ilişkilendirilmiş değişkenlerdir. Her değişken, verinin tipi ve erişim seviyesi ile birlikte tanımlanır. Değişken ismi, tuttuğu veriyi ifade edecek şekilde ve anlaşılır olmalıdır. Erişim seviyesi yazılmayan field’ler varsayılan olarak private olarak tanımlanır.

public class Cat
{
		// private erişim seviyesinde
		// string değişken tipinde
		string _name;

		// private erişim seviyesinde 
		// bool değişken tipinde
		private bool _isHungry;
		
		// public erişim seviyesinde
		// bool değişken tipinde
		public bool _isSleep;
}

Method

Sınıflarda yapmak istediğimiz belli işleri bir araya getiren yapılara “method” (metot) denir. Bir metot yazarken metoda vereceğimiz isim o metotta yapılacak işi anlatan, sizin dışınızda birinin bu metodun ismini gördüğünde ne yaptığına dair bir fikrinin olacağı şekilde olmalıdır. Bir metot yazılırken geri dönüş tipi belirtilir. (“Constructor method”larda geri dönüş değeri yazılmaz.)

public class Cat
{
		// public erişim seviyesinde
		// geri dönüş tipi void (boş) olan method
		public void EatSomething()
		{
		}

		// private erişim seviyesinde
		// string geri dönüş tipinde
		// geri dönüş değeri return ile Speak() methodunu çağırana teslim edilir.
		private string Speak()
		{
				string voice = "Meooow";
				return voice;
		}	
}

Bir metot parametreli veya parametresiz şekilde yazılabilir. Aynı isimde ve parametrelere sahip iki ayrı metot yazamazsınız. C# yazılım dili de aynı isimde ve farklı parametrelere sahip iki metot yazabilirsiniz. Bu durumdaki metotlar için “overload method” denir.

public class Cat
{
		private bool _isHungry;		

		public void EatSomething()
		{
				_isHungry = false;
		}	
		
		// EatSomething metodunun overload'u
		public void EatSomething(string feedType)
		{
				if(feedType == "meat")
				{
						_isHungry = false;
				}
				else
				{
						_isHUngry = true;
				}
		}	
}

Bir metot içerisinde başka bir metodu çağırabilirsiniz.

public class Cat
{
		public void FindToys()
		{
				PlayWithToys();
		}

		public void PlayWithToys()
		{
		}
}

Metotları, çalışan alt programcıklar olarak düşünebilirsiniz. Dolayısıyla bir metot içerisinde birden fazla iş yazılabilir. Fakat bir metodun birden fazla sorumluluğu olmamasına özen göstermeliyiz. Buna dikkat etmediğimizi en rahat şöyle anlayabiliriz: bir metot içinde yapılan bir işleme başka bir metot içerisinde ihtiyaç duyuyor ve o işlerle ilgili yazdığımız kodları başka bir metoda da kopyalama ihtiyacı duyuyorsak kopyaladığımız kodlardan başka bir metot yazabiliriz demektir. Eğer ki kopyaladığımız bu kodlara başka bir sınıfta (class) ihtiyaç duyuyorsak o zaman yeni bir sınıfa yani nesneye ihtiyacımız var demektir. Böylece o işlemde bir hata olur veya bir değişiklik yapmak ihtiyacı duyduğunuzda, tek tek yazdığınız yerleri arayıp düzeltmek yerine ilgili metodu veya ilgili sınıfı değiştirdiğinizde, yaptığınız değişikliği bir seferde her yere uygulamış olursunuz.

Örnek olarak; Bir metot kendi içerisinde bir ürünün fiyatını güncelleyip kayıtlı kullanıcılara mail atıyorsa bunu en az iki metoda bölebiliriz. Biri fiyat güncelleme metodu, diğeri kullanıcılara mail atma metodu. Mesela bir ürünün satışını sonlandırıp kullanıcılara mail atmak istediğinizde mail atma işlemi ile ilgili metodu tekrar çağırmamız yeterli olacaktır.

public class StockManagement
{
		private int _price = 50;
		public void UpdatePrice()
		{
				_price = 100;
				string message = "Ürünün fiyatı güncellendi: "+ _price.ToString();
				SendMail(message);
		}

		public void StopSell()
		{
				SendMail("Ürünümüzün satışı bulunmamaktadır.");
		}

		public void SendMail(string message)
		{
		}
}

Constructor

Bir nesne “Run Time”da yaratıldığında ilk çalışan metoda “constructor” denir. “Constructor method”larda geri dönüş değeri yazılmaz ve “Constructor method” sınıf (class) ismi ile aynıdır. “Constructor Method”un varsayılan geri dönüş değeri sınıfın kendisidir.

Bir sınıf içerisinde “Constructor method” yazılmamışsa arka planda varsayılan olarak aşağıdaki gibi “Constructor method” varmış gibi işlem görür.

public class Cat
{
		public Cat()
		{
		}
}

Constructor’lar bir nesne yaratıldığında ilk çalışan metot olduğu için genel olarak o nesne içerisinde varsayılan değerleri tanımlamak için kullanılabilirler.

public class Cat
{
		private bool _isHungry;
		private bool _isSleep;
		public string name;

		//Constructor
		public Cat()
		{
				_isHungry = true;
				_isSleep = false;
		}

		//Constructor method (overload)
		public Cat(bool isHungry, bool isSleep)
		{
				_isHungry = isHungry;
				_isSleep = isSleep;
		}
}

Bir nesneye ait sınıfı yazdık. Peki “Run Time”da bir nesneyi nasıl yaratırız? Örnek olarak yukarıda yazdığımız Cat sınıfını kullanalım.

using UnityEngine;

public class Main : MonoBehavior
{
		private Cat cat1;
		private Cat cat2 = new Cat();

		private void Awake()
		{
				Cat cat0 = new Cat();
				cat0.name = "Tekir";
				
				cat1 = new Cat(false, false);
				cat1.name = "Garfield";
				
				DoSomeThing();
				
				cat2.name = "sarman";
		}

		public void DoSomeThing()
		{
				// null referance error verecektir.
				Debug.Log("cat0 name: "+cat0.name);

				// "Garfield" değerini bize geri döndürür.
				Debug.Log("cat1 name: "+ cat1.name);

				// null referance hatasi verecektir.
				Debug.Log("cat2 name: "+cat2.name);
		}
}

Yukarıdaki örnekte iki ayrı Cat nesnesi yarattık. Yeni bir nesne yaratırken sınıf isminin önüne “new” yazarak bellekte o nesne için bir alanı ayırmış oluruz.

Örnekte cat0 nesnesi ana sınıfının (main class) “Awake” metodu içerisinde tanımlandığı için (Cat cat0 = new Cat()) bu değişkene erişimimiz sadece “Awake” içerisinde olacaktır. cat1 ise metotların dışında tanımlanıp yaratma işlemi “Awake” metodu içerisinde yapılmıştır. “DoSomeThing” içerisinde ise isim (name) değeri istenmiştir. Hatasız bir şekilde çalışacaktır. cat2 ise metotların dışında tanımlanmış ve “new” denilerek yaratılmıştır. Fakat “DoSomeThing” içerisinde hata verecektir. Çünkü yaratma işlemi “DoSomeThing” metodundan sonra yapıldığı için bellekte kendisi için ayrılmış bir alanı bulamayacaktır. Hata verecektir.

Yarattığımız her nesnenin veya değişkenin bellekte bir yaşam ömrü vardır. cat0 sadece metot içerisinde tanımlanıp yaratıldığı için ömrü “Awake” içerisinde sınırlandırılmıştır. cat1 ve cat2 ise sınıf içerisinde tanımlandığı için ömrü “Main” ömrü kadardır.

cat0’a bu nedenle “Local Variable” (yerel değişken) denir.

cat1 ve cat2’e bu nedenle “Global Variable” (genel değişken) denir. Daha fazla bilgi için “Local Variable” ve “Global Variable” konularını ve “Memory Management” ve “Garbage Collector” konularını araştırmanızı öneririm.

Properties

Bir field’ın değerini okumak (get), yazmak veya değiştirmek (set) için kullandığımız öğelerdir. Bir field’ın değerinin sınıf (class) dışından değiştirilmesini istemediğimiz de bu field’ı “private” yaparız. Fakat bu field’ın değerini sınıf dışından da değiştirilebilir olmasını istiyor ve doğrudan erişimini istemiyorsak bu field için bir property yazabiliriz.

public class Cat
{
		private string _name;

		public string Name
		{
				get { return _name: }
				set { _name = value: }
		}
}

Property içerisinde, bir field’ı “get” veya “set” ederken kontrol edebiliriz. Aşağıdaki örnekte “_name” değerinin boş olmasını istemiyorum. Bu nedenle set ederken (yazarken) değeri boş veya “null” ise varsayılan bir değer atamasını istiyorum. Aynı şekilde get ederken (okurken) değeri boş veya null ise bana field’ına varsayılan bir değer atanmasını ve bana bu değeri dönmesini istiyorum.

public class Cat
{
		private string _name;

		public string Name
		{
				get 
				{ 
						if(string.IsNUllOrEmpty(_name))
						{
								_name = "UnNamed"
						}
						return _name;
				}
				set 
				{ 
						if(string.IsNullOrEmpty(value))
								_name = "UnNamed";
						else
								_name = value: 
				}
		}
}
using UnityEngine;

public class Main : MonoBehavior
{
		
		private void Awake()
		{
				Cat cat0 = new Cat();
				Debug.Log("1 Cat Name: " + cat0.Name); 
				cat0.Name = "Garfield";
				Debug.Log("2 Cat Name: " + cat0.Name);
		}
}
//Log ciktisi:
//1 Cat Name: UnNamed
//2 Cat Name: Garfield

Bir property’nin bir field ile ilişkisinin olmasına gerek yoktur. Aşağıdaki gibi bir property de yazabilirsiniz.

public class Cat
{
		public bool IsSleeping
		{
				get; 
				set;
		}
}

Bir property’nin get ve set işlemlerini isterseniz sınıf (class) dışı erişimlere kapatabilirsiniz. Aşağıdaki örnekte sınıf dışına sadece get açık, set ise sadece sınıf içinde kullanılabilirdir. Sınıf dışından set edersek derleme hatası alırız.

public class Cat
{
		public bool IsSleeping
		{
				get; 
				private set;
		}
}
using UnityEngine;

public class Main : MonoBehavior
{
		
		private void Awake()
		{
				Cat cat0 = new Cat();
				Debug.Log("Is Sleeping: " + cat0.IsSleeping);
				// IsSleeping sadece Cat class'ı içersinden set edilebilir.
				// Aşaşıdaki satırda derleme hatası alırız. 
				cat0.IsSleeping = true;
		}
}

Neler Öğrendik?

  • Bir nesneyi bir programa aktarırken classları kullandık.
  • Nesnelerin özelliklerini ve işlevlerini sınıfların (class) field, method, properties gibi öğeleri ile ifade ettik.
  • Method, Property, Field, Constructor öğelerini ve nasıl kullanıldıklarını öğrendik.
  • public ve private Access Modifiers (Erişim Belirleyicileri) ve bunların basit kullanım ve etkilerini öğrendik.

Daha fazlasını araştırmak, okumak, ve öğrenmek için anahtar kelimeler: Access Modifiers, Constructor, Method, Property, Field, Local Variable, Global Variable, Memory Management, Garbage Collector

Related Posts