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

یک‌شنبه ۲۶ اردیبهشت ۱۳۹۵ - ۱۶:۰۰
مطالعه 17 دقیقه
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}

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

حال می‌خواهیم به این سه ویژگی مقادیری را بدهیم و آن‌ها را در خروجی استاندارد چاپ کنیم. بنابراین به کلاس اصلی می‌رویم و ابتدا یک آبجکت از روی کلاس 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); }}

خروجی برنامه‌ی بالا به صورت زیر است:

Time is: 1:25:35

برنامه‌ی بالا از جهت طراحی شی گرا ایراد دارد. بهتر است بجای اینکه ما فیلد‌های کلاس Time را یکی یکی مقداردهی کنیم، یک متد تعریف کنیم و به وسیله‌ی آن متد، فیلد‌های کلاس را مقداردهی کنیم. به کلاس Time برگردید و متد زیر را طبق کد زیر به آن اضافه کنید:

package ir.zoomit;public class Time { int hour; // 0-23 int minute; // 0-59 int second; // 0-59 public void setTime(int h, int m, int s) { hour = h; minute = m; second = s; }}

متدی با نام setTime تعریف کرده‌ایم که سه پارامتر دریافت می‌کند و سپس در بدنه‌ی متد، مقادیر پارامتر‌ها به فیلد‌های کلاس نسبت داده می‌شود. حالا در کلاس اصلی برنامه (MainApp)، با استفاده از آبجکتی که از روی کلاس Time ساخته‌ایم، متد setTime را فراخوانی می‌کنیم و به دلیل اینکه این متد سه پارامتر دریافت کرده است، ما باید هنگام فراخوانی، حتما سه داده از نوعی که تعریف شده است (در اینجا عدد صحیح (int)) به آن ارسال کنیم. کد زیر:

package ir.zoomit;public class MainApp { public static void main(String[] args) { // object creation Time now = new Time(); now.setTime(1, 25, 35); }}

همانطور که مشاهده می‌کنید با یک‌بار فراخوانی متد setTime، سه ویژگی (سه متغیر) کلاس Time را مقداردهی کردیم.

package ir.zoomit;public class MainApp { public static void main(String[] args) { // object creation Time now = new Time(); now.setTime(1, 25, 35); System.out.println("Time is: " + now.hour + ":" + now.minute + ":" + now.second); }}

کد ما بهتر شد، اما باز هم از جهت شی گرایی ایراد دارد. مشکلی که این برنامه دارد این است که هر بار برای اینکه بتوانیم مقادیر فیلد‌های کلاس Time را در کنسول چاپ کنیم، باید ;()System.out.println را بنویسیم و داده‌ها را به وسیله‌ی + به یکدیگر متصل کنیم. اما راه حلی بهتر وجود دارد و آن اینکه می‌توانیم یک متد دیگر در کلاس Time پیاده‌سازی کنیم که این کار را بر عهده بگیرد.

در جاوا متدی است با نام ()toString که این متد باید دقیقا به همین شکلی که در کد پایین مشاهده می‌کنید نوشته شود.

@Override public String toString() { return super.toString(); }

در جاوا وقتی آبجکتی را چاپ می‌کنیم، جاوا به صورت خودکار متد ()toString آبجکت را فراخوانی می‌کند تا بتواند شکل نمایش آبجکت را کشف کند. بنابراین متد ()toString باید دقیقا شبیه چیزی که در بالا است باشد. در غیراینصورت جاوا نمی‌تواند تشخیص دهد. حالا متد ()toString را به کلاس Time اضافه می‌کنیم و بدنه‌ی آن را به صورت زیر می‌نویسیم:

package ir.zoomit;public class Time { int hour; // 0-23 int minute; // 0-59 int second; // 0-59 public void setTime(int h, int m, int s) { hour = h; minute = m; second = s; } @Override public String toString() { return hour + ":" + minute + ":" + second; }}

با نوشتن متد ()toString دیگر نیازی نیست که در کلاس اصلی فرمتی برای نمایش مقادیر فیلد‌ها برای نمایش در خروجی مشخص کنیم. فقط کافی است آبجکت now را در خروجی استاندارد نمایش دهیم. کدر زیر:

package ir.zoomit;public class MainApp { public static void main(String[] args) { // object creation Time now = new Time(); now.setTime(1, 25, 35); System.out.println("Time is: " + now); }}

همانطور که مشاهده می‌کنید من متد ()toString را فراخوانی نکردم. زیرا جاوا به صورت خودکار این کار را انجام می‌دهد، پس بنابراین فقط آبجکت now را می‌نویسیم.

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

package ir.zoomit;import javax.swing.JOptionPane;public class MainApp { public static void main(String[] args) { // object creation Time now = new Time(); now.setTime(1, 25, 35); JOptionPane.showMessageDialog(null, "Time is: " + now); }}

خروجی برنامه‌ی بالا به صورت زیر است:

output

همانطور که مشاهده می‌کنید، برنامه جدا از اینکه کم کم به طراحی شی گرا نزدیک می‌شود، حجم کد‌های نوشته شده در برنامه هم کاهش پیدا می‌کند و تغییرات در برنامه آسان‌تر می‌شود. اما باز هم برنامه‌ی ما ایراد دارد. یکی از ایراد‌های اصلی‌ای که برنامه‌ی ما دارد و حتما باید آن را رفع کنیم، این است که ما برای مقادیر ساعت، دقیقه و ثانیه، هر عددی را می‌توانیم در نظر بگیریم. مسلما این کار منطقی نیست. مثلا ساعت 200 نداریم. پس باید برنامه را طوری تغییر دهیم که قبل از اینکه مقادیر به متغیر‌ها نسبت داده شوند، بررسی شوند که آیا مقادیر بین بازه‌ای که مشخص کرده‌ایم است یا خیر؟ و همچنین کلاس را کپسوله سازی کنیم تا فقط فیلد‌های کلاس Time از طریق متد‌هایی که این بررسی را انجام می‌دهند مقداردهی شوند. در غیراینصورت باز هم ممکن است مقادیر دیگری نسبت داده شود. پس ابتدا فیلد‌های کلاس Time را private می‌کنیم و بعد متد‌های getter و setter برای آن تعریف می‌کنیم. به کد زیر دقت کنید:

package ir.zoomit;public class Time { private int hour; // 0-23 private int minute; // 0-59 private int second; // 0-59 public void setTime(int h, int m, int s) { hour = h; minute = m; second = s; } public int getHour() { return hour; } public void setHour(int h) { hour = h; } public int getMinute() { return minute; } public void setMinute(int m) { minute = m; } public int getSecond() { return second; } public void setSecond(int s) { second = s; } @Override public String toString() { return hour + ":" + minute + ":" + second; }}

تنها کاری که در کلاس Time کردیم این است که سطح دسترسی سه فیلد کلاس را به private تغییر داده‌ایم و سپس برای آن‌ها متد‌های getter و setter تعریف کرده‌ایم. حالا می‌خواهیم با استفاده از ساختار if...else شرطی را بررسی کنیم. ساختار if...else ساختاری بسیار راحتی دارد. به کد زیر توجه کنید:

if (n < 10) { n = 10; } else { n = 0; }

کد بالا به این صورت است که اگر مثلا متغیری داشته باشیم که نام آن n باشد و این متغیر از 10 کوچکتر باشد، بلاک جلوی دستور if اجرا می‌شود و اگر شرط ما برقرار نبود (یعنی n یا 10 یا بزرگتر از 10 بود) قسمت else اجرا می‌شود.

حالا می‌توانیم بررسی شرط بالا را در یک خط هم انجام دهیم. به کد زیر دقت کنید:

n = (n < 10) ? n = 10 : (n = 0);

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

حالا برگردیم به برنامه؛ از آنجایی که کلاس Time را محصور سازی کردیم، فقط با استفاده از متد‌های getter و setter به آن‌ها دسترسی داریم. در اینجا ما می‌خواهیم مقادیری را به فیلد‌های کلاس نسبت دهیم، پس باید از متد‌های setter استفاده کنیم و همچنین قبل از اینکه مقداری به فیلد‌ها نسبت داده شود، بررسی شود که آیا مقادیر در بازه‌ی مشخص شده هستند یا خیر. بنابراین بدنه‌ی متد‌های setter را در کلاس Time به صورت زیر تغییر می‌دهیم:

package ir.zoomit;public class Time { private int hour; // 0-23 private int minute; // 0-59 private int second; // 0-59 public void setTime(int h, int m, int s) { hour = h; minute = m; second = s; } public int getHour() { return hour; } public void setHour(int h) { hour = (h >= 0 && h < 24) ? h : 0; } public int getMinute() { return minute; } public void setMinute(int m) { minute = (m >= 0 && m < 60) ? m : 0; } public int getSecond() { return second; } public void setSecond(int s) { second = (s >= 0 && s < 60) ? s : 0; } @Override public String toString() { return hour + ":" + minute + ":" + second; }}

عملگر && به معنی And است. یعنی دو شرط را بررسی می‌کنیم اگر دو شرط (شرط اول و شرط دوم) هر دو همزمان برقرار بودند، کد‌هایی اجرا می‌شود، اما اگر یکی از شرط‌ها برقرار نبود،کدی اجرا نمی‌شود. بررسی شرط‌ها هم به این صورت است که مثلا ساعت (h) حتما باید 0 یا بزرگتر از صفر باشد و (&&) از 24 هم کوچکتر باشد. در این صورت مقدار خود ساعت (h) نسبت داده می‌شود اما در غیر این صورت مقدار صفر نسبت داده می‌شود.

اگر دقت کرده باشید، من از طریق متدی با نام setTime سه فیلد کلاس Time را مقداردهی کردم. حالا ما باید در داخل متد setTime، متد‌های setter را بنویسیم تا شرط را بررسی کند. به بدنه‌ی متد setTime در کد زیر توجه کنید:

package ir.zoomit;public class Time { private int hour; // 0-23 private int minute; // 0-59 private int second; // 0-59 public void setTime(int h, int m, int s) { setHour(h); setMinute(m); setSecond(s); } public int getHour() { return hour; } public void setHour(int h) { hour = (h >= 0 && h < 24) ? h : 0; } public int getMinute() { return minute; } public void setMinute(int m) { minute = (m >= 0 && m < 60) ? m : 0; } public int getSecond() { return second; } public void setSecond(int s) { second = (s >= 0 && s < 60) ? s : 0; } @Override public String toString() { return hour + ":" + minute + ":" + second; }}

همانطور که مشاهده می‌کنید، از داخل متد setTime، متد‌های setter را فراخوانی کرده‌ایم.

حالا به کلاس اصلی رفته و برای مثال برای ساعت (h)، مقدار 100 را در نظر بگیرید و سپس برنامه را اجرا کنید. کد زیر:

package ir.zoomit;import javax.swing.JOptionPane;public class MainApp { public static void main(String[] args) { // object creation Time now = new Time(); now.setTime(100, 25, 35); JOptionPane.showMessageDialog(null, "Time is: " + now); }}

حالا اگر برنامه را اجرا کنیم، خروجی برنامه به صورت زیر است:

output

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

در اینجا ما با یکی دیگر از ویژگی‌های Encapsulation آشنا شدیم. با کپسوله سازی کلاس می‌توانیم متغیر‌ها را با مقادیر صحیح مقداردهی کنیم.

این برنامه باز هم ایراداتی دارد. یعنی بهتر است بجای استفاده از متد setTime، از کانستراکتور (Constructor) استفاده کنیم و همچنین یک‌سری مسائل دیگر که در جلسات آینده با آن‌ها آشنا می‌شوید.

برای مشاهده‌ی کامل مطالب آموزش جاوا می‌توانید بر روی صفحه‌ی آموزش جاوا کلیک کنید.

تبلیغات
داغ‌ترین مطالب روز

نظرات

تبلیغات