Klasat në Dart

Dart është gjuhë e bazuar në OOP. Të gjitha klasat në Dart janë pasardhës të klasës Object pa pasur nevojë të ekstendohen në mënyrë eksplicite.

Klasa Object që është superklasa e të gjitha klasave posedon variablat anëtare: hashCode dhe runtimeType, metodat: toString() dhe noSuchMethod(), si dhe operatorin ==.

Krijimi i një klase:

class Personi {
  
}

Deklarimi i variablave anëtare (member variables, properties) bëhet duke e specifikuar patjetër tipin (int, String,. etj), jo me var, nëse krahas deklarimit nuk e bëjmë edhe inicializimin e tyre.

class Personi {
  String emri;
  int mosha;
  double pesha;
}

Nëse e inicializojmë anëtarin në një vlerë të caktuar, mund të shërbehemi edhe me var:

class Personi {
  String emri;
  var mosha = 30;
  double pesha;
}

Instancimi i klasës

Klasën e instancojmë me new.

class Personi {
  String emri;
  int mosha = 30;
  double pesha;
}

void main() {
  var p = new Personi();
  print(p.emri); // null
  print(p.mosha); // 30
}

Siç shihet, anëtari i painicializuar e ka vlerën null.

Instancimi mund të bëhet edhe pa new:

void main() {
  var p = Personi();
  print(p.mosha);
}

Konstruktori

Konstruktori definohet si funksion me emër të njëjtë si klasa. Si parametra të konstruktorit i cekim anëtarët të cilët mund t’i inicializojmë gjatë instancimit të klasës.

class Personi {
  String emri;
  int mosha = 30;
  double pesha;

  Personi({this.emri, this.mosha, this.pesha});
}


void main() {
  var p = Personi(emri: "Dart", mosha:45, pesha: 75);
  print(p.emri); // Dart
  print(p.mosha); // 45
  print(p.pesha); // 75
}

Anëtarët janë të ndryshueshëm, kështu që mund t’i ndryshojmë edhe pas instancimit të klasës dhe inicializimit me vlera fillestare:

void main() {
  var p = Personi(emri: "Dart", mosha:45, pesha: 75);
  p.emri = "Flutter";
  print(p.emri); // Flutter
}

Nëse dëshirojmë që vlerat e dhëna gjatë instancimit të klasës të jenë të pandryshueshme (immutable), atëherë para deklarimit të anëtarëve e vendosim fjalën final.

class Personi {
  final String emri;
  final int mosha = 30;
  final double pesha;

  Personi({this.emri, this.mosha, this.pesha});
}

void main() {
  var p = Personi(emri: "Dart", mosha: 45, pesha: 75);
  print(p.emri); // Dart
}

Çdo tentimi i ndryshimit të vlerës së anëtarit pas instancimit do të rezultojë në gabim gjatë kompajlimit.

void main() {
  var p = Personi(emri: "Dart", mosha: 45, pesha: 75);
  p.emri = "Flutter";
  print(p.emri);
}

Raporti i gabimit:

Error: The setter 'emri' isn't defined for the class 'Personi'.
 - 'Personi' is from 'klasat.dart'.
Try correcting the name to the name of an existing setter, or defining a setter or field named 'emri'.

Metodat

Metodat janë funksionet e vendosura brenda klasës. Për t’iu qasur varaiblave anëtare të klasës, metodave nuk u nevojiten parametrat, sepse atyre mund t’i qasen drejtpërsëdrejti.

class Personi {
  final String emri;
  final int mosha;
  final double pesha;

  Personi({this.emri, this.mosha, this.pesha});

  String Prezantohu() {
    return "Unë jam ${this.emri}, jam ${mosha} vjeç dhe kam ${pesha} kg";
  }
}

void main() {
  var p = Personi(emri: "Dart", mosha: 45, pesha: 75);
  print(p.Prezantohu()); // Unë jam Dart, jam 45 vjeç dhe kam 75.0 kg

}

Inheritance

Me inheritance trashëgohen variablat anëtare dhe metodat nga superklasa, në rastin konkret klasa është Studenti, ndërsa superklasa Personi.

class Personi {
  final String emri;
  final int mosha;
  final double pesha;

  Personi({this.emri, this.mosha, this.pesha});

  String Prezantohu() {
    return "Unë jam ${this.emri}, jam ${mosha} vjeç dhe kam ${pesha} kg";
  }
}

class Studenti extends Personi {
  final int nota;
  Studenti({String emri, int mosha, double pesha, this.nota}) : super(emri: emri, mosha: mosha, pesha: pesha);
}

void main() {
  var p = Studenti(emri: "Dart", mosha: 45, pesha: 75, nota: 9);
  print(p.Prezantohu()); // Unë jam Dart, jam 45 vjeç dhe kam 75.0 kg

}

Tash nga objekti p që është instancë e klasës Studenti mund ta thërrasim metodën Prezantohu() e cila gjendet në klasën Personi.

