1. "Təmiz kontraktdan" çevik arxitekturaya
C# 8-dək interface tam sərt kontrakt idi: interface-i implement eləmək istəyirsənsə — hər şeyi son vergülədək yazmalısan. Əgər ora yeni üzv əlavə olunsa, bütün mövcud implementasiyalar təcili şəkildə onu əlavə etməli idi, yoxsa compiler layihəni yığmağa imkan vermirdi.
Amma həyat daha mürəkkəbdir. Təsəvvür elə, sən yüzlərlə layihənin istifadə etdiyi bir kitabxananı dəstəkləyirsən və birdən interface-ə yeni metod əlavə etmək lazım olur. Geri uyğunluğu pozmaq istəmirsən? Bax, burada səhnəyə default metodlar (Default Interface Methods, DIM) çıxır!
Məğz nədir?
Default metodlar imkan verir ki, metodun implementasiyasını birbaşa interface-də yazasan. İndi kontrakt daha elastik olur: əgər class "yeniliyi" implement eləmirsə, default implementasiya işləyəcək. Bu, filmdəki dublör kimidir: aktyor körpüdən tullanmaq istəmir? Onu kaskadyor əvəz edir.
2. Default Interface Methods-un sintaksisi
Interface-də implementasiyalı metodlar necə yazılır?
Çox bənzəyir adi metodlara, sadəcə indi metodun gövdəsini birbaşa interface-də yazmaq olar (və lazımdır):
public interface ILogger
{
void Log(string message);
// Yeni metod, default implementasiyası ilə!
void LogWarning(string message)
{
Log("[WARNING] " + message);
}
}
Burada LogWarning artıq implementasiyaya sahibdir! ILogger-i implement edən istənilən class yalnız Log-u yazmalıdır, LogWarning isə default implementasiya ilə işləyəcək (əgər öz versiyası yoxdursa).
Müqayisə et: klassik vs. müasir imza
| Versiya | Interface-də elan |
|---|---|
| C# 8-dək | |
| C# 8 və sonrası | |
Sintaksisin vacib detalları
- Implementasiyalı metod üçün mütləq gövdəni süslü mötərizədə yaz.
- Default metodlar abstract ola bilməz.
- Bütün interface metodları hələ də qeyri-rəsmi public-dir.
- Default get/set ilə property də elan etmək olar (aşağıya bax).
3. Praktik nümunələr
Nümunə 1. Geri uyğunluğu qoruyuruq
Tutaq ki, sənin tətbiqində məlumat saxlamaq üçün interface var:
public interface ISaveable
{
void Save(string filePath);
}
Sonra qərara gəlirsən ki, cloud-a da saxlamaq lazımdır. Yüzlərlə class-ı dəyişmək istəmirsən? Default metod əlavə et!
public interface ISaveable
{
void Save(string filePath);
// Yeni metod, default implementasiyası ilə!
void SaveToCloud(string cloudService)
{
Console.WriteLine($"Cloud-a saxlayıram {cloudService} (default — heç nə etmirəm)");
}
}
İndi bütün köhnə class-lar avtomatik olaraq cloud-a saxlamağı "bacarır" (hələlik sadəcə mesaj çıxarır).
Nümunə 2. Logger interface-ni genişləndiririk
Əvvəl sadə logging interface-miz var idi:
public interface ILogger
{
void Log(string message);
}
Error log üçün default metod əlavə edirik:
public interface ILogger
{
void Log(string message);
void LogError(string message)
{
Log("[ERROR] " + message);
}
}
ILogger-i implement edən class LogError-u yazmaya bilər — default versiya işləyəcək:
public class ConsoleLogger : ILogger
{
public void Log(string message)
{
Console.WriteLine(message);
}
// LogError yazmırıq — default implementasiya işləyəcək!
}
ILogger logger = new ConsoleLogger();
logger.Log("Hər şey əladır!");
logger.LogError("Ay-ay-ay, nəsə düz getmədi!"); // Default implementasiya çağırılacaq
Nümunə 3. Default metodlar + genişlənən tətbiq
Tətbiqin müxtəlif export növlərini dəstəkləyir: fayla, DB-yə, şəbəkəyə. Interface:
public interface IExporter
{
void Export(string data, string destination);
// Yeni funksiya — arxivə export
void ExportToArchive(string data, string archivePath)
{
Console.WriteLine("Default olaraq arxivləşdirmə dəstəklənmir.");
}
}
Başqa developer-lərin yazdığı plugin-lər işləməyə davam edəcək, hətta yeni metoddan xəbərləri olmasa belə.
4. Interface-in default metodları necə çağırılır?
"Köhnə class — yeni interface" ssenarisi
Əgər class interface-in default metodunu implement eləmirsə, interface referansı ilə çağıranda interface-in implementasiyası işləyir. Əgər implement eləyibsə — öz versiyası işləyir.
public class FileExporter : IExporter
{
public void Export(string data, string destination)
{
Console.WriteLine("Fayla saxlayıram...");
}
// ExportToArchive yazmırıq — default çıxış olacaq
}
IExporter exporter = new FileExporter();
exporter.Export("məlumat", "file.txt"); // FileExporter-in implementasiyası işləyəcək
exporter.ExportToArchive("məlumat", "file.zip"); // Default implementasiya işləyəcək!
"Class default metodu override edir" ssenarisi
public class AdvancedExporter : IExporter
{
public void Export(string data, string destination)
{
Console.WriteLine("Genişlənmiş rejimdə saxlayıram...");
}
public void ExportToArchive(string data, string archivePath)
{
Console.WriteLine("Arxivləşdirmə dəstəklənir!");
}
}
IExporter exporter = new AdvancedExporter();
exporter.ExportToArchive("məlumat", "file.zip"); // İndi class-ın implementasiyası çağırılacaq!
5. Default Interface Methods ilə başqa nə etmək olar?
Default property və event-lər
Əgər property-nin get və ya set gövdəsi varsa, default implementasiya ilə property elan etmək olar:
public interface IHasId
{
// Override olunmayana qədər avtomatik 42 qaytarır
int Id => 42;
}
public class Person : IHasId {}
Console.WriteLine(new Person().Id); // 42
Interface kodunda default metodları çağırmaq
Interface daxilində default metodlar və digər üzvlər bir-birini çağıra bilər:
public interface IDemo
{
void Foo() => Bar();
void Bar() => Console.WriteLine("BAR");
}
6. Default Interface Methods-un məhdudiyyətləri və xüsusiyyətləri
Field, konstruktor elan etmək olar?
Yox. Default metodlar olsa da, interface hələ də class deyil. Field, konstruktor, destruktor ola bilməz.
base-i interface-ə tətbiq etmək olar?
Olar, amma özəllikləri var. Interface-in default metodunda parent interface-in metodunu açıq şəkildə çağırmaq olar:
public interface IBase
{
void Greet() => Console.WriteLine("Salam IBase-dan");
}
public interface IDerived : IBase
{
void IBase.Greet()
{
Console.WriteLine("Salam IDerived-dən!");
IBase.Greet(this); // Parent interface-in metodunu açıq çağırırıq
}
}
Amma bu, əsas ssenarilərdə nadir hallarda lazımdır.
Default implementasiyalar toqquşanda nə olur?
public interface IA { void Foo() { Console.WriteLine("A"); } }
public interface IB { void Foo() { Console.WriteLine("B"); } }
// Class Foo-nu açıq implement etmir:
public class C : IA, IB { }
// Kompilyasiya səhvi: hansı implementasiyanı seçmək lazım olduğu bilinmir!
7. Tipik səhvlər, məhdudiyyətlər və xüsusiyyətlər
Maraqlı tipik səhv:
Bəziləri DIM ilə tanış olandan sonra interface-də field elan etməyə çalışır — amma hələ də olmaz. Həmçinin, əgər birdən static default metod yazmaq istəsən — C# 8-dək mümkün deyil. (8-dən sonra interface-də static metodlar ola bilər, amma static default implementasiya — ayrıca mövzudur.)
Xüsusiyyət: Diamond Problem ("romb problemi")
Əgər class-ın iki interface-də eyni default metodu varsa, həmin metodu açıq implement etməlisən:
public class ConflictClass : IA, IB
{
public void Foo() // özün seçməlisən, hansı variant!
{
// Lazım olan implementasiyanı interface vasitəsilə açıq çağırmaq olar
((IA)this).Foo();
// və ya
((IB)this).Foo();
}
}
Çox istifadə etmə!
Default metodlar geri uyğunluğu xilas edir, amma həddindən artıq istifadə etsən, arxitektura "çirkli" ola bilər, məntiqin bir hissəsi interface-lərə yayılır. Əsas məntiqi class-larda saxla, interface-i həqiqətən "kontrakt" kimi istifadə et.
GO TO FULL VERSION