C# Arayüzler
C# Arayüzler
Bir sınıftan kalıtımla almak güçlü bir mekanizmadır, asıl kalıtımın gücü bir arayüzden kalıtımla almasında yatar. Bir sınıf kalıtımla sadece bir sınıftan türetilebilir ancak bu kısıtlama kalıtımla arayüzden türetme söz konusu olduğunda ortadan kalkar yani bir sınıf birden çok arayüzden kalıtımla türeyebilir. Arayüzler sınıflar gibi kod ve veri içermez, sadece tanım içerir. Aslında soyut sınıflar arabirimlere benzerler ancak soyut sınıflar kod ve veri içerebildikleri için arayüzlerden ayrılır. Ancak çoğu zaman arayüzler soyut sınıflarla beraber kullanılarak güçlü uygulamalar geliştirilir.
Arayüz Tanımlama
Arayüz tanımlamak için class (kılas) ya da struct (sıtrakt) sözcükleri yerine “interface” (interfeys) sözcüğü kullanılır. Arayüz içinde metotları aynen bir sınıf (class) ya da yapı (struct) içerisinde olduğu gibi bildirilir. Farklı olarak herhangi bir erişim belirteci (public (pablik), private (priveyt) ya da protected (protektıd)) kullanılmaz ve metot gövdesi noktalı virgülle ayrılır. Örneğin,
[csharp]interface IComparable
{
int CompareTo(object obj);
}
[/csharp]
Arayüz isminin başında I harfi kullanıldığına dikkat edilmelidir. Bu kullanılan sınıfın bir arayüz olduğunu anlamamıza yarayan bir isim kullanma tekniğidir. Bu sayede, sınıfların kalıtımsal olarak aldığı elemanların arayüz olup olmadığı daha kolayca anlaşılabilir.
Arayüzlerin sahip olabileceği üyeler:
- Özellikler (properties)
- Metodlar (methods)
- Olaylar (events)
- İndeksleyiciler (indexers)
Arayüzlerin sahip olamayacağı üyeler:
- Yapıcılar (constructors)
- Yokediciler (destructors)
- Alanlar (fields)
Arayüz Kısıtlamaları
Bir arayüzün hiçbir zaman bir uygulama (tanımlama dışında deyim ve ifadeler) içermeyeceği unutulmamalıdır. Bu durumun doğal sonucu olarak aşağıdaki kısıtlamalar ortaya çıkar:
- Bir arayüz için herhangi bir alan (field) tanımlanamaz (statik olsa bile). Çünkü alan bir sınıf ya da bir yapının uygulama detayıdır.
- Bir arayüz içinde kurucu (constructor) tanımlamaya da izin verilmez. Bu da uygulama detayı kabul edilir.
- Bir arayüz içinde bir yıkıcı (destructor) da tanımlanamaz.
- Bir metot için bir erişim belirteci de belirtilemez. Çünkü bir arayüzdeki tüm metotlar dolaylı olarak ortaktır (public).
- Bir arayüz içerisinde hiçbir türü yuvarlanamaz. Bir başka deyişle arayüzler sınıf, yapı ya da numaralandırma içeremez.
- Bir arayüzü bir sınıf ya da yapıdan kalıtımla alamazsınız yani türetemezsiniz. Arayüzler ,arayüzlerden türeyebilir. Yapılar ve sınıflar uygulama içerdikleri için bu kısıtlama olmasaydı üstteki bazı kısıtlamalar ihlal edilirdi.
Bir Arayüz Uygulamak
Bir arayüzü uygulamak için arayüzden kalıtımla alan ve arayüzde belirtilen tüm yöntemleri sağlayan bir sınıf ya da yapı bildirilir. Öğrenme faaliyeti -1 içinde temel sınıf olan insan hatırlanmalıdır. Bu sınıfın içerisinde Buyu adında bir metot kullanılmıştı. Insan sınıfı IInsan adında bir arayüzü uyguluyor olsun. Bu arayüz de aşağıdaki gibi tanımlanmış olsun.
[csharp]interface IInsan
{
void Buyu();
}
[/csharp]
Arayüz adlarına I harfi ile başlamak kural olmasa bile neredeyse kural hâline gelmiş bir alışkanlıktır. Hiçbir sakıncası bulunmadığı gibi programcılar topluluğuna uyum sağlamayı kolaylaştırır.
Insan sınıfının bu arayüzü uygulaması için bu arayüzden kalıtım yoluyla türetilsin:
[csharp]class Insan : IInsan
{
public void Buyu()
{
//metot kodları
}
}[/csharp]
Bir arayüz herhangi bir sınıfa uygulandığında (sınıf ilgili arayüzden türetildiğinde) o sınıfı tamamen kendisine benzemeye zorlayacaktır. Bu da birden fazla sınıfı aynı arayüzden türeterek bunlara tek bir değişken üzerinden erişebilme imkânı sağlayacaktır.
Örneğin, .Net, .NET Framework içerisinde yer alan Icomparable arayüzü iki nesnenin değerini karşılaştırır ve değerleri aynı ise 0, farklı ise -1 değerini üretir. Ancak bu karşılaştırmayı yapabilmesi için karşılaştırılan türlerin Icomparable arayüzünü uyguluyor olması gerekir. Bu ifade aslında bir kural tanımıdır ve şu anlama gelmektedir : “Eğer sınıftan oluşturulacak nesneleri karşılaştırılabilir yapmak isteniyorsa bu sınıfı Icomparable arayüzünden türetmelisiniz. ”
Aşağıdaki örnekte IComparable arayüzünü uygulayan “int” türünden iki değer karşılaştırılmaktadır.
[csharp]static void Main(string[] args)
{
int s1 = 3;
int s2 = 3;
Console.WriteLine(s1.CompareTo(s2));
}[/csharp]
Program çalıştırıldığında ekrana 0 değerini yazacaktır. Çünkü s1 ve s2’nin değerleri aynıdır. Burada “int” türü için Icomparable arayüzünü uygular denilebilir. Siz de kendi hazırladığınız bir sınıf nesneleri için birbirleri ile karşılaştırılabilir olmasını sağlayabilirsiniz.
Aşağıdaki örnekte Daire isminde bir sınıf yazılmış ve bu sınıfa IComparable arayüzü uygulanmıştır. Böylece Daire sınıfından oluşturulan iki ayrı nesneyi karşılaştırma imkânına sahip olunur.
[csharp]class Daire:IComparable
{
int yaricap; //Sınıfımıza ait field(alan)
public Daire(int yaricap)//Sınıfın kurucu metodu
{
this.yaricap = yaricap;
}
public int CompareTo(object daire1) //Arayüzden gelen metodumuz.
{
//metoda gelen daire1 değişkeni object olduğu için öncelikle kendi türüne dönüştürüyoruz.
Daire karsilastirilacak_daire = daire1 as Daire;
//yarıçapı bu dairenin yarıçapı ile aynı ise 0, farklı ise -1 gönderiyoruz.
return (yaricap == karsilastirilacak_daire.yaricap) ? 0 : -1;
}
}
class Program
{
static void Main(string[] args)
{
Daire daire_1 = new Daire(3);//Birinci daire nesnemiz
Daire daire_2 = new Daire(2);//İkinci daire nesnenimiz
//Karşılaştırılan her iki daire nesnemizinde yarıçapı farklı olduğu için ekrana -1 yazacaktır.
Console.WriteLine(daire_1.CompareTo(daire_2));
}
}[/csharp]
Daire isimli sınıfımız IComparable arayüzünü uyguladığı için CompareTo isimli, dışarıdan object alan ve int değer döndüren bir metoda sahip olmalıdır. Bu kural IComparable arayüzü tarafından tanımlanmış bir kuraldır. Siz arayüzü sınıfınıza uygularsanız bu kuralı kabul etmiş sayılırsınız. Bu yüzden IComparable arayüzünden türetilmiş olunan sınıfta bu kurala uygun bir metot tanımlamak zorunda kalınır.
Bir arayüzü uygulandığında her metodun kendine uygun bir arayüz metoduyla tam olarak eşleşmesi garantiye alınmalıdır.
- Metot adları ve dönüş türleri kesin olarak eşleşmelidir.
- Parametreler (ref ve out anahtar sözcükleri dahil) tam olarak eşleşmelidir.
- Arayüz tanımı ve arayüz uygulaması arasında bir fark varsa uygulama derlenmez.
- Arayüzden türetilen sınıf içerisinde oluşturulan arayüze ait metotlar mutlaka public olarak tanımlanmalıdır(Örneğin, Daire sınıfı için CompareTo metoduprivate yapılıarsa derleme hatası alınır.).
Bir sınıf hem bir sınıftan devralıyor hem de bir arayüzü uyguluyorsa aşağıdaki gibi kodlanır:
[csharp]class Ogrenci : Insan, IInsan
{
public void Buyu()
{
//metot komutları
}
}[/csharp]
Önce sınıf adı belirtilir. Bunu bir virgül takip eder. Daha sonra arayüz adı yazılır.
Bir Sınıfa Arayüz İle Ulaşma
Hiyerarşide üst sıralarda tanımlanmış bir sınıf değişkeni ile bir nesneye başvurulabildiği gibi sınıfın uyguladığı bir arayüz olarak tanımlanmış bir değişken kullanarak da nesneye ulaşılabilir.
[csharp]
Ogrenci birOgrenci = new Ogrenci(“Ali UZUN”);
Insan birInsan = birOgrenci; //Geçerli bir atama. Ogrenci Insan’dan türetiliyor.
[/csharp]
örneğiyle Insan tipinde bildirilmiş olan birInsan değişkeni ile Ogrenci tipinde bildirilmiş olan
birOgrenci nesnesine başvurmuştuk. Benzer şekilde;
[csharp]
Ogrenci birOgrenci = new Ogrenci();
IInsan birInsan = birOgrenci; //Geçerli bir atama. Ogrenci IInsan’ı uyguluyor.
[/csharp]
IInsan tipinde bildirilmiş olan birInsan arayüz değişkeni ile Ogrenci tipinde bildirilmiş olan birOgrenci nesnesine başvurulabilir. Çünkü Ogrenci sınıfı IInsan arayüzünü uygulamaktadır.
Bir nesneye bir arayüz üzerinden başvurma yöntemi faydalıdır. Çünkü türler belirtilen arayüzü uyguladığı sürece, parametre olarak farklı türler alabilen metotları tanımlayabilmeyi sağlar. Örneğin, aşağıda gösterilen Sil metodu, IInsan arayüzünü uygulayan herhangi bir bağımsız değişken alabilir.
[csharp]
static void Sil(IInsan birOgrenci)
{
//…
}[/csharp]
Bu örnekte bağımsız değişken IInsan arayüzünü uygulayan herhangi bir nesne olabilir. Bir arayüz üzerinden bir nesneye başvurulduğunda sadece arayüz içerisinde tanımlanan metotlar kullanılabileceği unutulmamalıdır yani Ogrenci sınıfı IInsan arayüzünde tanımlanmayan Oku metoduna sahip olsaydı bu metot kullanılamayacaktı.
Soyut (Abstract) Sınıflar
IInsan arayüzünü uygulayan Ogrenci ve Ogretmen sınıflar olsun. IInsan arayüzünde de Oku metodu tanımlanmış olsun. Bu durum hem Ogretmen hem de Ogrenci iki sınıfta da tamamen aynı olacak, Oku metodunu uygulayacaklardır. Burada bir uyarı ile karşı karşıya kalmak demektir. Çünkü kod tekrar etmektedir.
[csharp]
interface IInsan
{
void Oku();
void Konus();
}
class Ogrenci : IInsan
{
public void Oku()
{
//…
}
public void Konus()
{
//…
}
}
class Ogretmen : IInsan
{
public void Oku()
{
//..
}
public void Konus()
{
//…
}
}[/csharp]
Oku metodunun içeriği sınıfa göre değişmeyecektir. Bir öğretmenin okuması ile öğrencinin okuması arasında ne fark olabilir Ama aynı durum Konus metodu için geçerli olmayacaktır. Çünkü Ogretmen ve Ogrenci sınıfları Konus metodunu kendilerine özgü gerçekleştireceklerdir (Herkesin ses tonu ve şivesi gibi özellikler değişkendir.). Yinelemeden kaçınmak ve kodun bakımını kolaylaştırmak için kodu yeniden düzenlenmelidir. Bunun yolu ortak uygulamayı özellikle bu amaç için oluşturulmuş yeni bir sınıf içerisine taşımaktır. Bu tasarım şu şekilde değiştirilebilir:
[csharp]interface IInsan
{
void Konus();
}
class OkuyanInsan
{
public void Oku()
{
//…
}
}
class Ogrenci : OkuyanInsan, IInsan
{
}
class Ogretmen : OkuyanInsan, IInsan
{
}[/csharp]
Burada OkuyanInsan adında bir sınıf oluşturuldu ve Oku metodu buraya taşındı. Sorun çözülmüş gibi görünse de hâlâ bir problem var. OkuyanInsan sınıfının örneğini oluşturmak mümkündür. Bu çok mantıksızdır. Biz onu sadece kendisinden Ogrenci ve Ogretmeni türetmek için oluşturuldu ve ortak uygulama (Oku eylemi) sağlaması amaçlandı. Yani OkuyanInsan sınıfı kendi haklarına sahip bir girişten çok ortak işlevselliklerin bir soyutlaması olacaktır. Bir sınıfın oluşumlarının yaratılmasına izin verilmediğini belirtmek için abstract anahtar sözcüğünü kullanarak açıkça sınıfın soyut olduğunu bildirmek gerekir. Örneğin,
[csharp]abstract class OkuyanInsan
{
public void Oku()
{
//Oku metodu soyut olarak tanımlanan OkuyanInsan sınıfı içerisinde public olarak gövdesi ile birlikte oluşturuluyor.Böylece bu sınıftan türetilen tüm sınıflar için burada yazılan “Oku” işlevi kalıtım yolu ile geçecektir.
}
}
class Ogrenci : OkuyanInsan, IInsan
{
//OkuyanInsan sınıfında tanımlanan Oku metodu public olduğu için kalıtım yolu ile Ogrenci sınıfımıza da geçecektir.
public void Konus()//IInsan arayüzünden gelen metodum
{
Console.WriteLine(“Soyut sınıfların nesnesi oluşturulabilir mi?”);
}
}
class Ogretmen : OkuyanInsan, IInsan
{
//OkuyanInsan sınıfında tanımlanan Oku metodu public olduğu için kalıtım yolu ile Ogretmen sınıfımıza da geçecektir.
public void Konus()//IInsan arayüzünden gelen metodum
{
Console.WriteLine(“Soyut sınıfların nesnesi oluşturulamaz”);
}
}[/csharp]
Bir OkuyanInsan nesnesi oluşturmaya çalışılırsa kod derlenmez.
[csharp]OkuyanInsan birInsan = new OkuyanInsan();//kod derlenmez.Abstract olarak tanımlanan sınıflar nesnesi oluşturulmak için değil kendisinden türetme yapılması amacıyla oluşturulmaktadır.[/csharp]
Ancak aşağıdaki kullanımlar geçerli olacaktır.
[csharp]OkuyanInsan ogretmen = new Ogretmen();//Kalıtımdan dolayı bu kullanım geçerlidir.
OkuyanInsan ogrenci = new Ogrenci();//Kalıtımdan dolayı bu kullanım geçerlidir.[/csharp]
Yukarıdaki örnekte Ogretmen ve Ogrenci sınıfları hem OkuyanInsan soyut sınıfından hem de IInsan arayüzünden türetildi. Aslında soyut sınıfı biraz değiştirilerek IInsan arayüzünün işlevini de ona yaptırabilirdi.
Örnek aşağıdaki gibi değiştirilsin.
[csharp]//IInsan arayüzünde yer alan “Konus” metodunu soyut sınıfımız içerisinde
soyut olarak işaretliyoruz.
abstract class OkuyanInsan
{
public void Oku()
{
//…
}
public abstract void Konus();
//abstract sınıf içerisinde abstract olarak işaretlenmiş üyeler türeyen sınıfta mutlaka override edilmelidir. Zaten bizim isteğimiz Ogrenci ve Ogretmen sınıfları için “Konus” metodunun olması ancak her iki sınıf için farklı davranış göstermesidir.
}
class Ogrenci : OkuyanInsan
{
//”OkuyanInsan” soyut sınıfımızda yer alan soyut olarak işaretlenmiş Konus metodumuzu burada geçersiz kılıyoruz.
public override void Konus()
{
Console.WriteLine(“Soyut sınfların nesnesi olabilir mi?”);
}
}
class Ogretmen : OkuyanInsan
{
//”OkuyanInsan” soyut sınıfımızda yer alan soyut olarak işaretlenmiş Konus metodumuzu burada geçersiz kılıyoruz.
public override void Konus()
{
Console.WriteLine(“Soyut sınıfların nesnesi olamaz.”);
}
}[/csharp]
Daha önceki örnekte “Konus” metodu IInsan arayüzünde tanımlanmış,” Oku” metodu ise soyut sınıf olan “OkuyanInsan” sınıfı içerisinde gövdesi ile birlikte yazılmıştı.
Soyut sınıf içerisinde yazılan bir üye abstract olarak işaretlenirse arayüz içerisinde tanımlanan üye gibi davranacaktır. Yani kendisinden türetilen sınıfta bu üyenin gerçekleştirilmesini zorunlu kılar (Yukarıdaki örnekte OkuyanInsan soyut sınıfı içerisinde yer alan Konus isimli metodun Ogretmen ve Ogrenci sınıflarında gerçekleştirilmesinin zorunlu olması gibi). Bu sayede örnek için IInsan arayüzüne ihtiyaç kalmamıştır.Soyut sınıf içerisinde yer alan bir üyenin türeyen sınıfta gerçekleştirilmesi zorunlu kılınması istenirse üyeyi abstract olarak işaretlenmelidir.
Kendi sınıf hiyerarşisi içerisinde kod tekrarını önleyecek ortak işlevselliklerin bir soyutlamasını oluşturmak için abstract sınıflar oluşturulabilir.
Mühürlenmiş (Sealed) Sınıflar
Kalıtımı her zaman akıllıca kullanmak çok kolay olmamaktadır. Bir arayüz ya da soyut bir sınıf oluşturursa gelecekte kalıtımla alınacak bir şeyler yazılıyor demektir. Gelecek önceden tahmin edilemez. Pratik yaptıkça ve deneyim kazandıkça esnek, kullanımı kolay arayüz, soyut sınıflar ve sınıflar hiyerarşisi oluşturma becerisi geliştirilebilir. Bu çok çaba gerektirir ve ayrıca modellediği problemi çok iyi anlamış olmak şarttır. Bunu yapmanın başka bir yolunu bulmak için temel bir sınıf olarak kullanmak üzere oluşturulmadığı sürece bir sınıftan kalıtımla alınması engellenebilir. Bunun için programlama dili sealed anahtar sözcüğü sunmaktadır.
Örneğin,
[csharp]sealed class Ogrenci : OkuyanInsan, IInsan
{
}[/csharp]
Artık Ogrenci sınıfından başka bir sınıf türetmenin imkânı kalmamıştır. Bu sözcük sınıfın hâlihazırda mühürlenip kapatıldığını belirtir.
Kaynak: Megep Modülleri