JavaRush /وبلاگ جاوا /Random-FA /طراحی کلاس ها و رابط ها (ترجمه مقاله)
fatesha
مرحله

طراحی کلاس ها و رابط ها (ترجمه مقاله)

در گروه منتشر شد
طراحی کلاس ها و رابط ها (ترجمه مقاله) - 1

محتوا

  1. معرفی
  2. رابط ها
  3. نشانگرهای رابط
  4. رابط های کاربردی، روش های استاتیک و روش های پیش فرض
  5. کلاس های انتزاعی
  6. کلاس های تغییرناپذیر (دائمی).
  7. کلاس های ناشناس
  8. دید
  9. وراثت
  10. ارث چندگانه
  11. ارث و ترکیب
  12. کپسوله سازی
  13. کلاس ها و روش های نهایی
  14. بعدش چی
  15. کد منبع را دانلود کنید

1. معرفی

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

2. رابط ها

در برنامه نویسی شی گرا ، مفهوم رابط ها اساس توسعه قراردادها را تشکیل می دهد . به طور خلاصه، اینترفیس ها مجموعه ای از روش ها (قراردادها) را تعریف می کنند و هر کلاسی که برای آن رابط خاص نیاز به پشتیبانی دارد، باید پیاده سازی آن روش ها را ارائه دهد: یک ایده نسبتا ساده اما قدرتمند. بسیاری از زبان های برنامه نویسی به یک شکل رابط دارند، اما جاوا به طور خاص پشتیبانی زبانی را برای این کار فراهم می کند. بیایید نگاهی به تعریف رابط کاربری ساده در جاوا بیندازیم.
package com.javacodegeeks.advanced.design;

public interface SimpleInterface {
void performAction();
}
در قطعه بالا، رابطی که ما فراخوانی کردیم SimpleInterface، تنها یک متد به نام را اعلام می کند performAction. تفاوت اصلی بین اینترفیس ها و کلاس ها این است که اینترفیس ها مشخص می کنند که مخاطب چه چیزی باید باشد (آنها یک روش را اعلام می کنند)، اما اجرای خود را ارائه نمی دهند. با این حال، رابط‌ها در جاوا می‌توانند پیچیده‌تر باشند: آنها می‌توانند شامل رابط‌های تودرتو، کلاس‌ها، شمارش‌ها، حاشیه‌نویسی‌ها و ثابت‌ها باشند. مثلا:
package com.javacodegeeks.advanced.design;

public interface InterfaceWithDefinitions {
    String CONSTANT = "CONSTANT";

    enum InnerEnum {
        E1, E2;
    }

    class InnerClass {
    }

    interface InnerInterface {
        void performInnerAction();
    }

    void performAction();
}
در این مثال پیچیده‌تر، محدودیت‌های متعددی وجود دارد که رابط‌ها بدون قید و شرط بر ساختارهای تودرتو و اعلان‌های متد اعمال می‌کنند که کامپایلر جاوا اعمال می‌کند. اول از همه، حتی اگر به صراحت اعلام نشده باشد، هر اعلان متد در یک رابط عمومی است (و فقط می تواند عمومی باشد). بنابراین اعلان های روش زیر معادل هستند:
public void performAction();
void performAction();
شایان ذکر است که هر متد منفرد در یک رابط به طور ضمنی انتزاعی اعلام می شود و حتی این اعلان های متد معادل هستند:
public abstract void performAction();
public void performAction();
void performAction();
در مورد فیلدهای ثابت اعلام شده، علاوه بر عمومی بودن، به طور ضمنی ثابت هستند و به صورت نهایی مشخص می شوند . بنابراین اظهارات زیر نیز معادل هستند:
String CONSTANT = "CONSTANT";
public static final String CONSTANT = "CONSTANT";
در نهایت، کلاس‌های تودرتو، رابط‌ها یا شمارش‌ها، علاوه بر عمومی بودن، به طور ضمنی ثابت نیز اعلام می‌شوند . برای مثال، این اعلان ها نیز معادل هستند:
class InnerClass {
}

