1. Giriş
Deməli, biz əmin olduq ki, interfeyslər güclü kontraktlardır. Bəs interfeys səviyyəsində proqramlaşdırma nədir? Bu, dizayn fəlsəfəsidir və belə deyir: "Abstraksiyalardan asılı ol, konkret realizasiyalardan yox."
Gəlin yenə analoqa baxaq. Sən qəhvə hazırlamaq istəyirsən. Qəhvə maşını alırsan. Sənə fərqi yoxdur ki, bu konkret "Super-Puper-Maşın V3000" modelidir, ya "Mega-Avtomat Barista 5000", düzdür? Sənə vacib olan odur ki, bu cihaz "qəhvə hazırlaya bilir" – yəni, "kontraktı" ICoffeeMaker reallaşdırır. Sadəcə "Qəhvə hazırla" düyməsini basırsan və o bunu edir. Necə edir – üyüdülmüş dənlə, kapsulla, ya başqa üsulla – istifadəçi kimi səni maraqlandırmır.
Kodda bu o deməkdir ki, metodlarında konkret Document və ya Image tipini tələb etmək əvəzinə, IPrintable tələb edəcəksən. Kodun istənilən interfeysi reallaşdıran obyektlə işləyəcək, onun konkret tipini bilmədən.
İnterfeys kontraktı — bu zəmanətdir ki, interfeysi reallaşdıran hər bir class mütləq interfeysin bütün üzvlərini (metodlar, property-lər, event-lər) təyin edəcək. Proqram baxımından, interfeysi reallaşdıran istənilən obyekt müəyyən "imkanlar dəstini" dəstəkləyir və buna arxayın olmaq olar, içəridə necə işlədiyini bilmədən.
Tutaq ki, sən kolleqanla razılaşmısan ki, həmişə foyedə qırmızı divanın yanında görüşürsünüz. O ora necə gələcək – liftlə, piyada, ya divarlarla qaçaraq – fərqi yoxdur. Əsas odur: orada olacaq. Bax, interfeys kod üçün "qırmızı divan"dır.
Nümunə
public interface IPrintable
{
void Print();
}
Kontrakt: IPrintable reallaşdıran hər kəs özünü ekrana çap etməyi bacarmalıdır. Necə – artıq detal məsələsidir.
2. Kontrakt sistem hissələri arasında əlaqə nöqtəsi kimi
Bu praktikada necə işləyir
Sistem onlarla class-dan ibarət ola bilər, fərqli insanlar tərəfindən, fərqli vaxtlarda, fərqli məqsədlərlə yaradılıb. Amma hamısı eyni kontrakta (yəni, bir interfeysi reallaşdırırsa), onları eyni cür istifadə etmək olar.
Real tətbiqdən nümunə: təsəvvür et, müştəri bazası var. Customer class-ı, Employee class-ı, Contractor class-ı. Hərəsi məlumatı özünəməxsus saxlayır, amma hamısı, məsələn, IContactInfo interfeysini reallaşdırırsa, hansı ki, GetEmail() və GetPhoneNumber() metodlarını tələb edir, onda xarici kod üçün obyektin hansı tipə aid olması önəmli deyil – əsas odur ki, email və telefon almaq mümkündür.
public interface IContactInfo
{
string GetEmail();
string GetPhoneNumber();
}
public class Customer : IContactInfo
{
public string Email { get; set; }
public string Phone { get; set; }
public string GetEmail() => Email;
public string GetPhoneNumber() => Phone;
}
// Employee və Contractor üçün də eyni...
İndi şirkətinin əlaqədə olduğu hər kəsin məlumatını çap etmək lazımdırsa (kim olmasından asılı olmayaraq), sadəcə IContactInfo siyahısından keçirsən, lazım olan metodları çağırırsan – və hər şey işləyir.
3. "İnterfeys səviyyəsində proqramlaşdırma"
İnterfeys səviyyəsində proqramlaşdırmaq – bu o deməkdir ki, kodunu konkret class-lardan yox, yalnız interfeyslərdən (yəni, kontraktlardan) asılı yazırsan. İnterfeysi reallaşdıran class istənilən cür ola bilər, əsas odur ki, interfeysin tələblərini yerinə yetirsin.
Niyə bu vacibdir?
- Miqyaslana bilənlik: yeni tipləri əlavə etmək asandır, mövcud kodu dəyişməyə ehtiyac yoxdur.
- Test oluna bilənlik: test zamanı obyektləri asanlıqla mock-larla (stub-larla) əvəz etmək olur.
- Çeviklik: realizasiya hər gün dəyişə bilər, interfeys isə sabit qalır.
- Arxitekturanın təmizliyi: modulların bir-birinə zəif bağlılığı olur, onları təkrar istifadə etmək olur.
Nümunə — Tanış proqram: "Bank hesabı"
Əvvəlki tətbiq nümunələrində bizdə BankAccount adlı abstrakt class və Withdraw() adlı abstrakt metod var idi, konkret tiplər (SavingsAccount, CheckingAccount) detalları reallaşdırırdı.
İndi interfeys əlavə edək – məsələn, balans haqqında məlumat çıxarmaq üçün:
public interface IBalanceReporter
{
void ReportBalance();
}
public abstract class BankAccount : IBalanceReporter
{
public double Balance { get; set; }
public abstract void Withdraw(double amount);
public void ReportBalance()
{
Console.WriteLine($"Cari balans: {Balance} avro");
}
}
İndi IBalanceReporter ilə işləyən hər kəs ReportBalance() çağırıb, konkret hesab tipindən asılı olmayaraq məlumat ala bilər.
4. Ümumi emal və polimorfizm kontrakt vasitəsilə
Nümunə: ümumi emalçı
İnterfeyslə işləyən kimi, obyektin tipindən asılı olmayan ümumi metodlar yarada bilərik:
static void PrintAllBalances(IBalanceReporter[] accounts)
{
foreach (var reporter in accounts)
{
reporter.ReportBalance();
}
}
Bu siyahıya IBalanceReporter reallaşdıran istənilən obyekt daxil ola bilər: SavingsAccount, CheckingAccount, hətta MockAccountForTesting. Heç bir sehr yoxdur, hər şey real işləyir.
Sxematik: interfeys kontraktı nə verir
+-------------------+ reallaşdırır +-------------------+
| BankAccount | <---------------- | IBalanceReporter |
| (SavingsAccount) | |-------------------|
| (CheckingAccount)| | + ReportBalance() |
+-------------------+ +-------------------+
| ^
| |
+-----------+-----------------------+
|
Kontraktı reallaşdıran istənilən başqa class
5. Kontraktlar, biznes-loqika və arxitektura yazmaq
İnterfeys kontraktı dəqiq ayırır ki, class nə etməlidir və necə bunu reallaşdırır. Ona görə proqram arxitektorları sistem loqikasını məhz interfeyslərlə dizayn etməyi sevirlər. Çox vaxt əvvəlcə interfeys (kontrakt) hazırlanır, realizasiya isə sonra gəlir.
Həyatdan nümunə: ödəniş sistemləri
Elektron pulqabılar, kartlar, PayPal, kriptovalyuta... Hərəsinin öz detalı var, amma abstraksiya edib IPaymentProvider interfeysi yaratsaq:
public interface IPaymentProvider
{
void Pay(decimal amount);
bool Refund(decimal amount);
}
Bu interfeyslə işləyən kodu maraqlandırmır ki, kartdan ödəyir, yoxsa hesabdan. Bu həm arxitektura, həm də həyat üçün rahatdır: yeni ödəniş sistemlərini əlavə etmək olur, qalan kodu dəyişmədən.
6. Biznes-loqikanı kontrakta çıxarmaq
Kontrakt əsas biznes qaydalarını interfeys səviyyəsinə çıxarmağa imkan verir, detallar (məsələn, limit yoxlaması, cashback hesablama və s.) isə konkret class-lara qalır.
Başqa bir nümunə
public interface ILogger
{
void LogInfo(string message);
void LogError(string message);
}
public class ConsoleLogger : ILogger
{
public void LogInfo(string message) => Console.WriteLine($"INFO: {message}");
public void LogError(string message) => Console.WriteLine($"ERROR: {message}");
}
public class FileLogger : ILogger
{
public void LogInfo(string message) => /* Fayla yazmaq */;
public void LogError(string message) => /* Fayla yazmaq */;
}
Sonra müştəri kodu belə olur (hansı logger ilə işlədiyinin fərqi yoxdur):
void DoWork(ILogger logger)
{
logger.LogInfo("İş başladı.");
// ... hansısa iş ...
logger.LogError("İş alınmadı.");
}
Bax, bu interfeys səviyyəsində proqramlaşdırmadır: müştəri kodu yalnız kontrakta (interfeysə) bağlıdır, konkret realizasiyaya yox.
7. Səhvlər və tipik tələlər
Yeni başlayanlar tez-tez belə səhv edirlər: kodları konkret class-lara bərk bağlı olur, interfeyslərə yox. Bu bir çox problem yaradır:
- Bir şeyi əvəz etmək və ya test etmək çətin olur – çox kodu dəyişmək lazımdır.
- Yeni tipləri əlavə etmək olmur, bir çox yerdə düzəliş etmək lazım gəlir.
- Modullar bir-birinə çox "bağlı" olur.
Əksinə – əgər sistemi əvvəldən interfeyslərlə qurub, parametrləri onlar vasitəsilə ötürsən, zəif bağlılıq və çeviklik əldə etmək olur.
Əsas məsləhət: modullar arasında əlaqəni konkret realizasiyalar yox, kontraktlar kimi düşünməyə çalış.
GO TO FULL VERSION