JavaRush /จาวาบล็อก /Random-TH /การออกแบบคลาสและอินเทอร์เฟซ (การแปลบทความ)
fatesha
ระดับ

การออกแบบคลาสและอินเทอร์เฟซ (การแปลบทความ)

เผยแพร่ในกลุ่ม
การออกแบบคลาสและอินเทอร์เฟซ (การแปลบทความ) - 1

เนื้อหา

  1. การแนะนำ
  2. อินเทอร์เฟซ
  3. เครื่องหมายอินเทอร์เฟซ
  4. ส่วนต่อประสานการทำงาน วิธีการแบบสแตติก และวิธีการเริ่มต้น
  5. ชั้นเรียนที่เป็นนามธรรม
  6. คลาสที่ไม่เปลี่ยนรูป (ถาวร)
  7. ชั้นเรียนที่ไม่ระบุชื่อ
  8. ทัศนวิสัย
  9. มรดก
  10. มรดกหลายรายการ
  11. มรดกและองค์ประกอบ
  12. การห่อหุ้ม
  13. คลาสสุดท้ายและวิธีการ
  14. อะไรต่อไป
  15. ดาวน์โหลดซอร์สโค้ด

1. บทนำ

ไม่ว่าคุณจะใช้ภาษาโปรแกรมอะไร (และ Java ก็ไม่มีข้อยกเว้น) การทำตามหลักการออกแบบที่ดีคือกุญแจสำคัญในการเขียนโค้ดที่สะอาด เข้าใจได้ และตรวจสอบได้ และยังสร้างให้มีอายุยืนยาวและรองรับการแก้ปัญหาได้ง่ายอีกด้วย ในส่วนนี้ของบทช่วยสอน เราจะหารือเกี่ยวกับ Building Block พื้นฐานที่ภาษา Java มีให้ และแนะนำหลักการออกแบบสองสามประการเพื่อช่วยให้คุณตัดสินใจออกแบบได้ดีขึ้น โดยเฉพาะอย่างยิ่ง เราจะหารือเกี่ยวกับอินเทอร์เฟซและอินเทอร์เฟซโดยใช้วิธีการเริ่มต้น (คุณลักษณะใหม่ใน Java 8) คลาสนามธรรมและคลาสสุดท้าย คลาสที่ไม่เปลี่ยนรูป การสืบทอด องค์ประกอบ และทบทวนกฎการมองเห็น (หรือการเข้าถึง) ที่เรากล่าวถึงสั้น ๆ ใน ส่วนที่ 1 บทเรียน“วิธีสร้างและทำลายวัตถุ” .

2. อินเทอร์เฟซ

ในการเขียนโปรแกรม เชิงวัตถุแนวคิดของอินเทอร์เฟซเป็นพื้นฐานสำหรับการพัฒนาสัญญา โดยสรุป อินเทอร์เฟซกำหนดชุดของวิธีการ (สัญญา) และแต่ละคลาสที่ต้องการการสนับสนุนสำหรับอินเทอร์เฟซเฉพาะนั้นจะต้องจัดให้มีการนำวิธีการเหล่านั้นไปใช้: เป็นแนวคิดที่ค่อนข้างเรียบง่ายแต่ทรงพลัง ภาษาการเขียนโปรแกรมจำนวนมากมีอินเทอร์เฟซในรูปแบบเดียวหรืออีกรูปแบบหนึ่ง แต่ Java โดยเฉพาะให้การสนับสนุนภาษาสำหรับสิ่งนี้ มาดูคำจำกัดความอินเทอร์เฟซอย่างง่ายใน Java กัน
package com.javacodegeeks.advanced.design;

public interface SimpleInterface {
void performAction();
}
ในตัวอย่างข้างต้น อินเทอร์เฟซที่เราเรียกว่าSimpleInterfaceประกาศเพียงวิธีเดียวที่เรียกperformActionว่า ข้อแตกต่างที่สำคัญระหว่างอินเทอร์เฟซและคลาสก็คือ อินเทอร์เฟซจะสรุปว่าผู้ติดต่อควรเป็นอย่างไร (ประกาศวิธีการ) แต่ไม่ได้จัดให้มีการใช้งาน อย่างไรก็ตาม อินเทอร์เฟซใน Java อาจมีความซับซ้อนมากขึ้น โดยอาจรวมถึงอินเทอร์เฟซแบบซ้อน คลาส จำนวน จำนวน คำอธิบายประกอบ และค่าคงที่ ตัวอย่างเช่น:
package com.javacodegeeks.advanced.design;

public interface InterfaceWithDefinitions {
    String CONSTANT = "CONSTANT";

    enum InnerEnum {
        E1, E2;
    }

    class InnerClass {
    }

    interface InnerInterface {
        void performInnerAction();
    }

    void performAction();
}
ในตัวอย่างที่ซับซ้อนกว่านี้ มีข้อจำกัดหลายประการที่อินเทอร์เฟซกำหนดโดยไม่มีเงื่อนไขกับโครงสร้างการซ้อนและการประกาศเมธอด และสิ่งเหล่านี้บังคับใช้โดยคอมไพเลอร์ Java ประการแรก แม้ว่าจะไม่ได้ประกาศอย่างชัดเจน แต่การประกาศวิธีการทุกรายการในอินเทอร์เฟซจะเป็นแบบสาธารณะ (และสามารถเป็นแบบสาธารณะได้เท่านั้น) ดังนั้นการประกาศวิธีการต่อไปนี้จึงเทียบเท่ากัน:
public void performAction();
void performAction();
เป็นที่น่าสังเกตว่าทุก ๆ วิธีการในอินเทอร์เฟซได้รับการประกาศโดยปริยายabstractและแม้แต่การประกาศวิธีการเหล่านี้ก็ยังเทียบเท่ากัน:
public abstract void performAction();
public void performAction();
void performAction();
สำหรับฟิลด์คงที่ที่ประกาศไว้ นอกเหนือจากการเป็นสาธารณะแล้ว ฟิลด์เหล่านั้นยังเป็นแบบคงที่ โดยปริยาย และทำเครื่องหมายเป็นขั้นสุดท้าย ด้วย ดังนั้นการประกาศต่อไปนี้จึงเทียบเท่ากันด้วย:
String CONSTANT = "CONSTANT";
public static final String CONSTANT = "CONSTANT";
สุดท้ายนี้ คลาสที่ซ้อนกัน อิน เท อร์เฟซ หรือการนับ นอกเหนือจากการเป็นสาธารณะยังได้รับการประกาศโดยปริยายstatic ด้วย ตัวอย่างเช่น การประกาศเหล่านี้เทียบเท่ากับ:
class InnerClass {
}

static class InnerClass {
}
สไตล์ที่คุณเลือกนั้นเป็นความชอบส่วนตัว แต่การรู้คุณสมบัติง่ายๆ ของอินเทอร์เฟซเหล่านี้สามารถช่วยคุณประหยัดจากการพิมพ์ที่ไม่จำเป็นได้

3. เครื่องหมายอินเทอร์เฟซ

อินเทอร์เฟซของมาร์กเกอร์เป็นอินเทอร์เฟซชนิดพิเศษที่ไม่มีเมธอดหรือโครงสร้างที่ซ้อนกันอื่นๆ วิธีที่ไลบรารี Java กำหนด:
public interface Cloneable {
}
เครื่องหมายอินเทอร์เฟซไม่ใช่สัญญาต่อตัว แต่เป็นเทคนิคที่ค่อนข้างมีประโยชน์สำหรับการ "แนบ" หรือ "เชื่อมโยง" ลักษณะเฉพาะบางอย่างกับคลาส ตัวอย่างเช่น ในส่วนที่เกี่ยวกับCloneableคลาสจะถูกทำเครื่องหมายว่า cloneable แต่วิธีที่สามารถทำได้หรือควรจะนำไปใช้นั้นไม่ได้เป็นส่วนหนึ่งของอินเทอร์เฟซ อีกตัวอย่างที่มีชื่อเสียงและใช้กันอย่างแพร่หลายของเครื่องหมายอินเทอร์เฟซคือSerializable:
public interface Serializable {
}
อินเทอร์เฟซนี้ทำเครื่องหมายคลาสว่าเหมาะสำหรับการซีเรียลไลซ์และการดีซีเรียลไลซ์ และอีกครั้ง ไม่ได้ระบุว่าสิ่งนี้สามารถหรือควรนำไปใช้อย่างไร เครื่องหมายอินเทอร์เฟซมีตำแหน่งในการเขียนโปรแกรมเชิงวัตถุ แม้ว่าจะไม่เป็นไปตามวัตถุประสงค์หลักของอินเทอร์เฟซที่เป็นสัญญาก็ตาม 

4. อินเตอร์เฟซการทำงาน วิธีการเริ่มต้น และวิธีการแบบคงที่

นับตั้งแต่เปิดตัว Java 8 อินเทอร์เฟซได้รับคุณสมบัติใหม่ที่น่าสนใจมากมาย: วิธีการคงที่ วิธีการเริ่มต้น และการแปลงอัตโนมัติจาก lambdas (อินเทอร์เฟซการทำงาน) ในส่วนของอินเทอร์เฟซ เราเน้นย้ำถึงข้อเท็จจริงที่ว่าอินเทอร์เฟซใน Java สามารถประกาศวิธีการได้เท่านั้น แต่ไม่ได้จัดให้มีการใช้งาน ด้วยวิธีการเริ่มต้น สิ่งต่างๆ จะแตกต่างออกไป: อินเทอร์เฟซสามารถทำเครื่องหมายวิธีการด้วย คำหลัก เริ่มต้นและจัดเตรียมการนำไปปฏิบัติ ตัวอย่างเช่น:
package com.javacodegeeks.advanced.design;

public interface InterfaceWithDefaultMethods {
    void performAction();

    default void performDefaulAction() {
        // Implementation here
    }
}
เมื่ออยู่ที่ระดับอินสแตนซ์ วิธีการเริ่มต้นอาจถูกแทนที่โดยการใช้อินเทอร์เฟซแต่ละครั้ง แต่ขณะนี้อินเทอร์เฟซยังสามารถรวม วิธีการ คงที่ได้เช่น: package com.javacodegeeks.advanced.design;
public interface InterfaceWithDefaultMethods {
    static void createAction() {
        // Implementation here
    }
}
อาจกล่าวได้ว่าการติดตั้งใช้งานในอินเทอร์เฟซขัดขวางวัตถุประสงค์ทั้งหมดของการเขียนโปรแกรมตามสัญญา แต่มีสาเหตุหลายประการว่าทำไมคุณลักษณะเหล่านี้จึงถูกนำมาใช้ในภาษา Java และไม่ว่าคุณลักษณะเหล่านี้จะมีประโยชน์หรือน่าสับสนเพียงใด คุณลักษณะเหล่านี้ก็พร้อมสำหรับคุณและการใช้งานของคุณ อินเทอร์เฟซการใช้งานเป็นเรื่องราวที่แตกต่างไปจากเดิมอย่างสิ้นเชิงและได้รับการพิสูจน์แล้วว่าเป็นส่วนเสริมที่มีประโยชน์มากสำหรับภาษา โดยพื้นฐานแล้ว ส่วนต่อประสานการทำงานคือส่วนต่อประสานที่มีวิธีนามธรรมเพียงวิธีเดียวที่ประกาศไว้ Runnableอินเทอร์เฟซไลบรารีมาตรฐานเป็นตัวอย่างที่ดีของแนวคิดนี้
@FunctionalInterface
public interface Runnable {
    void run();
}
คอมไพเลอร์ Java ปฏิบัติต่ออินเทอร์เฟซการทำงานที่แตกต่างกัน และสามารถเปลี่ยนฟังก์ชันแลมบ์ดาให้เป็นการใช้งานอินเทอร์เฟซการทำงานที่เหมาะสม ลองพิจารณาคำอธิบายฟังก์ชันต่อไปนี้: 
public void runMe( final Runnable r ) {
    r.run();
}
หากต้องการเรียกใช้ฟังก์ชันนี้ใน Java 7 และต่ำกว่า จะต้องจัดให้มีการใช้งานอินเทอร์เฟซRunnable(เช่น การใช้คลาสที่ไม่ระบุชื่อ) แต่ใน Java 8 ก็เพียงพอแล้วที่จะจัดให้มีการใช้งานเมธอด run() โดยใช้ไวยากรณ์แลมบ์ดา:
runMe( () -> System.out.println( "Run!" ) );
นอกจากนี้ คำอธิบายประกอบ @FunctionalInterface (คำอธิบายประกอบจะกล่าวถึงรายละเอียดในส่วนที่ 5 ของบทช่วยสอน) บอกเป็นนัยว่าคอมไพเลอร์สามารถตรวจสอบได้ว่าอินเทอร์เฟซมีเมธอดนามธรรมเพียงวิธีเดียวหรือไม่ ดังนั้นการเปลี่ยนแปลงใด ๆ ที่เกิดขึ้นกับอินเทอร์เฟซในอนาคตจะไม่ละเมิดสมมติฐานนี้ .

5. ชั้นเรียนบทคัดย่อ

แนวคิดที่น่าสนใจอีกประการหนึ่งที่สนับสนุนโดยภาษา Java คือแนวคิดของคลาสนามธรรม คลาสนามธรรมค่อนข้างคล้ายกับอินเทอร์เฟซใน Java 7 และอยู่ใกล้กับอินเทอร์เฟซวิธีการเริ่มต้นใน Java 8 มาก คลาสนามธรรมไม่สามารถสร้างอินสแตนซ์ได้ซึ่งแตกต่างจากคลาสทั่วไป แต่สามารถจัดคลาสย่อยได้ (โปรดดูที่ส่วนการสืบทอดสำหรับรายละเอียดเพิ่มเติม) ที่สำคัญกว่านั้น คลาสนามธรรมสามารถมีวิธีการแบบนามธรรมได้: วิธีการชนิดพิเศษที่ไม่มีการใช้งาน เช่นเดียวกับอินเทอร์เฟซ ตัวอย่างเช่น:
package com.javacodegeeks.advanced.design;

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

    public abstract void performAnotherAction();
}
ในตัวอย่างนี้ คลาสSimpleAbstractClassถูกประกาศให้เป็นนามธรรมและมีวิธีการประกาศแบบนามธรรมหนึ่งวิธี คลาสนามธรรมมีประโยชน์มาก รายละเอียดการใช้งานส่วนใหญ่หรือบางส่วนสามารถแชร์ระหว่างคลาสย่อยจำนวนมากได้ แต่อย่างไรก็ตาม พวกเขายังคงแง้มประตูเอาไว้ และอนุญาตให้คุณปรับแต่งพฤติกรรมที่มีอยู่ในแต่ละคลาสย่อยโดยใช้วิธีนามธรรม เป็นที่น่าสังเกตว่าไม่เหมือนกับอินเทอร์เฟซซึ่งสามารถมีเพียง การประกาศ สาธารณะ เท่านั้น คลาสนามธรรมสามารถใช้กฎการเข้าถึงแบบเต็มกำลังเพื่อควบคุมการมองเห็นของวิธีนามธรรม

6. ชั้นเรียนทันที

ความไม่เปลี่ยนรูปมีความสำคัญมากขึ้นเรื่อยๆ ในการพัฒนาซอฟต์แวร์ในปัจจุบัน การเพิ่มขึ้นของระบบมัลติคอร์ทำให้เกิดปัญหามากมายที่เกี่ยวข้องกับการแบ่งปันข้อมูลและความเท่าเทียม แต่ปัญหาหนึ่งเกิดขึ้นอย่างแน่นอน: การมีสถานะที่ไม่แน่นอนเพียงเล็กน้อย (หรือไม่มีเลย) นำไปสู่การขยายที่ดีขึ้น (ความสามารถในการปรับขนาด) และการให้เหตุผลที่ง่ายขึ้นเกี่ยวกับระบบ น่าเสียดายที่ภาษา Java ไม่ได้ให้การสนับสนุนที่เหมาะสมสำหรับความไม่เปลี่ยนรูปของคลาส อย่างไรก็ตาม ด้วยการผสมผสานเทคนิคต่างๆ ทำให้สามารถออกแบบคลาสที่ไม่เปลี่ยนรูปได้ ก่อนอื่น ทุกสาขาของชั้นเรียนจะต้องถือเป็นที่สิ้นสุด (ทำเครื่องหมายว่าเป็นที่สิ้นสุด ) นี่เป็นการเริ่มต้นที่ดี แต่ก็ไม่มีการรับประกัน 
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 );
}
และสุดท้าย รับรองการเข้าถึงที่เหมาะสม (getters) สำหรับคอลเลกชัน ความไม่เปลี่ยนรูปจะต้องระบุเป็น wrapper  Collections.unmodifiableXxx: สำหรับอาร์เรย์ วิธีเดียวที่จะระบุความไม่เปลี่ยนรูปได้อย่างแท้จริงคือการจัดทำสำเนาแทนที่จะส่งคืนการอ้างอิงไปยังอาร์เรย์ สิ่งนี้อาจไม่เป็นที่ยอมรับจากมุมมองเชิงปฏิบัติ เนื่องจากมันขึ้นอยู่กับขนาดของอาเรย์เป็นอย่างมาก และอาจสร้างแรงกดดันมหาศาลให้กับผู้รวบรวมขยะ
public String[] getArrayOfStrings() {
    return Arrays.copyOf( arrayOfStrings, arrayOfStrings.length );
}
แม้แต่ตัวอย่างเล็กๆ น้อยๆ นี้ก็ยังให้ความคิดที่ดีว่าการเปลี่ยนแปลงไม่ได้ยังไม่ใช่พลเมืองชั้นหนึ่งในชวา สิ่งต่างๆ อาจมีความซับซ้อนได้หากคลาสที่ไม่เปลี่ยนรูปมีฟิลด์ที่อ้างอิงถึงอ็อบเจ็กต์ของคลาสอื่น คลาสเหล่านั้นควรไม่เปลี่ยนรูปเช่นกัน แต่ไม่มีวิธีใดที่จะรับประกันสิ่งนี้ มีโปรแกรมวิเคราะห์ซอร์สโค้ด Java ที่เหมาะสมหลายตัว เช่น FindBugs และ PMD ที่สามารถช่วยได้อย่างมากโดยการตรวจสอบโค้ดของคุณและชี้ให้เห็นข้อบกพร่องทั่วไปในการเขียนโปรแกรม Java เครื่องมือเหล่านี้เป็นเพื่อนที่ดีของนักพัฒนา Java ทุกคน

7. ชั้นเรียนที่ไม่ระบุชื่อ

ในยุคก่อน Java 8 คลาสที่ไม่เปิดเผยตัวตนเป็นวิธีเดียวที่จะให้แน่ใจว่าคลาสถูกกำหนดได้ทันทีและเริ่มต้นทันที วัตถุประสงค์ของคลาสที่ไม่ระบุชื่อคือเพื่อลดรูปแบบสำเร็จรูปและจัดเตรียมวิธีที่สั้นและง่ายในการแสดงคลาสเป็นบันทึก มาดูวิธีดั้งเดิมในการสร้างเธรดใหม่ใน Java:
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อินเทอร์เฟซจะถูกจัดเตรียมทันทีเป็นคลาสที่ไม่ระบุชื่อ แม้ว่าจะมีข้อจำกัดบางประการที่เกี่ยวข้องกับคลาสที่ไม่ระบุชื่อ แต่ข้อเสียเปรียบหลักของการใช้คลาสเหล่านี้คือไวยากรณ์การสร้างที่ละเอียดมากซึ่ง Java เป็นภาษาจำเป็นต้องใช้ แม้แต่คลาสที่ไม่เปิดเผยตัวตนที่ไม่ได้ทำอะไรเลยก็จำเป็นต้องมีโค้ดอย่างน้อย 5 บรรทัดทุกครั้งที่เขียน
new Runnable() {
   @Override
   public void run() {
   }
}
โชคดีที่มี Java 8, lambda และอินเทอร์เฟซการทำงาน แบบเหมารวมเหล่านี้จะหายไปในไม่ช้า ในที่สุดการเขียนโค้ด Java จะดูกระชับอย่างแท้จริง
package com.javacodegeeks.advanced.design;

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

8. การมองเห็น

เราได้พูดคุยกันเล็กน้อยเกี่ยวกับกฎการมองเห็นและการเข้าถึงใน Java ในส่วนที่ 1 ของบทช่วยสอน ในส่วนนี้ เราจะกลับมาทบทวนหัวข้อนี้อีกครั้ง แต่ในบริบทของคลาสย่อย การออกแบบคลาสและอินเทอร์เฟซ (การแปลบทความ) - 2การมองเห็นในระดับที่แตกต่างกันอนุญาตหรือป้องกันไม่ให้คลาสมองเห็นคลาสหรืออินเทอร์เฟซอื่น ๆ (เช่น หากอยู่ในแพ็คเกจที่แตกต่างกันหรือซ้อนกันภายในกันและกัน) หรือคลาสย่อยไม่ให้เห็นและเข้าถึงเมธอด ตัวสร้าง และฟิลด์ของพาเรนต์ ในส่วนถัดไป เรื่องการสืบทอด เราจะเห็นการดำเนินการนี้

9. มรดก

การสืบทอดเป็นหนึ่งในแนวคิดหลักของการเขียนโปรแกรมเชิงวัตถุ ซึ่งทำหน้าที่เป็นพื้นฐานสำหรับการสร้างคลาสของความสัมพันธ์ เมื่อรวมกับกฎการมองเห็นและการเข้าถึง การสืบทอดจะช่วยให้คลาสได้รับการออกแบบเป็นลำดับชั้นที่สามารถขยายและดูแลรักษาได้ ในระดับแนวคิด การสืบทอดใน Java จะถูกนำไปใช้โดยใช้คลาสย่อยและ คีย์เวิร์ด ขยายร่วมกับคลาสพาเรนต์ คลาสย่อยสืบทอดองค์ประกอบสาธารณะและองค์ประกอบที่ได้รับการป้องกันทั้งหมดของคลาสพาเรนต์ นอกจากนี้ คลาสย่อยจะสืบทอดองค์ประกอบแพ็กเกจส่วนตัวของคลาสพาเรนต์ หากทั้งสองคลาส (คลาสย่อยและคลาส) อยู่ในแพ็กเกจเดียวกัน ดังที่กล่าวไปแล้ว เป็นสิ่งสำคัญมาก ไม่ว่าคุณจะพยายามออกแบบอะไรก็ตาม จะต้องยึดติดกับชุดวิธีการขั้นต่ำที่คลาสเปิดเผยต่อสาธารณะหรือคลาสย่อย ตัวอย่างเช่น ลองดูที่คลาส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";
    }
}
Inheritance เป็นหัวข้อที่ใหญ่มากในตัวเอง โดยมีรายละเอียดเล็กๆ น้อยๆ มากมายเกี่ยวกับ Java โดยเฉพาะ อย่างไรก็ตาม มีกฎบางประการที่ง่ายต่อการปฏิบัติตามและสามารถช่วยรักษาลำดับชั้นของชั้นเรียนให้สั้นกระชับได้ ใน Java แต่ละคลาสย่อยสามารถแทนที่เมธอดที่สืบทอดมาจากพาเรนต์ของมันได้ เว้นแต่จะได้รับการประกาศขั้นสุดท้าย อย่างไรก็ตาม ไม่มีไวยากรณ์หรือคีย์เวิร์ดพิเศษในการทำเครื่องหมายวิธีการว่าถูกแทนที่ ซึ่งอาจนำไปสู่ความสับสนได้ นี่คือสาเหตุว่าทำไมจึงมีการนำคำอธิบาย ประกอบ @Overrideมาใช้: เมื่อใดก็ตามที่เป้าหมายของคุณคือการแทนที่วิธีที่สืบทอดมา โปรดใช้ คำอธิบายประกอบ @Overrideเพื่อระบุอย่างชัดเจน ภาวะที่กลืนไม่เข้าคายไม่ออกอีกประการหนึ่งที่นักพัฒนา Java เผชิญอยู่ตลอดเวลาในการออกแบบคือการสร้างลำดับชั้นของคลาส (ด้วยคลาสที่เป็นรูปธรรมหรือนามธรรม) เทียบกับการใช้งานอินเทอร์เฟซ เราขอแนะนำอย่างยิ่งให้เลือกใช้อินเทอร์เฟซมากกว่าคลาสหรือคลาสนามธรรมทุกครั้งที่เป็นไปได้ อินเทอร์เฟซมีน้ำหนักเบา ทดสอบและบำรุงรักษาง่ายกว่า และยังลดผลข้างเคียงจากการเปลี่ยนแปลงการใช้งานอีกด้วย เทคนิคการเขียนโปรแกรมขั้นสูงหลายอย่าง เช่น การสร้างคลาสพร็อกซีในไลบรารีมาตรฐาน Java อาศัยอินเทอร์เฟซอย่างมาก

10. มรดกหลายรายการ

ไม่เหมือนกับ C++ และภาษาอื่นๆ บางภาษา Java ไม่รองรับการสืบทอดหลายรายการ โดยใน Java แต่ละคลาสสามารถมีพาเรนต์โดยตรงได้เพียงพาเรนต์เดียวเท่านั้น (โดยมีคลาสObjectอยู่ด้านบนสุดของลำดับชั้น) อย่างไรก็ตาม คลาสสามารถใช้หลายอินเทอร์เฟซได้ ดังนั้นการซ้อนอินเทอร์เฟซจึงเป็นวิธีเดียวที่จะบรรลุ (หรือจำลอง) การสืบทอดหลายรายการใน Java
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
    }
}
การใช้งานหลายอินเทอร์เฟซนั้นค่อนข้างทรงพลัง แต่บ่อยครั้งที่ความจำเป็นในการใช้งานซ้ำแล้วซ้ำเล่านำไปสู่ลำดับชั้นของคลาสระดับลึกซึ่งเป็นวิธีการเอาชนะการขาดการสนับสนุนของ Java สำหรับการสืบทอดหลายรายการ 
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
    }
}
และอื่นๆ... Java 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 ที่รู้จักกันดีมักถูกอ้างถึงว่าเป็นข้อบกพร่องสำคัญในการใช้งานการสืบทอดหลายรายการ ทำให้นักพัฒนาต้องออกแบบลำดับชั้นของคลาสอย่างระมัดระวัง น่าเสียดายที่อินเทอร์เฟซ Java 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 {
}
ณ จุดนี้ มันยุติธรรมที่จะกล่าวว่า Java ในฐานะภาษาหนึ่งพยายามหลีกเลี่ยงกรณีมุมของการเขียนโปรแกรมเชิงวัตถุมาโดยตลอด แต่เมื่อภาษาพัฒนาขึ้น บางกรณีเหล่านั้นก็เริ่มปรากฏขึ้นทันที 

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 เป็นความสัมพันธ์แบบสืบทอด: คลาสย่อยยังเป็นไปตามข้อกำหนดคลาสของคลาสพาเรนต์และการเปลี่ยนแปลงของคลาสพาเรนต์ คลาสย่อย) จะขยายพาเรนต์ หากคุณต้องการทราบว่าเอนทิตีหนึ่งขยายเอนทิตีอื่นหรือไม่ ให้ทำการทดสอบการจับคู่ - IS -A (is).") ดังนั้น HAS-A จึงเป็นความสัมพันธ์ในการเรียบเรียง: คลาสเป็นเจ้าของ (หรือมี) อ็อบเจ็กต์ที่ ในกรณีส่วนใหญ่ หลักการ HAS-A ทำงานได้ดีกว่า IS-A ด้วยเหตุผลหลายประการ: 
  • การออกแบบมีความยืดหยุ่นมากขึ้น
  • โมเดลมีเสถียรภาพมากขึ้นเนื่องจากการเปลี่ยนแปลงไม่ได้เผยแพร่ผ่านลำดับชั้นของคลาส
  • คลาสและองค์ประกอบของคลาสนั้นเชื่อมโยงกันอย่างหลวมๆ เมื่อเปรียบเทียบกับการจัดองค์ประกอบ ซึ่งเชื่อมโยงคลาสพาเรนต์และคลาสย่อยเข้าด้วยกันอย่างแน่นหนา
  • การฝึกคิดเชิงตรรกะในชั้นเรียนนั้นง่ายกว่า เนื่องจากการพึ่งพาทั้งหมดรวมอยู่ในนั้นในที่เดียว 
โดยไม่คำนึงว่ามรดกจะเข้ามาแทนที่และแก้ไขปัญหาการออกแบบจำนวนหนึ่งที่มีอยู่ในหลายวิธี ดังนั้นจึงไม่ควรละเลย โปรดคำนึงถึงทางเลือกทั้งสองนี้เมื่อออกแบบโมเดลเชิงวัตถุของคุณ

12. การห่อหุ้ม

แนวคิดของการห่อหุ้มในการเขียนโปรแกรมเชิงวัตถุคือการซ่อนรายละเอียดการใช้งานทั้งหมด (เช่น โหมดการทำงาน วิธีการภายใน ฯลฯ) จากโลกภายนอก ประโยชน์ของการห่อหุ้มคือการบำรุงรักษาและความง่ายในการเปลี่ยนแปลง การใช้งานภายในของคลาสถูกซ่อนไว้ การทำงานกับข้อมูลคลาสจะเกิดขึ้นผ่านวิธีการสาธารณะของคลาสเท่านั้น (ปัญหาที่แท้จริงหากคุณกำลังพัฒนาไลบรารีหรือเฟรมเวิร์กที่คนจำนวนมากใช้) การห่อหุ้มใน Java ทำได้ผ่านกฎการมองเห็นและการเข้าถึง ใน Java ถือเป็นแนวปฏิบัติที่ดีที่สุดที่จะไม่เปิดเผยฟิลด์โดยตรง เฉพาะผ่าน getters และ setters เท่านั้น (เว้นแต่ว่าฟิลด์นั้นจะถูกทำเครื่องหมายเป็นขั้นสุดท้าย) ตัวอย่างเช่น:
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ในภาษา Java: คลาส Java มาตรฐานถูกเขียนตามชุดของแบบแผน ซึ่งหนึ่งในนั้นอนุญาตให้เข้าถึงฟิลด์ได้โดยใช้เมธอด getter และ setter เท่านั้น ตามที่เราได้เน้นไปแล้วในส่วนการรับมรดก โปรดปฏิบัติตามสัญญาประชาสัมพันธ์ขั้นต่ำในชั้นเรียนเสมอ โดยใช้หลักการห่อหุ้ม ทุกสิ่งที่ไม่ควรเปิดเผยต่อสาธารณะควรกลายเป็นส่วนตัว (หรือป้องกัน/แพ็คเกจส่วนตัว ขึ้นอยู่กับปัญหาที่คุณกำลังแก้ไข) สิ่งนี้จะคุ้มค่าในระยะยาวด้วยการให้อิสระแก่คุณในการออกแบบโดยไม่ต้อง (หรืออย่างน้อยก็ลดทอน) การเปลี่ยนแปลงที่เสียหาย 

13. ชั้นเรียนสุดท้ายและวิธีการ

ใน Java มีวิธีการป้องกันไม่ให้คลาสกลายเป็นคลาสย่อยของคลาสอื่น: คลาสอื่นจะต้องถูกประกาศให้เป็นที่สิ้นสุด 
package com.javacodegeeks.advanced.design;

public final class FinalClass {
}
คำสำคัญสุดท้าย เดียวกัน ในการประกาศวิธีการป้องกันไม่ให้คลาสย่อยแทนที่วิธีการ 
package com.javacodegeeks.advanced.design;

public class FinalMethod {
    public final void performAction() {
    }
}
ไม่มีกฎทั่วไปในการตัดสินใจว่าคลาสหรือเมธอดควรถือเป็นที่สิ้นสุดหรือไม่ คลาสและวิธีการขั้นสุดท้ายจำกัดความสามารถในการขยาย และเป็นเรื่องยากมากที่จะคิดล่วงหน้าว่าคลาสควรหรือไม่ควรสืบทอด หรือควรแทนที่วิธีการในอนาคตหรือไม่ นี่เป็นสิ่งสำคัญอย่างยิ่งสำหรับนักพัฒนาห้องสมุด เนื่องจากการตัดสินใจในการออกแบบเช่นนี้อาจจำกัดความสามารถในการใช้งานของห้องสมุดได้อย่างมาก Java Standard Library มีตัวอย่างคลาสสุดท้ายหลายตัวอย่าง โดยคลาสที่มีชื่อเสียงที่สุดคือคลาส String ในช่วงแรก การตัดสินใจนี้มีขึ้นเพื่อป้องกันไม่ให้นักพัฒนาพยายามหาวิธีแก้ปัญหาที่ "ดีกว่า" ของตนเองในการนำสตริงไปใช้ 

14. อะไรต่อไป

ในส่วนนี้ของบทเรียน เราได้กล่าวถึงแนวคิดของการเขียนโปรแกรมเชิงวัตถุใน Java นอกจากนี้เรายังดูอย่างรวดเร็วเกี่ยวกับการเขียนโปรแกรมตามสัญญา สัมผัสแนวคิดการทำงานบางอย่าง และดูว่าภาษามีการพัฒนาอย่างไรเมื่อเวลาผ่านไป ในส่วนถัดไปของบทเรียน เราจะมาพบกับยาชื่อสามัญและวิธีที่พวกมันเปลี่ยนวิธีที่เราจัดการกับความปลอดภัยของประเภทในการเขียนโปรแกรม 

15. ดาวน์โหลดซอร์สโค้ด

คุณสามารถดาวน์โหลดซอร์สได้ที่นี่ - Advanced-java-part-3 ที่มา: วิธีการออกแบบคลาสและ
ความคิดเห็น
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION