سفارش تبلیغ
صبا ویژن

آموزش زبان برنامه نویسی جاوا: Encapsulation

 یا کپسوله سازی یا محصور سازی یکی از ویژگی‌های برنامه نویسی شی گرا است. در واقع Encapsulation به این معنی است که ما برای یک کلاس ویژگی‌ها و رفتار‌هایی را تعریف کرده و سپس آن‌ها را پنهان کنیم. برای کپسوله کردن یک کلاس، فقط کافی است ویژگی‌ها و متد‌های یک کلاس را به صورت خصوصی یا private تعریف کنیم. بخش‌های خصوصی یا private فقط داخل همان کلاس قابل استفاده هستند و توسط کلاس‌های دیگر قابل استفاده نیستند. به این ویژگی Implementaion hiding یا پیاده‌سازی پنهان گفته می‌شود.
تبلیغات
آموزش حسابداری
فروش گوشی سامسونگ
آمادگی ورود به بازار کار
نگاهی دقیق به ویژگی‌های یک کلاس

ابتدا به کد زیر توجه کنید:

package ir.zoomit;

public class Test {
    String name = "Sina";
    int age = 22;
}

در کد بالا ما یک کلاس تعریف کرده‌ایم با نام Test که در این کلاس دو ویژگی تعریف شده است. ویژگی name که از جنس کلاس String است و ویژگی age که از جنس عدد صحیح است.
نکته: به ویژگی‌های کلاس اصطلاحا Property یا فیلد گفته می‌شود.

همانطور که در آموزش قبلی توضیح داده شد، اگر برای متغیر‌ها و متد‌ها سطح دسترسی را مشخص نکنیم، به صورت پیش فرض سطح دسترسی Package Access انتخاب می‌شود. حالا ما می‌خواهیم از کلاس اصلی که متد main در آن پیاده‌سازی و در همین پکیج هم (ir.zoomit) ساخته شده است، به فیلد‌های کلاس Test دسترسی پیدا کنیم و مقادیر آن‌ها را در خروجی استاندارد نمایش دهیم. بنابراین ابتدا باید از کلاس Test یک آبجکت ایجاد کنیم تا به وسیله‌ی آن آبجکت بتوانیم به فیلد‌های کلاس Test دسترسی داشته باشیم. به کد زیر دقت کنید:

package ir.zoomit;

public class MainApp {

    public static void main(String[] args) {
                // object creation
        Test t = new Test();

        System.out.println(t.name);
        System.out.println(t.age);
    }
}

همانطور که مشاهده می‌کنید ما با استفاده از آبجکت ساخته شده به فیلد‌های کلاس Test دسترسی پیدا و مقادیر آن‌ها را نیز در کنسول چاپ کردیم (دسترسی مستقیم). اما حالا می‌خواهیم کمی برنامه را تغییر دهیم. یعنی می‌خواهیم فیلد‌های کلاس Test را به صورت خصوصی یا private تعریف کنیم. کد زیر:

package ir.zoomit;

public class Test {
    private String name = "Sina";
    private int age = 22;
}

همانطور که مشاهده می‌کنید، ما سطح دسترسی فیلد‌های کلاس را به private تغییر دادیم. حالا اگر برنامه را Save و سپس اجرا کنیم، با خطای کامپایل مواجه می‌شویم. خطا به شکل زیر است:

Exception in thread "main" java.lang.Error: Unresolved compilation problems:
    The field Test.name is not visible
    The field Test.age is not visible

    at ir.zoomit.MainApp.main(MainApp.java:9)

اگر به متن ارور دقت کنید، نوشته شده است که متغیر‌های name و age به صورت Visible (قابل رویت) نیستند.

اگر برای نوشتن کد‌ها از محیط‌های توسعه‌ی برنامه نویسی مانند اکلیپس استفاده می‌کنید، این محیط‌های توسعه در چنین مواقعی پیشنهاداتی را برای رفع مشکل ارائه می‌دهند. (در اکلیپس، ارور‌ها با رنگ قرمز مشخص می‌شود. نشانگر ماوس را روی ارور‌ها نگه دارید تا محیط توسعه پیشنهادات خود را بدهد). تصویر زیر:

fixes

من نشانگر ماوس را روی فیلد age نگه داشتم و اکلیپس دو پیشنهاد می‌دهد. اولین پیشنهاد این است که سطح دسترسی متغیر age را به package access تغییر دهم و پیشنهاد دوم این است که متد‌های getter و setter بنویسم. ما بهترین راه‌حل، یعنی پیشنهاد دوم را انتخاب می‌کنیم.
متد‌های getter و setter
نکته: به متد‌های getter اصطلاحا Accessor و به متد‌های setter اصطلاحا Mutator گفته می‌شود.

متد‌های getter و setter متد‌هایی هستند که معمولا به صورت عمومی یا public تعریف می‌شوند و ما به وسیله‌ی آن‌ها می‌توانیم به فیلد‌هایی که به صورت private تعریف شده‌اند دسترسی پیدا کنیم. به کد زیر دقت کنید:

package ir.zoomit;

public class Test {
    private String name = "Sina";
    private int age = 22;

    public String getName() {
        return name;
    }

    public void setName(String n) {
        name = n;
    }

    public int getAge() {
        return age;
    }

    public void setage(int a) {
        age = a;
    }
}

نام گذاری متد‌های getter و setter به این صورت است که ابتدای نام متد، get یا set را می‌نویسیم و سپس نام فیلد مد نظرمان (مثل name و یا age) را در ادامه‌ی آن می‌نویسیم. نکته‌ای که وجود دارد اینکه ما باید از قوانین نام گذاری در جاوا پیروی کنیم. یعنی نام متد بهتر است با حرف کوچک انگلیسی شروع شود و اگر نام متد از چند کلمه تشکیل شده باشد، اولین حرف کلمه‌های دیگر با حرف بزرگ انگلیسی شروع شود مثل getName یا setAge.

نکته‌ی دیگری که در مورد متد‌های getter و setter وجود دارد این است که متد‌های getter باید از جنس فیلد مد نظر باشند. به عنوان مثال فیلد name از جنس کلاس String است، بنابراین متد getName هم لازم است که از جنس کلاس String باشد و باید یک مقدار String برگرداند که در اینجا مقدار name را با استفاده از کلیدواژه‌ی return بر می‌گرداند. اما متد setter باید از جنس void باشد و مقداری را بر نگرداند و یک پارامتر دریافت کند و مقدار آن پارامتر را به فیلد مورد نظر انتساب دهد. یعنی با استفاده از متد setter می‌توانیم مقادیر فیلد‌ها را تغییر دهیم.

حالا ما می‌خواهیم برنامه‌ی قبل را که در آن مستقیما به خود فیلد‌ها دسترسی داشتیم و مقادیر آن‌ها را چاپ کردیم، این بار از طریق متد‌های getter مقادیر آن را چاپ کنیم. (زیرا این بار دسترسی مستقیم نداریم (به علت private تعریف شدن ویژگی‌ها) و فقط از طریق متد‌های getter و setter به آن فیلد‌ها دسترسی داریم). به کد زیر دقت کنید:

package ir.zoomit;

public class MainApp {

    public static void main(String[] args) {
        // object creation
        Test t = new Test();

        System.out.println(t.getName());
        System.out.println(t.getAge());
    }
}

همانطور که گفته شد، کلیدواژه‌ی return مقدار فیلد مشخص شده را بر می‌گرداند. خروجی این برنامه دقیقا مشابه برنامه‌ی قبل است.

حالا می‌خواهیم با استفاده متد‌های setter مقادیر فیلد‌ها را تغییر دهیم. به کد زیر دقت کنید:

package ir.zoomit;

public class MainApp {

    public static void main(String[] args) {
        // object creation
        Test t = new Test();

        t.setName("ZoomIT");
        t.setage(1395);

        System.out.println(t.getName());
        System.out.println(t.getAge());
    }
}

هنگام تعریف متد‌های setter، برای آن‌ها یک پارامتر در نظر گرفتیم و زمانی که آن متد‌ها فراخوانی می‌شوند، باید پارامتر‌های آن را مشخص کنیم. ما برای متد setName پارامتری از جنس String تعریف کردیم، و برای همین ما رشته‌ی ZoomIT را برای setName ارسال کردیم (دقت کنید که رشته‌ی ZoomIT باید داخل دابل کوتیشن "" نوشته شود). اما برای setAge پارامتری از جنس عدد صحیح تعریف کردیم، بنابراین یک عدد صحیح (1395) به آن ارسال کردیم. حالا سوال اینجا است که متد‌های setter چگونه کار می‌کنند؟ مثلا به متد setName دقت کنید:

    public void setName(String n) {
        name = n;
    }

پارامتر‌های متد، بین پرانتز‌های باز و بسته‌ای که جلوی متد است نوشته می‌شود. متد setName یک پارامتر دارد که از جنس کلاس String و نام آن n است. داخل بدنه‌ی این متد، ما مقدار n را به فیلد name نسبت داده‌ایم. یعنی هرباری که متد setName را فراخوانی کنیم، ابتدا اینکه حتما باید برای تک تک پارامتر‌های آن، داده‌ای از جنسی که تعریف شده است ارسال کنیم و اینکه متد setName مقدار را می‌گیرد (مثلا ZoomIT)  و آن را به فیلد name نسبت می‌دهد. با این‌کار ما به متغیر name دسترسی غیر مستقیم پیدا کرده‌ایم.

بنابراین در کد زیر ما کلاسمان را اصطلاحا Encapsulate کرده‌ایم. کپسوله کردن یا محصور سازی کلاس، از کار‌های رایجی است که در برنامه نویسی شی گرا انجام می‌دهند. به کد زیر دقت کنید:

package ir.zoomit;

public class Test {
    private String name = "Sina";
    private int age = 22;

    public String getName() {
        return name;
    }

    public void setName(String n) {
        name = n;
    }

    public int getAge() {
        return age;
    }

    public void setage(int a) {
        age = a;
    }
}

کلاس فوق کپسول شده است. این کلاس دارای دو قسمت است. قسمت اول بخش پیاده‌سازی است و قسمت دوم بخش واسط یا Interface است. بخش پیاده‌سازی مربوط به دو خط اول برنامه است که دو ویژگی name و age تعریف و آن‌ها را پنهان کرده‌ایم. بخش واسط یا اینترفیس مربوط به متد‌های getter و setter است که واسط‌هایی عمومی هستند. یعنی به صورت public تعریف شده‌اند. اجازه دهید مفهوم واسط را با مثال توضیح دهم.
نکته: ابتدا این نکته را بدانید که در جاوا مفهومی است به نام Interface که بعدا با آن آشنا می‌شوید. در اینجا منظور از اینترفیس، آن مبحث مورد نظر در شی گرایی نیست. بنابراین با هم اشتباه نکنید.

منظور از اینترفیس در این بخش، واسط‌هایی است که ما به وسیله‌ی آن‌ها به ویژگی‌های کلاس که به صورت پنهان پیاده‌سازی شده‌اند، دسترسی پیدا کنیم. به عنوان مثال یک لامپ را در نظر بگیرید. معمولا در خانه‌ها، شرکت‌ها و تمام مکان‌هایی که از لامپ برای روشنایی استفاده می‌کنند، یک کلید برای روشن و خاموش کردن آن لامپ در نظر می‌گیرند و ما به راحتی با یک حرکت می‌توانیم یک لامپ را خاموش و یا روشن کنیم. حال سوال اینجاست که اگر کلید نباشد، ما باز هم می‌توانیم لامپ را خاموش و یا روشن کنیم؟ مسلما جواب مثبت است، اما دیگر کار ما به راحتی فشردن یک کلید نیست و باید از سیم‌هایی که به لامپ وصل شده‌اند استفاده کنیم و مستقیما سیم‌های لامپ را به برق بزنیم. در اینجا خطرات زیادی وجود دارد، ممکن است جایی از سیم لخت باشد و ما را برق بگیرد یا ممکن است در هنگام استفاده از سیم‌ها، ناگهان یکی از سیم‌ها پاره شود و مجبور می‌شویم یا سیم را عوض کنیم و یا آن را تعمیر کنیم که این کار، پروسه‌ی طولانی‌ای را طی خواهد کرد. بنابراین بهترین کار همام راه‌حل اول است. یعنی استفاده از یک کلید. وقتی از کلید استفاده می‌کنیم، دیگر ما درگیر جزئیات پیاده‌سازی نمی‌شویم. بنابراین پنهان سازی پیاده‌سازی، خطا را کاهش می‌دهد و هم اینکه کار ما با کلید ظاهر زیباتری دارد تا چند تا سیم!!! متد‌های getter و setter واسط‌ها (Interface) کلاس Test هستند.

در مثال فوق ما با بعضی از ویژگی‌های کپسوله سازی آشنا شدیم. یعنی پیاده‌سازی به صورت پنهان ظاهر زیباتری دارد، خطا را کاهش می‌دهد و استفاده کننده درگیر جزئیات نمی‌شود.
انواع داده‌ها در جاوا

تا این بخش از آموزش‌ها، ما با مفهوم کلاس، متد و شی (آبجکت) در جاوا آشنا شده‌ایم. یکی از کاربرد‌های اصلی کلاس، تعریف یک داده‌ی جدید است. در جاوا هشت نوع داده‌ی اولیه داریم که اصطلاحا به آن‌ها Primitive Data Type یا انواع داده‌های اولیه می‌گویند (که در آموزش‌های ابتدایی توضیح داده شده است). توجه داشته باشید که هر متغیر از این انواع اولیه، حاوی یک مقدار است نه شی. اما داده‌های دیگری هم وجود دارند که توسط کلاس‌ها به وجود آمده‌اند. بعضی از این کلاس‌ها در جاوا وجود دارند (مثل کلاس String) و بعضی داده‌ها را خود برنامه نویس آن‌ها را به وجود می‌اورد. این داده‌ها، انواع داده‌ی ارجاعی (Reference Data Type) هستند و هر متغیر از این انواع، یک ارجاع به یک شی است ( برای درک بهتر ارجاع به یک شی، به آموزش برنامه نویسی شی گرا مراجعه کنید). در ادامه می‌خواهیم یک داده‌ی جدید توسط یک کلاس ایجاد کنیم. مثالی که در ادامه کار می‌کنیم از کتاب احمدرضا صدیقی انتخاب شده است.

می‌خواهیم یک برنامه‌ای بنویسیم که در آن کلاسی تعریف کرده‌ایم با نام Time، که این کلاس معرف داده‌ی زمان است و توسط ما (برنامه نویس) ایجاد می‌شود. بنابراین ابتدا یک پروژه با نام Time ایجاد کنید، سپس اقدام به ساخت یک کلاس کنید که نام کلاس MainApp است و در داخل پکیج ir.zoomit قرار دارد و همچنین این کلاس، متد main را پیاده‌سازی کرده است. کد زیر کلاس اصلی برنامه را نشان می‌دهد:

package ir.zoomit;

public class MainApp {

    public static void main(String[] args) {

    }

}

حالا یک کلاس دیگر در داخل پکیج فعلی (ir.zoomit) ایجاد کنید و نام آن را Time در نظر بگیرید.

زمان سه ویژگی دارد: ساعت، دقیقه و ثانیه. بنابراین این سه ویژگی را باید در کلاس Time تعریف کنیم. از آنجا که هیچ کدام از این سه ویژگی نمی‌توانند عدد اعشاری باشند (مثلا ساعت 1.2 نداریم!!!)، بنابراین هر سه ویژگی را به صورت عدد صحیح تعریف می‌کنیم. به کد زیر دقت کنید:

package ir.zoomit;

public class Time {
    int hour; // 0-23
    int minute; // 0-59
    int second; // 0-59
}

در کد بالا سه ویژگی زمان را تعریف کرده‌ایم و با کامنت گذاری در جلوی هر ویژگی، مقداری برای آن ویژگی مشخص کرده‌ایم (البته کامنت گذاری مقداری را به متغیر انتساب نمی‌دهد. فقط نوشته‌ایم تا برنامه واضح شود). مثلا ساعت فقط می‌تواند عددی از صفر تا 23 باشد.

حال می‌خواهیم به این سه ویژگی مقادیری را بدهیم و آن‌ها را در خروجی استاندارد چاپ کنیم. بنابراین به کلاس اصلی می‌رویم و ابتدا یک آبجکت از روی کلاس Time ایجاد می‌کنیم. بعد از ساختن آبجکت، با استفاده از آبجکت مورد نظر، فیلد‌های کلاس Time را مقداردهی می‌کنیم. به کد زیر دقت کنید:

package ir.zoomit;

public class MainApp {

    public static void main(String[] args) {
        // object creation
        Time now = new Time();

        now.hour = 1;
        now.minute = 25;
        now.second = 35;
    }

}

همه چیز در کد فوق واضح است. ما به ویژگی‌های کلاس Time مقادیری را نسبت دادیم. حالا می‌خواهیم این مقادیر را در خروجی استاندارد چاپ کنیم. بنابراین از جمله‌ی ;()System.out.println استفاده می‌کنیم. کد زیر:

package ir.zoomit;

public class MainApp {

    public static void main(String[] args) {
        // object creation
        Time now = new Time();

        now.hour = 1;
        now.minute = 25;
        now.second = 35;

        System.out.println("Time is: " + now.hour + ":" + now.minute + ":" + now.second);
    }

}