قسمت های دیگر این مطلب:

در قسمت قبل به صورت کاملا مفهومی و پایه‌ای با مبحث شی گرایی آشنا شدید. این مطلب در ادامه مطلب قبل و با هدف تکمیل اطلاعات شما با مبحث شی گرایی تهیه شده است.

ارث بری در شی گرایی

خاصیت ارث بری در شی گرایی بسیار کاربردی است. برای ارث بری از کلمه کلیدی extends استفاده می‌شود و همانطور که می‌دانید حتی کلاس MainActivity پروژه اندروید نیز خود extends شده از کلاسی دیگر است. این کلاس را مشاهده کنید:

public class MainActivity extends AppCompatActivity {

}

در سطر اول عبارت MainActivity extends AppCompatActivity به این معناست که کلاس MainActivity از کلاس AppCompatActivity ارث بری کرده است. در واقع کلاس AppCompatActivity والد و یا Super Class، کلاس MainAcitivity است.

کلاس MainActivity را فرزند AppCompatActivity نیز می‌نامند.

برای بررسی عملکرد ارث بری کلاس Car را مانند زیر ایجاد کنید:

package ir.hitos.hitos;

public class Car {
    public static String public_static_string;
    private String private_string;
    protected String protected_string;
    String default_string;
}

یک کلاس جدید با نام BMW در پکیج جاری کلاس Car با سورس زیر ایجاد کنید:

package ir.hitos.hitos;

public class BMW extends Car{
    String str1 = public_static_string;
    String str2 = protected_string;
    String str3 = default_string;
}

همانطور که در بالا می‌بینید نیازی به ایجاد یک شی جدید از Car و یا حتی ذکر نام Car قبل از استفاده از متغیرها نیست.

در بالا تنها از متغیر private نمی‌توانید استفاده کنید چون این متغیر تنها در کلاس Car قابل استفاده است.

یک کلاس با نام Benz در package دیگری با نام test.hitos و سورس زیر ایجاد کنید:

package test.hitos;

import ir.hitos.hitos.Car;

public class Benz extends Car{
    String str1 = public_static_string;
    String str2 = protected_string;
}

در سطر 7 می‌توانیم از متغیر protected استفاده کنیم چون متغیرهای protected در پکیج جاری خود و تمام کلاس‌های فرزند قابل استفاده هستند.در مکان فوق نمی‌توان از متد default استفاده کرد چون این متد تنها در پکیج خودش قابل استفاده است.

متد سازنده یا Contractor

برای ایجاد یک متد سازنده یک متد با نام کلاس درون بدنه کلاس ایجاد می‌کنید. پس از این، هر گاه یک نمونه از کلاس شما ایجاد شود این متد به صورت خودکار اجرا خواهد شد.

به عنوان مثال در کلاس Car نیاز داریم هر وقت یک شی از کلاس Car ایجاد شد نام ماشین نیز باید معین شود. کلاس Car را به شکل زیر ببینید:

package ir.hitos.hitos;
import android.util.Log;

public class Car {
    public String name;
    public Car(String name){
        this.name = name;
        Log.i("oop_message", "constructor from Car class");
    }
}

در سطر پنج یک متغیر از نوع string تعریف کردیم که نام آن name است.

در سطر شش یک متد می‌بینید که همنام کلاس است و یک ورودی به نام name دارد.

در سطر هفتم عبارت this.name را برابر name قرار دادیم. عبارت this.name اشاره به name موجود در سطر پنجم کلاس دارد و name بعد از مساوی اشاره ورودی تابع سازنده است.

در سطر هشت یک Log ایجاد کردیم تا در صورت اجرا شدن این متد به ما گزارش دهد.

کلاس MainActivity را نیز به شکل زیر ویرایش می‌کنیم:

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Car car = new Car("BMW");
        Log.i("oop_testing car model: ", car.name);
    }
}

همانطور که در سطر ششم می‌بینید یک شی جدید به نام car ایجاد کردیم و در آخرین پرانتز ورودی BMW را به عنوان ورودی این شی وارد کردیم. در واقع این ورودی به متد سازنده ارسال می‌گردد و در کلاس Car متغیر name کلاس را مقدار دهی می‌کند.

در سطر هفتم یک Log ایجاد کردیم تا متغیر name را از کلاس car دریافت کرده و در گزارش‌ها چاپ کند.

پس از اجرای کدهای فوق خواهید دید دو Log ایجاد می‌شود. لاگ اول اجرا شدن متد سازنده را گزارش می‌دهد و لاگ دوم مقدار متغیر name کلاس Car را چاپ می‌کند.

اگر یک کلاس جدید از کلاس Car را extends کنید خواهید دید یک خط قرمز زیر نام کلاس شما تشکیل می‌شود و اشاره می‌کند که شما نا چارید متد سازنده کلاس Car را نیز فراخوانی کنید:

package ir.hitos.hitos;

public class BMW extends Car {
    public BMW(String name){
        super(name);
    }
}

در سطر چهارم می‌بینید که متدی به نام BMW ایجاد گردیده است که ورودی آن دقیقا مانند ورودی متد سازنده Car است.

عبارت super(name); به این قضیه اشاره می‌کند که عملیاتی که در اینجا روی متغیر انجام می‌پذیرد مانند آن چیزی است که در Car اتفاق می‌افتد.

می توانید بعد از این عبارت نیز دستورات دیگری را اضافه کنید. مانند زیر:

package ir.hitos.hitos;

public class BMW extends Car {
    public BMW(String name){
        super(name);
        Log.i("oop_message", "constructor from BMW class");
    }
}

اگر یک شی جدید از کلاس BMW در MainActivity فراخوانی کنید خواهید دید که دو پیام یکی از کلاس Car و دیگری از کلاس BMW برای شما نمایش داده می‌شود که نشان دهنده اجرا شدن هر دو متد است.

نکته: اگر متد سازنده ما ورودی‌ای نداشت نیاز به استفاده از این متد super و یا دادن متغیر به سازنده کلاس جدید نداریم.

مفهوم Overriding

تغییر متد والد بر اساس نیاز کلاس جدید ارث برده شده را Overriding می‌گویند. در واقع لفظ Override در شی گرایی به معنای باز نویسی است.

کلاس Car زیر را ببینید:

package ir.hitos.hitos;
import android.util.Log;

public class Car {
    public Car(){
        Log.i("oop_message", "constructor from Car class");
    }

    public void brake(){
        Log.i("oop_message", "Run method brake from Car class");
    }
}

کلاس BMW را در زیر ببینید:

package ir.hitos.hitos;
import android.util.Log;

public class BMW extends Car {

    public BMW(){
        Log.i("oop_message", "constructor from BMW class");
        brake();
    }

    @Override
    public void brake(){
        super.brake();
        Log.i("oop_message", "Run method brake from BMW class");
    }
}

در سطر 8 اعلام می‌کنیم که متد سازنده در اولین اجرای خود متد brake موجود در کلاس BMW را اجرا خواهد کرد.

در سطر 12 یک متد به نام brake داریم که هم نام متد brake کلاس Car می‌باشد. عبارت @Override که در سطر 11 آمده یک عبارت توضیحی است که نشان می‌دهد قصد Override کردن و یا بازنویسی متد را داریم.

در سطر 13 با دستور super.brake(); اعلام می‌کنیم که قبل از هر کاری باید متد brake از کلاس super اجرا گردد.

به کلاس MainActivity نیز یک شی از کلاس BMW به صورت زیر ایجاد کنید:

BMW bmw = new BMW();

لاگ‌هایی که در خروجی نمایش داده می‌شوند به ترتیب زیر هستند:

oop_message: constructor from Car class
oop_message: constructor from BMW class
oop_message: Run method brake from Car class
oop_message: Run method brake from BMW class

سطر اول: به محض ایجاد کلاس BMW از کلاس Car سازنده کلاس Car فراخوانی می‌شود.

سطر دوم: متد سازنده BMW در مرحله دوم اجرا شده و Log آن اجرا می‌شود.

سطر سوم: بدلیل فراخوانی متد brake درون سازنده کلاس BMW و وجود عبارت super.brake(); در این سازنده لاگ مخصوص به متد brake کلاس Car اجرا می‌شود.

سطر چهارم: پس از اجرا شدن متد super.brake(); لاگ موجود در متد brake کلاس BMW اجرا می‌شود.

نکته: اگر در متد brake کلاس BMW دستور super.brake(); را بعد از Log قرار دهیم جای سطر‌های سه و چهار عوض خواهد شد.

قوانین Overriding عبارتند از:

  • ورودی ها و خروجی متد Overide شده و متد اصلی باید یکسان باشند.
  • متدسازنده و متد final شده قابل Override شدن نیستند.
  • متدهای Static قابل Override شدن نیستند ولی می‌توان آن‌ها را دوباره تعریف کرد.

مفهوم Abstract چیست

همانطور که در قسمت قبل گفتیم وقتی از abstract استفاده می‌کنیم که بدانیم چه کارهایی را باید انجام بدهیم ولی مراحل عمل را حین ایجاد کردن شی جدید تعریف کنیم. مثال زیر را ببینید:

package ir.hitos.hitos;

public abstract class AbstractCar {
    public abstract void brake();
    public abstract void stop();
    public int max_speed(){
        return 180;
    }
}

در کلاس MainActivity به محض تایپ کردن AbstractCar car = new  و زدن کلیدهای ترکیبی Alt + Enter اولین گزینه همه کارها را به صورت خودکار برای شما انجام می‌دهد، و نتیجه را مانند زیر می‌بینید:

    AbstractCar car = new AbstractCar() {
            @Override
            public void brake() {

            }

            @Override
            public void stop() {

            }
    };

حال به سادگی می‌توانید عملیات موجود در متدهای brake و stop را پیاده سازی کنید، و همانطور که می‌بینید عبارت @Override به منظور Override شدن متد‌ها ذکر شده است.

نکته: طبیعتا وقتی یک کلاس از کلاس Car را extends می‌کنید نیز باید این متدها را پیاده سازی کنید.

مفهوم interface چیست

interface در واقع کلاسی از نوع abstract است که هیچ تابع غیر abstract‌ای ندارد و در تعریف آن از کلمه کلیدی class استفاده نمی‌شود. مانند:

package ir.hitos.hitos;

public interface InterfaceCar {
    abstract void speed();
}

interface را نمی‌توان extends کرد چون ساختاری متفاوت دارد و برای این منظور از implements استفاده می‌شود. مانند زیر:

package ir.hitos.hitos;

public class InterfaceBMW implements InterfaceCar{
}

وقتی یک interface را implements کردید یک خط قرمز زیر نام کلاس جدید کشیده می‌شود که با Alt + Enter می‌توانید کلیه متدها را وارد محیط Android Studio کنید.

مفهوم Overloading در شی گرایی و جاوا

نام متدهای یک کلاس همه باید منحصر به فرد باشند مگر این که ورودی آن‌ها متفاوت باشد. اگر کلاسی چند متد با یک نام ولی پارامترهای ورودی متفاوت داشت مفهوم Overloading را در باره این کلاس پیاده سازی کرده ایم.

به عنوان مثال در کلاس Car داریم:

package ir.hitos.hitos;
import android.util.Log;

public class Car {
    public Car(String name){
        Log.i("oop_message: ", "constructor number 1");
    }
    public Car(String name, int id){
        Log.i("oop_message: ", "constructor number 2");
    }
}

در مثال فوق کلاس Car دو متد سازنده دارد، متد سازنده اول که در سطر پنج قرار گرفته است یک ورودی و متد دوم که در سطر هشت قرار دارد دو ورودی دارد.

در کلاس MainActivity شی automobile را از روی کلاس Car ایجاد می‌کنیم:

Car automobile = new Car("BMW");

به نظر شما کدام Log نمایش داده می‌شود، Log متعلق به متد اول یا دوم؟ بدلیل هماهنگی نوع و تعداد ورودی شی فوق با متد اول قطعا Log متد اول اجرا می‌شود.

نکته‌ای که در این جا باید به آن اشاره کنیم این است که در overloading کلاس‌های extends شده باید مراقب بود و ورودی و خروجی‌ها را همیشه با دقت انتخاب کرد. مثلا فرض کنید کلاس BMW را از کلاس Car فوق extends کردیم:

package ir.hitos.hitos;

public class BMW extends Car {
    public BMW(String name) {
        super(name);
    }
    public BMW(String name, int id){
        super(name, id);
    }
    public BMW(){
        super("X3", 5);
    }
    public BMW(String name, int id, String max_speed){
        super(name);
    }
}

در کلاس فوق چهار متد سازنده ایجاد کردیم که به شرح زیر هستند:

متد سازنده سطر 4: این متد سازنده منطبق با متد سازنده اول کلاس Car است و یک ورودی به نام name دارد.

متد سازنده سطر 7: این متد سازنده منطبق با متد سازنده دوم کلاس Car است که دو ورودی دارد و با super دو ورودی را به کلاس Car ارجاع می‌دهیم.

متد سازنده سطر 10: این متد سازنده ورودی ندارد ولی با استفاده از super دو متغیر را به کلاس Car ارجاع دادیم، بنابراین این متد نیز منطبق با متد سازنده دوم کلاس Car است. ایجاد کردن یک شی از کلاس BMW منطبق با این متد نیز بدون ورودی است:

BMW bmw = new BMW();

متد سازنده سطر 13: این متد سازنده سه ورودی دارد ولی در super ما تنها پارامتر name را به کلاس Car ارجاع می‌دهیم پس این متد مطابق متد اول کلاس Car است.

نکته: overloading تنها برای متدهای سازنده نیست و این عملیات را می‌توان در مورد هر متدی اجرا کرد.

مفهوم چند ریختی یا Polymorphism

می توان کلاس‌های extends شده از هم را با هم تلفیق کرد، مثلا دو کلاس زیر را ببینید:

public class Pride extends Car{
}
public class Pride131 extends Pride{
}

حال انجام عملی مانند زیر مجاز است:

Pride131 pride131 = new Pride131();
Car car = pride131;

مفهوم شی گرایی در شروع کار کمی گنگ است ولی در ادامه برنامه نویسی و استفاده از آن برایتان شیرین و آسان خواهد شد.

قسمت‌های بعدی را از دست ندهید.