Në këtë shembull, gjatë instancimit të klasës Studenti japim si parametra variablat anëtare të klasës Personi (emri, mosha, pesha) si dhe variablin anëtar nota që i përket klasës Studenti. Për t’i bartur këta parametra në superklasën Personi, e thirrim superklasën me super() nga konstruktori i klasës Studenti:

Studenti({String emri, int mosha, double pesha, this.nota}) : super(emri: emri, mosha: mosha, pesha: pesha);

Në këtë mënyrë, metoda Prezantohu() në klasën Personi ka qasje në vlerat e parametrave emri, mosha dhe pesha të dhëna gjatë instancimit të klasës Studenti.

__toString() metoda

void main() {
  var p = Studenti(emri: "Dart", mosha: 45, pesha: 75, nota: 9);
  print(p); // Instance of 'Studenti'
}

Nëse e “printojmë” objektin, invokohet metoda __toString() e klasës Object, me ç’rast shfaqet një rresht me tekst, me përmbajtje p.sh. : Instance of ‘Studenti’. Klasa Studenti e trashëgon klasën Personi, klasa Personi e trashëgon klasën Object, prandaj klasa Studenti ka akses në metodën __toString() të klasës Object, e cila metodë invokohet kur përdoret funksioni print(). Në këtë rast, tregohet vetëm objekt i cilës klasë është objekti p.

Polymorphism

Për t’ia ndryshuar veprimin metodës __toString() në klasën Personi, duhet ta redefinojmë metodën toString() në klasën Personi. Këtë e shprehim në mënyrë eksplicite duke përdorur @override.

class Personi {
  final String emri;
  final int mosha;
  final double pesha;

  Personi({this.emri, this.mosha, this.pesha});

  @override
  String toString() {
    return "Unë jam ${this.emri}, jam ${mosha} vjeç dhe kam ${pesha} kg";
  }
}

class Studenti extends Personi {
  final int nota;
  Studenti({String emri, int mosha, double pesha, this.nota}) : super(emri: emri, mosha: mosha, pesha: pesha);
}

void main() {
  var p = Studenti(emri: "Dart", mosha: 45, pesha: 75, nota: 9);
  print(p); 
}

Rezultati në konzolë:

Unë jam Dart, jam 45 vjeç dhe kam 75.0 kg

Pra, tash kemi bërë polimorfizëm – ndryshim të veprimit (sjelljes) së një metode në nënklasë.

Redefinimin e metodës toString() mund ta bëjmë edhe në nënklasën Studenti, kështu që toString() jep një rezultat kur thirret nga objekti i instancuar nga klasa Personi, e tjetër rezultat kur thirret nga objekti i instancuar nga klasa Studenti.

class Personi {
  final String emri;
  final int mosha;
  final double pesha;

  Personi({this.emri, this.mosha, this.pesha});

  @override
  String toString() {
    return "Unë jam ${this.emri}, jam ${mosha} vjeç dhe kam ${pesha} kg";
  }
}

class Studenti extends Personi {
  final int nota;
  Studenti({String emri, int mosha, double pesha, this.nota}) : super(emri: emri, mosha: mosha, pesha: pesha);

  @override
  String toString() {
    return "Unë jam studenti ${this.emri}, jam ${mosha} vjeç dhe kam ${pesha} kg";
  }
}

void main() {
  var p = Personi(emri: "Dart", mosha: 45, pesha: 75);
  print(p);

  var s = Studenti(emri: "Dart", mosha: 45, pesha: 75, nota: 9);
  print(s);
}

Rezultati në konzolë:

Unë jam Dart, jam 45 vjeç dhe kam 75.0 kg
Unë jam studenti Dart, jam 45 vjeç dhe kam 75.0 kg

Abstraction

Klasë abstrakte quhet ajo klasë e cila përmban disa veti dhe metoda, pa specifikuar mënyrën e implementimit të tyre.

Klasa abstrakte nuk mund të instancohet.

void main() {
  final figura = FiguraGjeometrike();
}

abstract class FiguraGjeometrike {
  double Siperfaqja();
}

Gabimi i raportuar në konzolë:

Error: The class 'FiguraGjeometrike' is abstract and can't be instantiated.

Metoda Siperfaqja() nuk ka “trup”, gjegjësisht kod sikurse që kanë metodat/funksionet e rëndomta në formën:

  double Siperfaqja() {
    // Kodi
  }

Një klasë abstrakte mund të përmbajë edhe metoda konkrete, pra metoda që kanë “trup”.

Për aq kohë sa klasa është e definuar si abstrakte, nuk mund të instancohet edhe nëse të gjitha metodat i ka konkrete.

Për ta instancuar një klasë abstrakte, duhet ta ekstendojmë në një klasë konkrete, duke i zëvendësuar të gjitha metodat abstrakte me metoda konkrete.

Tahir Hoxha