static class InnerClass {
}
سبکی که انتخاب می کنید ترجیح شخصی است، اما دانستن این ویژگی های ساده رابط ها می تواند شما را از تایپ غیر ضروری نجات دهد.

3. نشانگر رابط

واسط نشانگر نوع خاصی از رابط است که متدها یا دیگر ساختارهای تودرتو ندارد. چگونه کتابخانه جاوا آن را تعریف می کند:
public interface Cloneable {
}
نشانگرهای واسط فی نفسه قرارداد نیستند، بلکه تکنیکی مفید برای "پیوست کردن" یا "ارتباط" برخی از ویژگی های خاص با یک کلاس هستند. برای مثال، با توجه به Cloneable ، کلاس به‌عنوان قابل شبیه‌سازی علامت‌گذاری می‌شود، اما روشی که در آن می‌توان یا باید پیاده‌سازی شود، بخشی از رابط نیست. یکی دیگر از نمونه های بسیار معروف و پرکاربرد نشانگر رابط این است Serializable:
public interface Serializable {
}
این رابط کلاس را به عنوان مناسب برای سریال‌سازی و سریال‌زدایی علامت‌گذاری می‌کند، و باز هم مشخص نمی‌کند که چگونه می‌تواند یا باید اجرا شود. نشانگرهای رابط جایگاه خود را در برنامه نویسی شی گرا دارند، اگرچه هدف اصلی یک رابط را که یک قرارداد است برآورده نمی کنند. 

4. رابط های عملکردی، روش های پیش فرض و روش های استاتیکی

از زمان انتشار جاوا 8، اینترفیس ها ویژگی های جدید بسیار جالبی به دست آورده اند: روش های استاتیک، روش های پیش فرض و تبدیل خودکار از لامبدا (رابط های کاربردی). در بخش اینترفیس ها بر این نکته تاکید کردیم که رابط ها در جاوا فقط می توانند متدها را اعلام کنند، اما پیاده سازی آنها را ارائه نمی دهند. با یک روش پیش‌فرض، همه چیز متفاوت است: یک رابط می‌تواند یک روش را با کلمه کلیدی پیش‌فرض علامت‌گذاری کند و یک پیاده‌سازی برای آن ارائه دهد. مثلا:
package com.javacodegeeks.advanced.design;

public interface InterfaceWithDefaultMethods {
    void performAction();

    default void performDefaulAction() {
        // Implementation here
    }
}
در سطح نمونه، روش‌های پیش‌فرض می‌توانند توسط هر پیاده‌سازی اینترفیس نادیده گرفته شوند، اما رابط‌ها اکنون می‌توانند شامل متدهای ثابت نیز باشند ، برای مثال: بسته com.javacodegeeks.advanced.design;
public interface InterfaceWithDefaultMethods {
    static void createAction() {
        // Implementation here
    }
}
می توان گفت که ارائه پیاده سازی در اینترفیس کل هدف برنامه نویسی قراردادی را ناکام می گذارد. اما دلایل زیادی وجود دارد که چرا این ویژگی ها به زبان جاوا وارد شدند و مهم نیست که چقدر مفید یا گیج کننده باشند، برای شما و استفاده شما وجود دارند. رابط های کاربردی داستانی کاملا متفاوت هستند و ثابت شده است که افزوده های بسیار مفیدی به زبان هستند. اساساً یک رابط عملکردی یک رابط است که فقط یک روش انتزاعی بر روی آن اعلام شده است. Runnableرابط استاندارد کتابخانه نمونه بسیار خوبی از این مفهوم است.
@FunctionalInterface
public interface Runnable {
    void run();
}
کامپایلر جاوا با اینترفیس‌های عملکردی متفاوت رفتار می‌کند و می‌تواند یک تابع لامبدا را به یک پیاده‌سازی رابط کاربردی تبدیل کند که منطقی باشد. بیایید توضیحات تابع زیر را در نظر بگیریم: 
public void runMe( final Runnable r ) {
    r.run();
}
برای فراخوانی این تابع در جاوا 7 و پایین تر، باید یک پیاده سازی از اینترفیس ارائه شود Runnable(به عنوان مثال، با استفاده از کلاس های ناشناس)، اما در جاوا 8 کافی است یک پیاده سازی از متد run() با استفاده از نحو لامبدا ارائه شود:
runMe( () -> System.out.println( "Run!" ) );
علاوه بر این، حاشیه نویسی @FunctionalInterface (حاشیه ها در قسمت 5 آموزش به تفصیل پوشش داده خواهد شد) اشاره می کند که کامپایلر می تواند بررسی کند که آیا یک رابط فقط یک متد انتزاعی دارد یا خیر، بنابراین هر تغییری که در آینده در رابط ایجاد شود این فرض را نقض نخواهد کرد. .

