كثيرا ما نواجه مشكلة عند تمثيل كيان معين في الكود، مثل شخص أو سيارة. لو أردنا تمثيل شخص، ننشئ variables منفصلة: اسم، عمر، وجندر.
String name = "John";
int age = 30;
String gender = "male";
المشكلة تظهر عندما نريد إضافة شخص ثانٍ. لازم ننشئ variables بأسماء مختلفة لأنه ما فينا نكرر نفس الاسم في نفس الـ scope.
String name2 = "Jane";
int age2 = 25;
String gender2 = "female";
وكل ما زاد عدد الأشخاص، زاد التكرار والفوضى. الحل الأمثل هو استخدام الـ class.
الـ class هو عبارة عن blueprint، أي مخطط. فكر فيه كمخطط بناء: يمكنك بناء عدد غير محدود من الأبنية بناءً على نفس المخطط، وكل بناء ممكن يختلف في اللون أو الشكل، لكن المخطط الأساسي واحد.
بنفس المبدأ في الـ Object Oriented Programming، أي شيء تمثله برمجيا هو object، وهو عبارة عن instance من class.
الـ class يحمل نوعين من المحتوى:
| النوع | الوصف |
|---|---|
| Attributes | المتغيرات (variables) التي تصف الكيان |
| Methods | الدوال (functions) التي تمثل سلوكه |
لإنشاء class جديد في Java، نعمل right click على المجلد src، ثم New Java Class. اسم الـ class يفضل دائما يكون مفرد (singular)، يعني وليس .
اشترك في النشرة البريدية
دروس جديدة، مقالات، وأدوات مباشرة لبريدك.
CarCarspublic class Car {
public String brand;
public String color;
public int speed;
}هذه الـ attributes هي variables تخص الـ class. public تعني أن أي مكان في الكود يقدر يصل إليها ويعدلها.
لاستخدام الـ class، نروح على ملف main وننشئ instance باستخدام الكلمة new.
Car myCar = new Car();الـ Car هنا هو الـ type، تماما كما نكتب String أو int. وكلمة new هي اللي تنشئ نسخة جديدة من الـ class.
يمكننا تحديد قيم افتراضية مباشرة داخل الـ class:
public class Car {
public String brand = "Toyota";
public String color = "Red";
public int speed = 0;
}ثم نطبع القيم من الـ main:
System.out.println(myCar.brand); // Toyota
System.out.println(myCar.color); // Red
System.out.println(myCar.speed); // 0يمكننا تغيير القيم الافتراضية من خارج الـ class عن طريق الـ dot notation:
myCar.brand = "Toyota";
myCar.color = "Yellow";
myCar.speed = 0;الـ constructor هو function خاص دائما اسمه نفس اسم الـ class. يشتغل أوتوماتيكيا في اللحظة التي ننشئ فيها instance جديدة باستخدام new.
public class Car {
public String brand;
public String color;
public int speed;
public Car(String brand, String color, int speed) {
System.out.println(brand);
}
}عندما ننشئ instance من الـ main:
Car myCar = new Car("Toyota", "Red", 0);لو نسينا الـ arguments، الـ editor يعطينا خطأ: expected 3 arguments but got 0.
الـ constructor يستقبل الـ brand والـ color والـ speed بالترتيب. لكن لو عملنا print لـ myCar.brand بعدها، سيعطينا null، لأننا ما عملنا assign للـ variable الموجود داخل الـ class.
this في الـ Constructorthis تشير إلى الـ class الحالي بأكمله. باستخدامها نعمل assign للـ attributes من داخل الـ constructor:
public Car(String brand, String color, int speed) {
this.brand = brand;
this.color = color;
this.speed = speed;
}الكود هذا يقول: "خذ القيمة اللي وصلت من الـ argument، وحطها في الـ variable الموجود داخل الـ class".
الآن إذا طبعنا:
Car myCar = new Car("Toyota", "Red", 0);
System.out.println(myCar.brand); // Toyota
System.out.println(myCar.color); // Red
System.out.println(myCar.speed); // 0الميزة الأساسية للـ class هي إنشاء instances متعددة، كل واحدة بقيمها الخاصة:
Car myCar = new Car("Toyota", "Red", 0);
Car myCar2 = new Car("Ford", "Yellow", 0);
System.out.println(myCar.color); // Red
System.out.println(myCar2.color); // Yellowالـ variables داخل كل instance منفصلة كليا عن الأخرى. ما في أي تعارض في الأسماء.
الـ getter هو method غرضها إرجاع قيمة attribute معين. التسمية الشائعة هي get يليها اسم الـ variable.
public String getBrand() {
System.out.println("getting brand type");
return brand;
}الـ return type يجب يتطابق مع نوع الـ variable الذي نرجعه. هنا brand هو String، إذا الـ return type يجب يكون String.
للاستخدام من الـ main:
myCar.getBrand();أو يمكننا حفظ القيمة في variable:
String brand = myCar.getBrand();
System.out.println(brand); // Toyotaالـ setter هو method غرضها تعديل قيمة attribute. الـ return type يكون void لأننا ما نرجع أي شيء.
public void setBrand(String brand) {
this.brand = brand;
}الفرق بين الـ constructor والـ setter:
| الـ Constructor | الـ Setter | |
|---|---|---|
| التوقيت | يشتغل مرة واحدة عند الإنشاء | يشتغل متى أردنا |
| الغرض | تحديد القيم الابتدائية | تعديل القيم لاحقا |
مثال على استخدام الـ setter:
Car myCar = new Car("Toyota", "Red", 0);
System.out.println("old brand: " + myCar.getBrand()); // Toyota
myCar.setBrand("Ford");
System.out.println("new brand: " + myCar.getBrand()); // Fordالـ setter أخذ القيمة من الـ argument، مررها إلى الـ parameter، وعمل update لـ this.brand داخل الـ class.
يمكننا كتابة methods تمثل أفعال الكيان. مثلا، method لتسريع السيارة وأخرى لإيقافها:
public void accelerate(int increment) {
this.speed += increment;
System.out.println("accelerated, current speed: " + speed + " km/h");
}
public void stop() {
this.speed = 0;
System.out.println("car has stopped");
}من الـ main:
myCar.accelerate(10);
myCar.accelerate(20);
myCar.accelerate(30);
myCar.accelerate(40);
myCar.accelerate(100);
myCar.stop();النتيجة:
accelerated, current speed: 10 km/h
accelerated, current speed: 20 km/h
accelerated, current speed: 30 km/h
accelerated, current speed: 40 km/h
accelerated, current speed: 100 km/h
car has stopped
كل مرة يشتغل accelerate، يأخذ الـ increment من الـ argument ويضيفه لـ speed الموجود داخل الـ class. وعند تشغيل stop، يرجع الـ speed إلى 0.
بهذا الشكل قدرنا نحقق مبدأ الـ encapsulation، أي تغليف الكود. وضعنا كل الـ logic المتعلق بالسيارة داخل الـ Car class، ونستخدمه من الـ main بشكل بسيط ومرتب دون الحاجة لتكرار الكود.
هذه هي أساسيات الـ classes في Java. في دروس قادمة سنتحدث عن الـ access modifiers كـ public و private وكثير من المفاهيم الأخرى.