1. Giriş
Sən kompüterdən və ya smartfondan istifadə edirsən — hər gün, heç düşünmədən. Brauzeri açdın, sayta girdin, şəkil çəkdin. Sənə prosessorun necə qurulduğu, operativ yaddaşın necə işlədiyi və ya mikrosxemlərdə hansı siqnalların qaçdığı maraqlı deyil. Bu istifadəçi səviyyəsidir — rahat, intuitiv, səndən artıq şeyləri gizlədir.
Bəs nəsə düz getməsə? Tutaq ki, tətbiq açılmır. Onu yenidən quraşdırmaq üçün artıq bir az daha çox bilik lazımdır — ən azı haradan yükləmək və necə quraşdırmaq. Bu artıq başqa abstraksiya səviyyəsidir — sistem, texniki. Əgər problem ümumiyyətlə dəmirdədirsə — məsələn, yaddaş qurğusu və ya ana plata sıradan çıxıbsa — burada artıq cihazın fiziki quruluşunu başa düşmədən keçinmək olmur.
Abstraksiya səviyyələri çox ola bilər və hər biri arxasında olan mürəkkəbliyi gizlədir, sənə isə yalnız həqiqətən lazım olanı təqdim edir.
Proqramlaşdırmada da hər şey eyni cürdür. Təsəvvür elə, taksiyə minirsən və deyirsən: «Proqramçılar küçəsi, 42-ci evə». Səni maraqlandırmır ki, sürücü hansı marşrutla gedəcək, sürət qutusunu necə dəyişəcək və ya bakda hansı yanacaq var. Sənə əsas olan yerinə çatmaqdır. Qalan hər şey — gizlidir. Bu tənbəllik deyil, saf abstraksiyadır: sən sistemlə aydın interfeys vasitəsilə işləyirsən, reallaşdırma detallarına baş vurmadan.
Başqa bir nümunə — telefonundakı kamera. İkonaya toxunursan, şəkil çəkirsən və o, qalereyada görünür. Sənə lazım deyil bilmək ki, işıq linzadan necə keçir, matris və çip necə işləyir, məlumat yaddaşa necə düşür. Bütün bunlar rahat interfeys qatının altında gizlənir. Bu da abstraksiyadır — güclü alətdən istifadə etmək imkanı, daxili quruluşunu bilmədən.
Proqramlaşdırma dünyasında, xüsusilə C# və .NET-də, abstraksiya sadəcə rahatlıq deyil, həyati zərurətdir. Onsuz böyük, başa düşülən, dəstəklənən layihələr qurmaq çətindir. O, proqramçılara bir dildə danışmağa imkan verir, hər modulun reallaşdırma detallarında batmadan.
2. Bəs bu abstraksiya bizə proqramlaşdırmada niyə lazımdır?
"Yaxşı, – deyəcəksən, – ağıllı səslənir, amma mənə, gələcəyin kod guru-suna, bu niyə lazımdır?" Əla sualdır! Abstraksiya abstraksiya üçün deyil, tam konkret, çox praktik üstünlüklər üçün lazımdır:
- Mürəkkəb sistemlərin sadələşdirilməsi: Beynimiz eyni anda böyük proqramın bütün detallarını yadda saxlaya bilmir. Abstraksiya bizə mürəkkəb tapşırığı daha kiçik, idarə olunan hissələrə bölməyə imkan verir. Hər hissə öz daxili mürəkkəbliyini "gizlədir", bizə isə yalnız həqiqətən vacib olanı təqdim edir. Bu, Lego konstruktora bənzəyir: hər detal özü-özlüyündə sadədir, amma onlardan istənilən şeyi yığmaq olar, hər bir kubun necə hazırlandığını düşünmədən.
- Kodun oxunaqlılığının və dəstəklənməsinin artırılması: Abstraksiya prinsiplərinə əsaslanan kod oxumaq və başa düşmək çox asan olur. device.TurnOn() metodunun çağırışını görəndə, dərhal onun məqsədini anlayırsan, yüzlərlə sətri oxumadan ki, bu konkret olaraq lampada və ya ventilyatorda necə işləyir. Bu isə kodu səhvlər üçün düzəltməyi və yeni funksiyalar əlavə etməyi asanlaşdırır.
- Modullar arasında asılılığın azaldılması: Təsəvvür elə, kodun fənəri yandırmaq üçün birbaşa bir modelə aid aşağı səviyyəli əməliyyatlara müraciət edir. Bəs bu modeli başqa bir modelə dəyişmək istəsən? Bütün kodu yenidən yazmaq lazım olacaq! Abstraksiya isə sənə "istənilən fənərlə" ümumi interfeys vasitəsilə işləməyə imkan verir. Fənərin "içini" dəyişsən, onu yandıran kod heç "hiss etməyəcək". Çünki o, abstrakt təqdimatla işləyir.
- Çeviklik və ağrısız dəyişiklik imkanı: Abstraksiya sayəsində, hər hansı komponentin daxili reallaşdırmasını dəyişə bilərsən, proqramın qalan hissəsinə toxunmadan. Bu, böyük layihələrdə əvəzsizdir, çünki inkişaf komandaları sistemin müxtəlif hissələri üzərində eyni vaxtda işləyə bilər, bir-birinə mane olmadan.
- Məsuliyyətin bölüşdürülməsi: Proqramının hər elementi (sinif, metod) öz dəqiq müəyyən olunmuş məsuliyyətini alır. LightBulb sinifi öz funksionallığına cavabdehdir, SmartHomeManager isə cihazların idarəsinə cavabdehdir, onların xırda detallarını bilmədən.
Abstraksiya — bu, proqramına "əlavə etdiyin" bir şey deyil. Bu daha çox düşüncə tərzidir kod dizaynında. Bu, ümumi cəhətləri görmək və fərqləri gizlətmək bacarığıdır.
3. Abstraksiya C#-da (və ümumiyyətlə OOP-də) necə görünür?
İnanmayacaqsan, amma biz artıq abstraksiyadan istifadə edirik, adını çəkmədən! O, C# proqramlarımızda müxtəlif səviyyələrdə özünü göstərir:
Siniflər və Obyektlər
Sinif anlayışı özü artıq abstraksiyadır. LightBulb sinifi "lampa" ideyasını abstraktlaşdırır, hansı ki, yandırıla, söndürülə və müəyyən parlaqlıq səviyyəsinə malik ola bilər. LightBulb myLamp = new LightBulb(); obyektini yaradanda, biz bu abstraksiya ilə işləyirik, konkret elektron və atom dəstəsi ilə yox.
Nümunə: Bizim LightBulb sinifinə bax. Onun TurnOn() metodu var. myLamp.TurnOn() çağırırsan və lampa yanır. Amma sən birbaşa elektrikə nəzarət edən, mikroskopik klapanları açan və ya spiralda termonüvə reaksiyası başladan kod yazmırsan (zarafat!). Bütün bu detallar TurnOn() metodunun içində gizlidir.
Girişə nəzarət modifikatorları: private sahə və metodlardan istifadə — bu, birbaşa enkapsulyasiyanın təzahürüdür, hansı ki, abstraksiyaya nail olmağın ən vacib yollarından biridir. Biz bəzi məlumat və əməliyyatları çöldən əlçatmaz edirik, bununla da istifadəçini sinifin daxili mürəkkəbliyindən "abstraktlaşdırırıq". Məsələn, bank tətbiqində _updateBalance() metodu (private, alt xəttlə, daxili detal olduğunu göstərmək üçün) balansı yeniləmək üçün mürəkkəb məntiqi yerinə yetirə bilər, amma xarici dünya üçün yalnız Deposit() və ya Withdraw() açıqdır. Bu da abstraksiyadır.
Metodlar və Funksiyalar
Hər metod çağırışı — abstraksiyadan istifadədir. Sən metoda hansısa işi görməyi tapşırsan, amma onun bunu necə edəcəyini düşünmürsən.
Məsələn, bizim köhnə dostumuz Console.WriteLine("Salam, dünya!"); yadındadır? Sadəcə bu metodu çağırırsan və gözləyirsən ki, mətn ekranda görünsün. Sənə lazım deyil bilmək: əməliyyat sistemi yaddaş buferini necə ayırır, şriftlər pikselə necə çevrilir, qrafik adapter onları monitora necə çıxarır?
Razılaş ki, bunların hamısını düşünməli olsaydın, hər qısa proqram saatlarla vaxt aparardı.
Console.WriteLine — güclü abstraksiyadır. O, arxasında böyük bir işi gizlədir.
İrsiyyət və Polimorfizm
Burada abstraksiya tam parlayır! Baza Animal sinifi və onun törəmələri Dog və Cat yaradanda, biz "heyvan" anlayışını abstraktlaşdırırıq, hansı ki, "səs çıxara" bilir.
Məsələn, yazırsan Animal myPet = new Dog(); və sonra myPet.MakeSound();. Burada sən Animal abstraksiyası ilə işləyirsən. Sən "abstraktlaşdırmısan" ki, myPet əslində Dog-dur. Polimorfizm bu abstrakt MakeSound() çağırışının müxtəlif konkret tiplər üçün fərqli işləməsinə imkan verir (it hürür, pişik miyoldayır). Sən "nə etmək" (səs çıxarmaq) proqramlaşdırmısan, "necə etmək" isə konkret siniflərə buraxmısan. Bu, abstraksiyanın təmiz qələbəsidir!
İnterfeyslər (hələlik sadəcə qeyd, ətraflı sonra olacaq)
Biz hələ onları öyrənməmişik, amma yadında saxla: C#-da interfeyslər — "nə, amma necə yox" abstraksiyasını ifadə etməyin ən təmiz yoludur. İnterfeys — əslində müqavilədir, hansı ki, metodlar, xüsusiyyətlər və ya hadisələr toplusunu təsvir edir, amma onların heç bir reallaşdırmasını vermir. O deyir: "Kim bu interfeysi reallaşdırırsa, mütləq bunu və bunu bacarmalıdır". Biz bunları 111-ci mühazirədə ətraflı danışacağıq, amma bil ki: bu, C#-da abstraksiyanın zirvəsidir.
Abstrakt siniflər (bu da sadəcə qeyd, ətraflı növbəti mühazirədə)
Abstrakt siniflər — adi siniflərlə interfeyslər arasında bir şeydir. Onlar həm reallaşdırılmış metodlar, həm də abstrakt metodlar (105-ci mühazirədə qısa gördük) saxlaya bilər, hansı ki, bədənə malik deyil və mütləq törəmə siniflərdə reallaşdırılmalıdır. Abstrakt siniflər ümumi skelet, ümumi funksionallıq dəsti yaratmaq üçün istifadə olunur, amma "dəliklər" (abstrakt metodlar) saxlayır ki, onları konkret reallaşdırmalar doldurmalıdır. Növbəti mühazirədə bunları çox ətraflı danışacağıq!
Beləliklə, yekunlaşdırsaq: abstraksiya — bu, C#-da hansısa bir sintaksis elementi deyil. Bu, kodumuzun bütün səviyyələrini əhatə edən güclü bir anlayışdır, sadə metodlardan tutmuş mürəkkəb sinif iyerarxiyalarına qədər.
4. Kod nümunəsi: ağıllı ev idarəetmə sistemi
Gəlin tətbiqimizə qayıdaq və baxaq, abstraksiya onu necə daha çevik edir. Təsəvvür elə, "Ağıllı ev" sistemi qururuq. Əvvəlcə sadəcə lampalar və ventilyatorlarımız var:
public class LightBulb
{
public string Name;
public LightBulb(string name) => Name = name;
public void TurnOn() => Console.WriteLine($"{Name}: işıq yandı");
public void ChangeBrightness(int level) => Console.WriteLine($"{Name}: parlaqlıq {level}%");
}
public class Fan
{
public string Name;
public Fan(string name) => Name = name;
public void TurnOn() => Console.WriteLine($"{Name}: ventilyator işə düşdü");
public void AdjustSpeed(int speed) => Console.WriteLine($"{Name}: sürət {speed}");
}
class Program
{
static void Main()
{
var lamp = new LightBulb("Mətbəx");
var fan = new Fan("Yataq otağı");
lamp.TurnOn();
lamp.ChangeBrightness(75);
fan.TurnOn();
fan.AdjustSpeed(3);
}
}
Bu kodda hər şey əla işləyir, hər cihazla ayrıca işlədiyimiz müddətcə. Amma bəs istəsək ki, ağıllı evimiz həqiqətən ağıllı olsun və bütün cihazları mərkəzləşdirilmiş şəkildə idarə edək? Məsələn, evə gəlməzdən əvvəl bütün cihazları yandıraq?
Əgər object[] yaradıb cihazlarımızı orada saxlasaq, problem çıxacaq: object tipi TurnOn() metodundan xəbərsizdir. Onu çağırmaq üçün hər obyektin tipini yoxlayıb uyğun tipe çevirməliyik, bu isə çox əziyyətli və çirkin olur:
// abstraksiya və polimorfizm olmadan:
foreach (object device in allDevices)
{
if (device is LightBulb bulb)
{
bulb.TurnOn();
}
else if (device is Fan fan)
{
fan.TurnOn();
}
// Hər yeni cihaz tipi üçün belə davam... dəhşət!
}
Burada səhnəyə irsiyyət və polimorfizm çıxır, hansılar ki, enkapsulyasiya ilə birlikdə abstraksiyaya nail olmaq üçün alətlərdir. Gəlin SmartDevice baza sinifi yaradaq, hansı ki, "ağıllı cihaz" anlayışını abstraktlaşdıracaq və lampanı, ventilyatoru ondan törədək.
class SmartDevice
{
public string Name;
public SmartDevice(string name) => Name = name;
public virtual void TurnOn() => Console.WriteLine($"{Name}: cihaz işə düşdü");
public virtual void TurnOff() => Console.WriteLine($"{Name}: cihaz söndü");
}
class LightBulb : SmartDevice
{
public LightBulb(string name) : base(name) { }
public override void TurnOn() => Console.WriteLine($"{Name}: işıq yandı");
public override void TurnOff() => Console.WriteLine($"{Name}: işıq söndü");
public void ChangeBrightness(int x) => Console.WriteLine($"{Name}: parlaqlıq {x}%");
}
class Fan : SmartDevice
{
public Fan(string name) : base(name) { }
public override void TurnOn() => Console.WriteLine($"{Name}: ventilyator işə düşdü");
public override void TurnOff() => Console.WriteLine($"{Name}: ventilyator söndü");
public void AdjustSpeed(int s) => Console.WriteLine($"{Name}: sürət {s}");
}
class Program
{
static void Main()
{
SmartDevice[] devices =
{
new LightBulb("Mətbəx"),
new Fan("Yataq otağı"),
new SmartDevice("Sensor")
};
foreach (var d in devices) d.TurnOn();
foreach (var d in devices) d.TurnOff();
// Xüsusi metodların çağırılması nümunəsi
foreach (var d in devices)
{
if (d is LightBulb b)
b.ChangeBrightness(50);
if (d is Fan f)
f.AdjustSpeed(2);
}
}
}
Bax, kodumuz nə qədər təmiz və çevik oldu! İndi smartHomeDevices-ə istənilən yeni cihaz tipi əlavə edə bilərik (məsələn, SmartTV, SmartThermostat), hansı ki, SmartDevice-dən irs alır və bizim foreach (SmartDevice device in smartHomeDevices) dövrümüz dəyişmədən işləyəcək. Bu, abstraksiyanın əsl gücüdür. Biz konkret cihaz tipindən abstraktlaşdıq, onun "işə düşmək" və "sönmək" ümumi bacarığına fokuslandıq.
Bu nümunə aydın göstərir ki, irsiyyət və polimorfizm, hansıları ki, əvvəllər öyrəndik, abstraksiyaya nail olmaq üçün alətlərdir. Biz ümumiləşdirilmiş təqdimat (SmartDevice) yaratdıq, hansı ki, müxtəlif konkret cihazlarla (LightBulb, Fan) eyni cür işləməyə imkan verir.
Amma bir nüans var: bizim indiki SmartDevice-də TurnOn() və TurnOff() metodlarının "ümumi reallaşdırması" var, hansı ki, sadəcə "Cihaz işə düşdü/söndü (ümumi reallaşdırma)" çıxarır. Bəs əgər bütün cihazlar üçün mənalı "ümumi reallaşdırma" yoxdursa? Məsələn, "ümumi cihaz" (SmartDevice birbaşa) — sadəcə temperatur sensorudur, onun "ON/OFF" düyməsi yoxdur. Və ya istəsək ki, bütün törəmə sinifləri bu metodların öz reallaşdırmasını mütləq versin? Məhz burada abstrakt siniflər və abstrakt metodlar köməyə gəlir, hansılar ki, növbəti mühazirədə ətraflı danışacağıq. Onlar abstraksiya prinsipini tətbiq etməyin daha güclü yoludur, bəzi metodların mütləq törəmələrdə reallaşdırılmasını təmin edir.
Bununla OOP-nin fundamental prinsipi kimi abstraksiyaya səyahətimiz bitdi. Növbəti mühazirədə C#-ın bizə xüsusi alətlər — abstrakt siniflər və abstrakt metodlar — verdiyini və bu konsepsiyanı kodda məcburi tətbiq etməyə necə imkan verdiyini öyrənəcəyik. Hazır ol, daha maraqlı olacaq!
GO TO FULL VERSION