5. کلاس های چکیده

مفهوم جالب دیگری که توسط زبان جاوا پشتیبانی می شود، مفهوم کلاس های انتزاعی است. کلاس های انتزاعی تا حدودی شبیه رابط های جاوا 7 هستند و بسیار نزدیک به رابط متد پیش فرض در جاوا 8 هستند. برخلاف کلاس های معمولی، یک کلاس انتزاعی را نمی توان نمونه سازی کرد، اما می توان آن را زیر کلاس قرار داد (برای جزئیات بیشتر به بخش Inheritance مراجعه کنید). مهمتر از آن، کلاس های انتزاعی می توانند حاوی متدهای انتزاعی باشند: نوع خاصی از متد بدون پیاده سازی، درست مانند یک رابط. مثلا:
package com.javacodegeeks.advanced.design;

public abstract class SimpleAbstractClass {
    public void performAction() {
        // Implementation here
    }

    public abstract void performAnotherAction();
}
در این مثال، کلاس به صورت انتزاعی و حاوی یک متد انتزاعی SimpleAbstractClassاعلام شده است . کلاس‌های انتزاعی بسیار مفید هستند؛ بیشتر یا حتی برخی از قسمت‌های جزئیات پیاده‌سازی را می‌توان در میان بسیاری از زیر کلاس‌ها به اشتراک گذاشت. به هر حال، آنها همچنان در را باز می گذارند و به شما اجازه می دهند رفتار ذاتی هر یک از زیر کلاس ها را با استفاده از روش های انتزاعی سفارشی کنید. شایان ذکر است که برخلاف رابط‌ها که فقط می‌توانند حاوی اعلان‌های عمومی باشند ، کلاس‌های انتزاعی می‌توانند از قدرت کامل قوانین دسترسی برای کنترل قابلیت مشاهده یک متد انتزاعی استفاده کنند.

6. کلاس های فوری

امروزه تغییر ناپذیری در توسعه نرم افزار اهمیت بیشتری پیدا کرده است. ظهور سیستم‌های چند هسته‌ای مسائل زیادی را در ارتباط با اشتراک‌گذاری داده‌ها و موازی‌سازی ایجاد کرده است. اما یک مشکل قطعاً بوجود آمده است: داشتن حالت تغییرپذیر کم (یا حتی بدون) منجر به توسعه پذیری (مقیاس پذیری) بهتر و استدلال آسان تر در مورد سیستم می شود. متأسفانه، زبان جاوا پشتیبانی مناسبی از تغییرناپذیری کلاس ارائه نمی دهد. با این حال، با استفاده از ترکیبی از تکنیک ها، می توان کلاس هایی را طراحی کرد که تغییر ناپذیر هستند. اول از همه، تمام فیلدهای کلاس باید نهایی باشند (به عنوان نهایی علامت گذاری شوند ). این شروع خوبی است، اما تضمینی نیست. 
package com.javacodegeeks.advanced.design;

import java.util.Collection;

public class ImmutableClass {
    private final long id;
    private final String[] arrayOfStrings;
    private final Collection<String> collectionOfString;
}
ثانیا، از مقداردهی اولیه مناسب اطمینان حاصل کنید: اگر یک فیلد مرجع یک مجموعه یا آرایه است، آن فیلدها را مستقیماً از آرگومان های سازنده تخصیص ندهید، به جای آن کپی کنید. این اطمینان حاصل می کند که وضعیت مجموعه یا آرایه در خارج از آن تغییر نمی کند.
public ImmutableClass( final long id, final String[] arrayOfStrings,
        final Collection<String> collectionOfString) {
    this.id = id;
    this.arrayOfStrings = Arrays.copyOf( arrayOfStrings, arrayOfStrings.length );
    this.collectionOfString = new ArrayList<>( collectionOfString );
}
و در نهایت اطمینان از دسترسی مناسب (گیرنده). برای مجموعه‌ها، تغییرناپذیری باید به‌عنوان یک پوشش ارائه شود  Collections.unmodifiableXxx: با آرایه‌ها، تنها راه برای ارائه تغییرناپذیری واقعی، ارائه یک کپی به جای بازگرداندن ارجاع به آرایه است. این ممکن است از نقطه نظر عملی قابل قبول نباشد، زیرا بسیار به اندازه آرایه بستگی دارد و می تواند فشار زیادی را به جمع کننده زباله وارد کند.
public String[] getArrayOfStrings() {
    return Arrays.copyOf( arrayOfStrings, arrayOfStrings.length );
}
حتی این مثال کوچک این ایده خوب را نشان می دهد که تغییرناپذیری هنوز شهروند درجه یک در جاوا نیست. اگر یک کلاس تغییرناپذیر فیلدی داشته باشد که به یک شی از کلاس دیگر اشاره دارد، ممکن است اوضاع پیچیده شود. آن کلاس ها نیز باید تغییر ناپذیر باشند، اما هیچ راهی برای اطمینان از این موضوع وجود ندارد. چندین تحلیلگر کد منبع جاوا مانند FindBugs و PMD وجود دارد که می توانند با بررسی کد شما و اشاره به ایرادات رایج برنامه نویسی جاوا به شما کمک کنند. این ابزارها دوستان خوبی برای هر توسعه دهنده جاوا هستند.

7. کلاس های ناشناس

در دوران پیش از جاوا 8، کلاس‌های ناشناس تنها راه برای اطمینان از تعریف کلاس‌ها و ایجاد فوری آن‌ها بود. هدف از کلاس های ناشناس کاهش دیگ بخار و ارائه راهی کوتاه و آسان برای نمایش کلاس ها به عنوان رکورد بود. بیایید نگاهی به روش قدیمی معمولی برای ایجاد یک رشته جدید در جاوا بیندازیم:
package com.javacodegeeks.advanced.design;

public class AnonymousClass {
    public static void main( String[] args ) {
        new Thread(
            // Example of creating anonymous class which implements
            // Runnable interface
            new Runnable() {
                @Override
                public void run() {
                    // Implementation here
                }
            }
        ).start();
    }
}
در این مثال، پیاده سازی Runnableاینترفیس بلافاصله به عنوان یک کلاس ناشناس ارائه شده است. اگرچه برخی محدودیت‌های مرتبط با کلاس‌های ناشناس وجود دارد، معایب اصلی استفاده از آن‌ها ساختار ساختاری بسیار پرمخاطب است که جاوا به‌عنوان یک زبان موظف به آن است. حتی فقط یک کلاس ناشناس که هیچ کاری انجام نمی دهد به حداقل 5 خط کد در هر بار نوشتن نیاز دارد.
new Runnable() {
   @Override
   public void run() {
   }
}
خوشبختانه، با جاوا 8، لامبدا و رابط های کاربردی، همه این کلیشه ها به زودی از بین می روند، در نهایت نوشتن کد جاوا واقعا مختصر به نظر می رسد.
package com.javacodegeeks.advanced.design;

public class AnonymousClass {
    public static void main( String[] args ) {
        new Thread( () -> { /* Implementation here */ } ).start();
    }
}

8. دید

قبلاً در قسمت 1 آموزش کمی در مورد قوانین دید و دسترسی در جاوا صحبت کردیم. در این قسمت، ما دوباره به این موضوع می پردازیم، اما در چارچوب زیر طبقه بندی. طراحی کلاس ها و رابط ها (ترجمه مقاله) - 2مشاهده در سطوح مختلف به کلاس‌ها اجازه می‌دهد یا از دیدن کلاس‌ها یا رابط‌های دیگر (مثلاً اگر در بسته‌های مختلف هستند یا درون یکدیگر قرار گرفته‌اند) یا کلاس‌های فرعی از دیدن و دسترسی به متدها، سازنده‌ها و فیلدهای والدینشان جلوگیری می‌کند. در بخش بعدی، ارث، این را در عمل خواهیم دید.

9. ارث

وراثت یکی از مفاهیم کلیدی برنامه نویسی شی گرا است که به عنوان پایه ای برای ایجاد یک کلاس از روابط عمل می کند. همراه با قوانین دید و دسترسی، وراثت به کلاس ها اجازه می دهد تا به صورت سلسله مراتبی طراحی شوند که قابل گسترش و نگهداری باشد. در سطح مفهومی، وراثت در جاوا با استفاده از کلاس‌بندی فرعی و کلمه کلیدی extends به همراه کلاس والد پیاده‌سازی می‌شود. یک زیر کلاس تمام عناصر عمومی و محافظت شده کلاس والد را به ارث می برد. علاوه بر این، یک زیر کلاس عناصر بسته-خصوصی کلاس والد خود را به ارث می برد اگر هر دو (زیر کلاس و کلاس) در یک بسته باشند. همانطور که گفته شد، مهم نیست، مهم نیست که شما چه چیزی را طراحی می کنید، به حداقل مجموعه روش هایی که یک کلاس به صورت عمومی یا به زیر کلاس های آن ارائه می دهد، پایبند باشید. به عنوان مثال، بیایید به کلاس Parentو زیر کلاس آن نگاه کنیم Childتا تفاوت در سطوح دید و اثرات آنها را نشان دهیم.
package com.javacodegeeks.advanced.design;

public class Parent {
    // Everyone can see it
    public static final String CONSTANT = "Constant";

    // No one can access it
    private String privateField;
    // Only subclasses can access it
    protected String protectedField;

    // No one can see it
    private class PrivateClass {
    }

    // Only visible to subclasses
    protected interface ProtectedInterface {
    }

    // Everyone can call it
    public void publicAction() {
    }

    // Only subclass can call it
    protected void protectedAction() {
    }

    // No one can call it
    private void privateAction() {
    }

    // Only subclasses in the same package can call it
    void packageAction() {
    }
}
package com.javacodegeeks.advanced.design;

// Resides in the same package as parent class
public class Child extends Parent implements Parent.ProtectedInterface {
    @Override
    protected void protectedAction() {
        // Calls parent's method implementation
        super.protectedAction();
    }

    @Override
    void packageAction() {
        // Do nothing, no call to parent's method implementation
    }

    public void childAction() {
        this.protectedField = "value";
    }
}
وراثت به خودی خود یک موضوع بسیار بزرگ است، با جزئیات بسیار دقیق مخصوص جاوا. با این حال، چند قانون وجود دارد که به راحتی می توان آنها را دنبال کرد و می تواند در حفظ اختصار سلسله مراتب کلاس کمک زیادی کند. در جاوا، هر زیر کلاس می تواند هر روش ارثی والد خود را لغو کند، مگر اینکه نهایی شده باشد. با این حال، سینتکس یا کلمه کلیدی خاصی برای علامت گذاری یک روش به عنوان لغو شده وجود ندارد که می تواند منجر به سردرگمی شود. به همین دلیل است که حاشیه نویسی @Override معرفی شد : هر زمان که هدف شما نادیده گرفتن یک روش ارثی است، لطفاً از حاشیه نویسی @Override برای نشان دادن مختصر آن استفاده کنید. یکی دیگر از معضلاتی که توسعه دهندگان جاوا به طور مداوم در طراحی با آن مواجه هستند، ساخت سلسله مراتب کلاس (با کلاس های مشخص یا انتزاعی) در مقابل پیاده سازی رابط ها است. ما قویاً توصیه می‌کنیم که در صورت امکان، رابط‌ها را به کلاس‌ها یا کلاس‌های انتزاعی ترجیح دهید. رابط‌ها سبک‌تر هستند، آزمایش و نگهداری آسان‌تر هستند و همچنین عوارض جانبی تغییرات پیاده‌سازی را به حداقل می‌رسانند. بسیاری از تکنیک های برنامه نویسی پیشرفته، مانند ایجاد کلاس های پروکسی در کتابخانه استاندارد جاوا، به شدت به رابط ها متکی هستند.

10. ارث چندگانه

برخلاف C++ و برخی زبان‌های دیگر، جاوا از وراثت چندگانه پشتیبانی نمی‌کند: در جاوا، هر کلاس می‌تواند تنها یک والد مستقیم داشته باشد (با کلاس Objectدر بالای سلسله مراتب). با این حال، یک کلاس می‌تواند چندین رابط را پیاده‌سازی کند، و بنابراین انباشتن رابط تنها راه برای دستیابی (یا شبیه‌سازی) وراثت چندگانه در جاوا است.
package com.javacodegeeks.advanced.design;

public class MultipleInterfaces implements Runnable, AutoCloseable {
    @Override
    public void run() {
        // Some implementation here
    }

    @Override
    public void close() throws Exception {
       // Some implementation here
    }
}
پیاده سازی چندین رابط در واقع بسیار قدرتمند است، اما اغلب نیاز به استفاده مکرر از پیاده سازی منجر به سلسله مراتب کلاس عمیق به عنوان راهی برای غلبه بر عدم پشتیبانی جاوا برای وراثت چندگانه می شود. 
public class A implements Runnable {
    @Override
    public void run() {
        // Some implementation here
    }
}
// Class B wants to inherit the implementation of run() method from class A.
public class B extends A implements AutoCloseable {
    @Override
    public void close() throws Exception {
       // Some implementation here
    }
}
// Class C wants to inherit the implementation of run() method from class A
// and the implementation of close() method from class B.
public class C extends B implements Readable {
    @Override
    public int read(java.nio.CharBuffer cb) throws IOException {
       // Some implementation here
    }
}
و غیره... نسخه اخیر جاوا 8 تا حدودی به مشکل تزریق روش پیش فرض پرداخته است. به دلیل روش های پیش فرض، رابط ها در واقع نه تنها یک قرارداد، بلکه یک پیاده سازی را نیز ارائه می دهند. بنابراین، کلاس هایی که این رابط ها را پیاده سازی می کنند، به طور خودکار این متدهای پیاده سازی شده را به ارث می برند. مثلا:
package com.javacodegeeks.advanced.design;

public interface DefaultMethods extends Runnable, AutoCloseable {
    @Override
    default void run() {
        // Some implementation here
    }

    @Override
    default void close() throws Exception {
       // Some implementation here
    }
}

