ورود

View Full Version : فرق بین Override کردن و abstrcat



Sina.iRoid
دوشنبه 29 تیر 1394, 12:33 عصر
سلام
دوستان من مفهوم اورراید کردن و ابسترکت و می دونم. اما یه سوالی برام پیش اومده. تکه تکده زیر و نگاه کنید:


public double area() {
return 0;
}


این متد در کلاس پدر نوشته شده که کلاس های فرزند این متد و Override می کنن. و مثلا یکی از کلاس های فرزند این متد و اورراید کرده. به صورت زیر:


public double area() {
return a * b;
}


حالا اگه ما بیام کلاس پدر و abstract تعریف کنیم و متد area رو هم به صورت ابسترکت تعریف کنیم باز دقیقا همین اتفاق می افته. من الان متوجه نمیشم کی باید اورراید کنیم و کی ابسترکت.

نمی دونم خوب توضیح دادم یا نه. اما امیدوارم که متوجه شده باشید. ممنون از شما :)

alpotkin
دوشنبه 29 تیر 1394, 12:51 عصر
سلام
جواب این قسمت رو میدم
کی باید اورراید کنیم و کی ابسترکت.

مفهوم میگه Abstruct باید باید باید override بشه
پس هر متودی Abstruct شد پاید فرزندش اون متد رو override کنه
تفاوت Virtual و Abstruct در اونه

Sina.iRoid
دوشنبه 29 تیر 1394, 13:33 عصر
ممنون.
ولی خب گاهی اوقات ما متد و abstract نمی کنیم، ولی متدهای کلاس و اورراید می کنیم. متوجه سوالم شدین؟ یعنی کی ما فقط اورراید کنیم (یعنی متد ابسترکت تعریف نشده باشه ولی ما متد و اورراید کرده باشیم).
ممنون :)

[younes]
دوشنبه 29 تیر 1394, 14:28 عصر
یعنی کی ما فقط اورراید کنیم (یعنی متد ابسترکت تعریف نشده باشه ولی ما متد و اورراید کرده باشیم).
هر وقت خودتون صلاح بدونید متدی نیاز به بازنویسی داره این کار انجام بدهید.

-سیّد-
سه شنبه 30 تیر 1394, 10:13 صبح
من صحبتای دوستان رو تکمیل می‌کنم:
شما وقتی یه متد رو abstract تعریف می‌کنید، یعنی یک شیء از این کلاس این متد رو داره، ولی هنوز متدش بدنه نداره. یعنی شما می‌تونید توی متدهای دیگه‌ی همین کلاس یا یه کلاس دیگه، این متد abstract رو فراخوانی کنید (چون قطعاً در یک شیء از این کلاس وجود خواهد داشت)، ولی هنوز نمی‌دونید که بدنه‌اش چطوری هست.

یه مثال مبتنی بر همین area که خودتون گفتید:

public abstract class Shape {
abstract protected double area();

public String toString() {
return "area: " + area();
}
}

اینجا چند تا نکته هست:
یکی این که یه شکل، قطعاً مساحت داره، ولی تا وقتی ندونیم چه شکلی هست نمی‌تونیم بگیم مساحتش چقدره. پس یه متد abstract به نام area تعریف می‌کنیم که مساحت شکل رو برمی‌گردونه. بنابراین هر شیء از این کلاس، قطعاً بلده مساحتش رو محاسبه کنه، ولی ما توی کلاس پدر نمی‌دونیم چطوری (فقط می‌دونیم بلده). حالا می‌تونیم از این تابع توی توابع دیگه‌ی همین کلاس استفاده کنیم. اگه protected نبود و public بود، توی جاهای دیگه هم می‌شد ازش استفاده کرد:

Shape s = ...;
double a = s.area();

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

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

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

خوب حالا بیاین کلاس مستطیل رو بنویسیم:

public class Rectangle extends Shape {
private final double x;
private final double y;

public Rectangle(double x, double y) {
this.x = x;
this.y = y;
}

@Override
protected double area() {
return x * y;
}
}

اینجا نحوه‌ی محاسبه‌ی مساحت رو بهش فهموندیم. حالا اگه یه شیء از نوع Rectangle بسازیم و toString اش رو فراخوانی کنیم، از area ای که اینجا Override کردیم استفاده می‌کنه.
یه نکته در استفاده از Override@ هست: استفاده از این annotation اجباری نیست و تأثیری در خروجی کد شما نداره، ولی فوایدی داره. یکی از فوایدش اینه که اگه یه تابع رو فکر کنیم که داریم override می‌کنیم ولی اینطور نباشه (یعنی یه تابع معمولی باشه)، در صورت استفاده از این annotation کامپایلر به ما خطا می‌ده که این تابع override نشده.

حالا برگردیم به سؤال شما:


حالا اگه ما بیام کلاس پدر و abstract تعریف کنیم و متد area رو هم به صورت ابسترکت تعریف کنیم باز دقیقا همین اتفاق می افته. من الان متوجه نمیشم کی باید اورراید کنیم و کی ابسترکت.

فرق این ۲ حالت اینه: در حالتی که شما توی کلاس پدر متد رو نوشتید و توی بچه هم override می‌کنید، کلاس پدر می‌تونه abstract نباشه و در نتیجه هر کسی می‌تونه یه شیء از نوع کلاس پدر داشته باشه. ولی اگه تابع مورد نظر توی کلاس پدر abstract باشه، مجبورید خود کلاس پدر رو هم abstract کنید و دیگه کسی نمی‌تونه مستقیماً از کلاس پدر new کنه (چون یکی از توابعش نیست!).

در واقع بیشتر تفاوت مفهومی هست. کدی که شما نوشتید، از نظر مفهومی اشکال داره. اگه توی کلاس Shape تابع area رو به این صورت بنویسیم:



public double area() {
return 0;
}


این یعنی این که هر شکل من دارای مساحت صفر هست! مگر این که خلافش ثابت بشه! خوب این فرض اشتباه هست. دقیقاً اینجا abstract کاربرد داره، چون نمی‌دونیم مساحت یه شکل چقدر هست.
نکته‌ی دیگه این که با abstract کردن تابع، کلاس‌های فرزند رو مجبور می‌کنیم که override اش کنن. ولی در حالتی که صفر برمی‌گردونید، ممکنه یکی از کلاس‌های فرزند یادش بره این تابع رو رونویسی کنه.

در ضمن سؤالتون یه مقدار اشکال داره. کی abstract کنیم و کی override، درست نیست. سؤالتون درستش می‌شه این: کی abstract کنیم و کی abstract نکنیم. چون چه تابع کلاس پدر abstract باشه و چه نباشه، کلاس بچه override اش می‌کنه.

یه مثال دیگه هم می‌زنم:
کلاس AbstractList که مال خود جاوا هست رو نگاه کنید. این کلاس یه کلاس abstract هست که ArrayList و LinkedList از اون مشتق می‌شن. این کلاس نمی‌دونه چطوری باید یه خونه از List رو برگردوند، چون هنوز نوع List مشخص نیست. برای همین یه تابع abstract به نام get گذاشته که این کار رو می‌کنه:

abstract public E get(int index);

اینم توضیحش:


Returns the element at the specified position in this list.


حالا ArrayList و LinkedList هر کدوم یه جور این تابع رو پیاده‌سازی می‌کنن. نکته‌اش اینه که کلاس AbstractList اومده و در جاهای مختلفی از این تابع استفاده کرده، بدون این که این تابع هنوز پیاده‌سازی شده باشه. چون مطمئناً توی بچه‌ها پیاده‌سازی می‌شه.


خوب حالا اگه این تابع می‌خواست abstract نباشه، باید چی کار می‌کرد؟ null برمی‌گردوند؟ خوب این کار از نظر مفهومی اشکال داره.

در مورد این یکی سؤال:


ولی خب گاهی اوقات ما متد و abstract نمی کنیم، ولی متدهای کلاس و اورراید می کنیم. متوجه سوالم شدین؟ یعنی کی ما فقط اورراید کنیم (یعنی متد ابسترکت تعریف نشده باشه ولی ما متد و اورراید کرده باشیم).

همونطور که گفتن، به خودتون بستگی داره. ببینید مشخصه که کی باید یه تابع رو abstract تعریف کنیم. حالا در حالت عادی شما تابع رو abstract تعریف نمی‌کنید. ولی ممکنه توی یکی از کلاس‌های فرزند، نیاز پیدا کنید که یکی از توابع معمولی نوشته شده رو رونویسی کنید. مثلاً توی همین مثال بالا، ممکنه شما بخواین تابع toString رو هم توی Rectangle رونویسی کنید.
مثلاً یکی از کاربردهای این کار اینه که یه پیاده‌سازی دقیق‌تر یا بهینه‌تر توی کلاس فرزند ارائه بدید. شاید شما توی کلاس پدر برای به دست اوردن یه چیزی، کلی عملیات لازم داشته باشید (چون نمی‌دونید شیء شما از چه کلاسی خواهد بود)، ولی توی کلاس فرزند با توجه به این که همه چیز مشخصه، بتونید یه میانبر استفاده کنید و عملیات رو کلی سبکتر کنید.
یکی دیگه از کاربردهاش اینه که یه پیاده‌سازی‌ای رو تغییر بدید. یعنی توی کلاس پدر یه جور عمل می‌کرد، شما توی کلاس فرزند می‌خواین یه جور دیگه عمل کنید. البته باید حواستون باشه که از تعریف تابع بیرون نزنید. یعنی اگه توی توضیحات تابع گفته شده که این تابع حتماً این کار رو می‌کنه، نباید اون رو توی کلاس فرزند نقض کنید.

مثال این مورد توی همین ArrayList هست. مثلاً تابع toArray یک لیست رو به آرایه تبدیل می‌کنه:

<T> T[] toArray(T[] a);

توضیحاتش:


Returns an array containing all of the elements in this collection; the runtime type of the returned array is that of the specified array. If the collection fits in the specified array, it is returned therein. Otherwise, a new array is allocated with the runtime type of the specified array and the size of this collection.

If this collection fits in the specified array with room to spare (i.e., the array has more elements than this collection), the element in the array immediately following the end of the collection is set to null. (This is useful in determining the length of this collection only if the caller knows that this collection does not contain any null elements.)

If this collection makes any guarantees as to what order its elements are returned by its iterator, this method must return the elements in the same order.

Like the toArray() (http://barnamenevis.org/%E2%98%82=LuceneTest/C:%5C/Program%20Files%5C/Java%5C/jdk1.8.0_40%5C/jre%5C/lib%5C/rt.jar%3Cjava.util(Collection.class%E2%98%83Collec tion~toArray~%5C%E2%98%83TT;%E2%98%82%E2%98%82toAr ray%E2%98%82) method, this method acts as bridge between array-based and collection-based APIs. Further, this method allows precise control over the runtime type of the output array, and may, under certain circumstances, be used to save allocation costs.

Suppose x is a collection known to contain only strings. The following code can be used to dump the collection into a newly allocated array of String:
String[] y = x.toArray(new String[0]);

Note that toArray(new Object[0]) is identical in function to toArray().

This implementation returns an array containing all the elements returned by this collection's iterator in the same order, stored in consecutive elements of the array, starting with index 0. If the number of elements returned by the iterator is too large to fit into the specified array, then the elements are returned in a newly allocated array with length equal to the number of elements returned by the iterator, even if the size of this collection changes during iteration, as might happen if the collection permits concurrent modification during iteration. The size method is called only as an optimization hint; the correct result is returned even if the iterator returns a different number of elements.

This method is equivalent to:
List<E> list = new ArrayList<E>(size());
for (E e : this)
list.add(e);
return list.toArray(a);


خوب حالا توی کلاس AbstractCollection که پدر AbstractList هست، یه پیاده‌سازی از این تابع اومده، که توش میاد با Reflection یه آرایه می‌سازه، بعد دونه دونه روی المان‌های لیست حرکت می‌کنه و المان‌ها رو توی آرایه‌ی جدید کپی می‌کنه:

public <T> T[] toArray(T[] a) {
// Estimate size of array; be prepared to see more or fewer elements
int size = size();
T[] r = a.length >= size ? a :
(T[])java.lang.reflect.Array
.newInstance(a.getClass().getComponentType(), size);
Iterator<E> it = iterator();

for (int i = 0; i < r.length; i++) {
if (! it.hasNext()) { // fewer elements than expected
if (a == r) {
r[i] = null; // null-terminate
} else if (a.length < i) {
return Arrays.copyOf(r, i);
} else {
System.arraycopy(r, 0, a, 0, i);
if (a.length > i) {
a[i] = null;
}
}
return a;
}
r[i] = (T)it.next();
}
// more elements than expected
return it.hasNext() ? finishToArray(r, it) : r;
}


ولی کلاس ArrayList نیازی به این کارا نداره! چون خودش آرایه‌ی مورد نظر رو داره و کافیه یه کپی ازش بسازه!

public <T> T[] toArray(T[] a) {
if (a.length < size)
// Make a new array of a's runtime type, but my contents:
return (T[]) Arrays.copyOf(elementData, size, a.getClass());
System.arraycopy(elementData, 0, a, 0, size);
if (a.length > size)
a[size] = null;
return a;
}

می‌بینید که این پیاده‌سازی چقدر راحت‌تر و بهینه‌تر هست.

حتی کلاس LinkedList هم بعضی از چک‌هایی که توی کلاس AbstractCollection لازم بود رو لازم نداره و پیاده‌سازیش کلی راحت‌تر می‌شه:

public <T> T[] toArray(T[] a) {
if (a.length < size)
a = (T[])java.lang.reflect.Array.newInstance(
a.getClass().getComponentType(), size);
int i = 0;
Object[] result = a;
for (Node<E> x = first; x != null; x = x.next)
result[i++] = x.item;

if (a.length > size)
a[size] = null;

return a;
}

developer.net@live.com
جمعه 23 مرداد 1394, 22:25 عصر
واقعا ترکوندی همه رو سید.
سپاسگزاری که انقدر با عشق توضیح دادی

محمد فدوی
یک شنبه 25 مرداد 1394, 03:58 صبح
سلام
دوستان من مفهوم اورراید کردن و ابسترکت و می دونم. اما یه سوالی برام پیش اومده. تکه تکده زیر و نگاه کنید:


public double area() {
return 0;
}


این متد در کلاس پدر نوشته شده که کلاس های فرزند این متد و Override می کنن. و مثلا یکی از کلاس های فرزند این متد و اورراید کرده. به صورت زیر:


public double area() {
return a * b;
}


حالا اگه ما بیام کلاس پدر و abstract تعریف کنیم و متد area رو هم به صورت ابسترکت تعریف کنیم باز دقیقا همین اتفاق می افته. من الان متوجه نمیشم کی باید اورراید کنیم و کی ابسترکت.

نمی دونم خوب توضیح دادم یا نه. اما امیدوارم که متوجه شده باشید. ممنون از شما :)
از نظر پیاده‌سازی تقریبا تفاوت عمده‌ای بین این نیست که ما یک کلاس انتزاعی داشته باشیم و بعد متدهاش رو پیاده‌سازی کنیم یا یه کلاس با متدهای تقریبا بی‌کار داشته باشیم و هرچی رو که دوست داشتیم پیاده‌سازی کنیم. اما این معنیش این نیست که اینا دقیقا مشابهن.

اولا مفهوم «انتزاع» خودش خیلی به قابل فهم بودن کد کمک می‌کنه. وقتی یک کلاس انتزاعی باشه تاکید بر اینه که هیچ شیءای از این نوع مستقیما وجود نخواهد داشت.
ثانیا کار اشکال‌یابی برنامه ساده‌تره. فرض کنیم یه کلاس انتزاعی رو به غلط به شیوه‌ی دوم ساختیم. کسی که قراره از اون کلاس یه کلاس فرزند بنویسه ملزم نیست همه‌ی متد‌هاش رو Override کنه. این یعنی تعداد زیادی خطا که پیدا کردنشون خیلی هم ساده نیست.
ثالثا موقع استفاده از کلاس‌های انتزاعی می‌شه کلاس فرزند رو مجبور کرد که متدی رو که قبلا مجبور به بازنویسیش نبوده اجبارا بازنویسی کنه (مثل toString):
public abstract class User implements Comparable<User> {
...

public abstract String toString();
}
رابعا موقع استفاده از کلاس‌های انتزاعی کار نوشتن امضای متد‌ها خیلی ساده‌تره. این دوتا رو با هم مقایسه کن و فرض کن قراره کلاسی با تعداد زیاد متد داشته باشی:
// Abstract class
public abstract String getName();

// Normal class
public String getName() {
return "";
}

در کل در زبانی مثل جاوا همه‌ی پیچیدگی‌های غیرضروری برنامه‌نویسی شیءگرا (مثل ارث‌بری چندگانه، Structها، پارامترهای نوع‌دار ژنریک، متدهای مجازی و...) حذف شده‌ان و همینی که باقی مونده مطمئنا ضروری بوده که مونده.