// Class C inherits the implementation of run() and close() methods from the
// DefaultMethods interface.
public class C implements DefaultMethods, Readable {
    @Override
    public int read(java.nio.CharBuffer cb) throws IOException {
       // Some implementation here
    }
}
به خاطر داشته باشید که وراثت چندگانه یک ابزار بسیار قدرتمند، اما در عین حال خطرناک است. مشکل معروف Diamond of Death اغلب به عنوان یک نقص بزرگ در پیاده سازی چند وراثت ذکر می شود و توسعه دهندگان را مجبور می کند تا سلسله مراتب کلاس ها را با دقت طراحی کنند. متاسفانه رابط های جاوا 8 با روش های پیش فرض نیز قربانی این نقص ها می شوند.
interface A {
    default void performAction() {
    }
}

interface B extends A {
    @Override
    default void performAction() {
    }
}

interface C extends A {
    @Override
    default void performAction() {
    }
}
به عنوان مثال، قطعه کد زیر کامپایل نخواهد شد:
// E is not compilable unless it overrides performAction() as well
interface E extends B, C {
}
در این مرحله، منصفانه است که بگوییم جاوا به عنوان یک زبان همیشه سعی کرده است از موارد گوشه ای برنامه نویسی شی گرا اجتناب کند، اما با تکامل زبان، برخی از این موارد ناگهان ظاهر شده اند. 

11. ارث و ترکیب

خوشبختانه، وراثت تنها راه طراحی کلاس شما نیست. جایگزین دیگری که بسیاری از توسعه دهندگان معتقدند بسیار بهتر از وراثت است، ترکیب است. ایده بسیار ساده است: به جای ایجاد یک سلسله مراتب از کلاس ها، آنها باید از کلاس های دیگر تشکیل شوند. بیایید به این مثال نگاه کنیم:
// E is not compilable unless it overrides performAction() as well
interface E extends B, C {
}
این کلاس Vehicleاز یک موتور و چرخ تشکیل شده است (به علاوه بسیاری از قطعات دیگر که برای سادگی کنار گذاشته شده اند). با این حال، می توان گفت که یک کلاس Vehicleیک موتور نیز هست، بنابراین می توان آن را با استفاده از وراثت طراحی کرد. 
public class Vehicle extends Engine {
    private Wheels[] wheels;
    // ...
}
کدام راه حل طراحی صحیح خواهد بود؟ دستورالعمل های اصلی اصلی به عنوان اصول IS-A (است) و HAS-A (شامل) شناخته می شوند. IS-A یک رابطه ارثی است: یک کلاس فرعی همچنین مشخصات کلاس کلاس والد را برآورده می کند و یک تغییر از کلاس والد. subclass) والد خود را گسترش می دهد. اگر می خواهید بدانید که آیا یک موجودیت دیگری را گسترش می دهد، یک تست مطابقت انجام دهید - IS -A (است).") بنابراین، HAS-A یک رابطه ترکیبی است: یک کلاس دارای یک شی است (یا حاوی) است که در بیشتر موارد، اصل HAS-A به دلایل متعددی بهتر از IS-A کار می کند: 
  • طراحی انعطاف پذیرتر است.
  • مدل پایدارتر است زیرا تغییر از طریق سلسله مراتب کلاس منتشر نمی شود.
  • یک کلاس و ترکیب آن در مقایسه با ترکیب، که به شدت یک والدین و زیر کلاس آن را جفت می‌کند، پیوند ضعیفی دارند.
  • رشته منطقی فکر در یک کلاس ساده تر است، زیرا تمام وابستگی های آن در یک مکان در آن گنجانده شده است. 
صرف نظر از این، وراثت جای خود را دارد و تعدادی از مشکلات طراحی موجود را به طرق مختلف حل می کند، بنابراین نباید از آن غافل شد. لطفاً هنگام طراحی مدل شی گرا خود این دو گزینه را در نظر داشته باشید.

12. کپسولاسیون.

مفهوم کپسوله سازی در برنامه نویسی شی گرا مخفی کردن تمام جزئیات پیاده سازی (مانند حالت عملیاتی، روش های داخلی و غیره) از دنیای بیرون است. مزایای کپسوله سازی، قابلیت نگهداری و سهولت تغییر است. پیاده‌سازی داخلی کلاس پنهان است، کار با داده‌های کلاس منحصراً از طریق روش‌های عمومی کلاس اتفاق می‌افتد (مشکل واقعی اگر در حال توسعه یک کتابخانه یا چارچوبی هستید که توسط افراد زیادی استفاده می‌شود). کپسوله سازی در جاوا از طریق قوانین قابل مشاهده و دسترسی به دست می آید. در جاوا، بهترین کار این است که هرگز فیلدها را مستقیماً در معرض دید قرار ندهید، فقط از طریق گیرنده و تنظیم کننده (مگر اینکه فیلدها نهایی شده باشند). مثلا:
package com.javacodegeeks.advanced.design;

public class Encapsulation {
    private final String email;
    private String address;

    public Encapsulation( final String email ) {
        this.email = email;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    public String getEmail() {
        return email;
    }
}
این مثال یادآور چیزی است که در زبان جاوا JavaBeans نامیده می‌شود: کلاس‌های استاندارد جاوا بر اساس مجموعه‌ای از قراردادها نوشته می‌شوند، یکی از آنها امکان دسترسی به فیلدها را تنها با استفاده از متدهای دریافت‌کننده و تنظیم‌کننده می‌دهد. همانطور که قبلاً در بخش ارث تأکید کردیم، لطفاً همیشه حداقل قرارداد تبلیغاتی را در یک کلاس با استفاده از اصول کپسولاسیون رعایت کنید. هر چیزی که نباید عمومی باشد، باید خصوصی شود (یا بسته به مشکلی که حل می کنید، محافظت شده/ بسته خصوصی شود). این در درازمدت با دادن آزادی طراحی بدون (یا حداقل به حداقل رساندن) تغییرات، نتیجه خواهد داد. 

13. کلاس های نهایی و روش ها

در جاوا، راهی برای جلوگیری از تبدیل شدن یک کلاس به زیر کلاس کلاس دیگر وجود دارد: کلاس دیگر باید نهایی اعلام شود. 
package com.javacodegeeks.advanced.design;

public final class FinalClass {
}
همان کلمه کلیدی نهایی در یک اعلان متد، از رد کلاس های فرعی متد جلوگیری می کند. 
package com.javacodegeeks.advanced.design;

public class FinalMethod {
    public final void performAction() {
    }
}
هیچ قانون کلی برای تصمیم گیری در مورد نهایی بودن یا نبودن یک کلاس یا متدها وجود ندارد. کلاس‌ها و متدهای نهایی توسعه‌پذیری را محدود می‌کنند و خیلی سخت است که از قبل فکر کنیم که آیا یک کلاس باید ارث بری شود یا نه، یا اینکه آیا یک متد باید یا نباید در آینده لغو شود. این امر به ویژه برای توسعه دهندگان کتابخانه مهم است، زیرا تصمیمات طراحی مانند این می تواند به طور قابل توجهی کاربرد کتابخانه را محدود کند. کتابخانه استاندارد جاوا چندین نمونه از کلاس های نهایی دارد که معروف ترین آنها کلاس String است. در مراحل اولیه، این تصمیم برای جلوگیری از هرگونه تلاش توسعه دهندگان برای ارائه راه حل «بهتر» خود برای اجرای رشته اتخاذ شد. 

14. بعدی چیست

در این قسمت از درس به مفاهیم برنامه نویسی شی گرا در جاوا پرداختیم. ما همچنین نگاهی گذرا به برنامه نویسی قرارداد انداختیم، برخی از مفاهیم کاربردی را لمس کردیم و دیدیم که چگونه زبان در طول زمان تکامل یافته است. در قسمت بعدی درس، با ژنریک ها آشنا می شویم و اینکه چگونه رویکرد ما به ایمنی نوع در برنامه نویسی را تغییر می دهد. 

15. دانلود کد منبع

می توانید منبع را از اینجا دانلود کنید - advanced-java-part-3 Source: How to design Classes an
نظرات
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION