ทำไมฉันควรประกาศคลาสเป็นคลาสนามธรรม?


40

ฉันรู้ไวยากรณ์กฎที่ใช้กับคลาสนามธรรมและฉันต้องการทราบการใช้คลาสนามธรรม

คลาสนามธรรมไม่สามารถสร้างอินสแตนซ์ได้โดยตรง แต่สามารถขยายได้ด้วยคลาสอื่น

ข้อดีของการทำเช่นนั้นคืออะไร?

มันแตกต่างจากส่วนต่อประสานอย่างไร

ฉันรู้ว่าคลาสหนึ่งสามารถใช้หลายอินเตอร์เฟส แต่สามารถขยายคลาสนามธรรมได้เพียงคลาสเดียวเท่านั้น นั่นเป็นข้อแตกต่างระหว่างอินเตอร์เฟสและคลาสนามธรรมหรือไม่?

ฉันรู้เกี่ยวกับการใช้งานส่วนต่อประสาน ฉันได้เรียนรู้ว่าจากรูปแบบการมอบหมายกิจกรรมของ AWT ใน Java

ในสถานการณ์ใดที่ฉันควรประกาศคลาสเป็นคลาสนามธรรม? ประโยชน์ของสิ่งนั้นคืออะไร?


14
สิ่งนี้ถูกถามคุณรู้ การค้นหาจะเปิดคำถามอื่น ๆ เช่นนี้ คุณควรเริ่มต้นใน Google ซึ่งจะนำคุณไปสู่ ​​Stack Overflow ทั้งหมดเหล่านี้เป็นรายการที่ซ้ำกัน: stackoverflow.com/search?q=abstract+interface
S.Lott

1
"การใช้คลาส Abstract พวกเขาหารือเกี่ยวกับกฎ ... " อะไร? "กฎ" และ "การใช้งาน" ต่างกันอย่างไร โดยวิธีการที่ถูกถามคำถามของคุณ ฉันรู้ว่า. ฉันตอบไปแล้ว มองไปเรื่อย ๆ สิ่งสำคัญคือการเรียนรู้วิธีใช้การค้นหา
S.Lott

ฉันหมายถึง "ในสถานการณ์ใดบ้างที่ฉันควรประกาศคลาสเป็นคลาสนามธรรม?
Vaibhav Jani

อินเทอร์เฟซเป็นคลาสนามธรรมที่บริสุทธิ์นั่นคือทั้งหมด พวกเขาเป็นหนึ่งเดียวกัน ฉันใช้คลาสนามธรรมตลอดเวลาสำหรับฟังก์ชั่นในคลาสฐานที่ไม่สามารถนำมาใช้เพราะต้องการข้อมูลที่มีเพียงคลาสย่อย แต่ฉันต้องการให้แน่ใจว่าทุกคลาสย่อยมีฟังก์ชั่นนี้และดำเนินการตามนั้น
AngryBird

ฉันพบว่ารูปแบบวิธีการแม่แบบหนึ่งที่มีประสิทธิภาพมากและเป็นกรณีการใช้งานที่ดีสำหรับการเรียนนามธรรม
m3th0dman

คำตอบ:


49

นี้คำตอบไม่ได้งานที่ดีของการอธิบายความแตกต่างระหว่างระดับนามธรรมและอินเตอร์เฟซ แต่ก็ไม่ได้คำตอบว่าทำไมคุณควรประกาศอย่างใดอย่างหนึ่ง

จากมุมมองทางเทคนิคล้วนๆไม่จำเป็นต้องประกาศคลาสเป็นนามธรรม

พิจารณาสามคลาสต่อไปนี้:

class Database { 
    public String[] getTableNames() { return null; } //or throw an exception? who knows...
}

class SqlDatabase extends Database { } //TODO: override getTableNames

class OracleDatabase extends Database { }  //TODO: override getTableNames

คุณไม่จำเป็นต้องทำให้คลาสฐานข้อมูลเป็นนามธรรมแม้ว่าจะมีปัญหาที่ชัดเจนเกี่ยวกับการใช้งาน: เมื่อคุณเขียนโปรแกรมนี้คุณสามารถพิมพ์new Database()และมันจะใช้ได้ แต่มันจะไม่ทำงาน

ไม่ว่าคุณจะยังคงได้รับ polymorphism ตราบใดที่โปรแกรมของคุณสร้างSqlDatabaseและOracleDatabaseอินสแตนซ์เท่านั้นคุณสามารถเขียนวิธีการเช่น:

public void printTableNames(Database database) {
    String[] names = database.getTableNames();
}

คลาสนามธรรมปรับปรุงสถานการณ์โดยการป้องกันการเป็นนักพัฒนาจากอินสแตนซ์ชั้นฐานเพราะนักพัฒนาได้ทำเครื่องหมายว่ามีฟังก์ชั่นที่ขาดหายไป นอกจากนี้ยังมีความปลอดภัยในการคอมไพล์เวลาเพื่อให้คุณสามารถมั่นใจได้ว่าคลาสใด ๆ ที่ขยายคลาสนามธรรมของคุณจะมีฟังก์ชั่นขั้นต่ำที่เปลือยเปล่าในการทำงานและคุณไม่จำเป็นต้องกังวลเกี่ยวกับการใส่วิธีการสตับ เพื่อที่จะรู้ได้อย่างน่าอัศจรรย์ว่าพวกเขาต้องแทนที่วิธีการเพื่อให้มันใช้งานได้

การเชื่อมต่อเป็นหัวข้อแยกกันโดยสิ้นเชิง อินเทอร์เฟซช่วยให้คุณอธิบายการดำเนินการที่สามารถทำได้บนวัตถุ โดยทั่วไปแล้วคุณจะใช้อินเทอร์เฟซเมื่อเขียนวิธีการส่วนประกอบ ฯลฯ ที่ใช้บริการของส่วนประกอบอื่น ๆ วัตถุ แต่คุณไม่สนใจว่าวัตถุประเภทจริงที่คุณได้รับบริการคืออะไร

พิจารณาวิธีการต่อไปนี้:

public void saveToDatabase(IProductDatabase database) {
     database.addProduct(this.getName(), this.getPrice());
}

คุณไม่สนใจว่าdatabaseวัตถุนั้นจะสืบทอดมาจากวัตถุใด ๆ หรือไม่คุณเพียงแคร์ว่ามันมีaddProductวิธีการ ดังนั้นในกรณีนี้อินเทอร์เฟซที่เหมาะสมกว่าการทำให้คลาสทั้งหมดของคุณเกิดขึ้นจากการสืบทอดจากคลาสพื้นฐานเดียวกัน

บางครั้งการรวมกันของทั้งสองทำงานได้เป็นอย่างดี ตัวอย่างเช่น:

abstract class RemoteDatabase implements IProductDatabase { 
    public abstract String[] connect();
    public abstract void writeRow(string col1, string col2);

    public void addProduct(String name, Double price) {
        connect();
        writeRow(name, price.toString());
    }
}

class SqlDatabase extends RemoteDatabase {
    //TODO override connect and writeRow
}

class OracleDatabase extends RemoteDatabase { 
    //TODO override connect and writeRow
}

class FileDatabase implements IProductDatabase {
    public void addProduct(String name, Double price) {
         //TODO: just write to file
    }
}

แจ้งให้ทราบว่าบางส่วนของฐานข้อมูลสืบทอดจาก RemoteDatabase เพื่อแบ่งปันการทำงานบางอย่าง (เช่นการเชื่อมต่อก่อนที่จะเขียนแถว) แต่ FileDatabase IProductDatabaseเป็นชั้นที่แยกต่างหากที่ดำเนินการเท่านั้น


16

ความคล้ายคลึงกัน

บทคัดย่อและอินเทอร์เฟซนามธรรมเป็นสิ่งจำเป็นสำหรับสิ่งที่เป็นนามธรรม ไม่สามารถสร้างอินสแตนซ์ใหม่ได้ แต่สามารถแก้ไขได้ด้วยการผกผันของคอนเทนเนอร์ควบคุมหรือผ่านรูปแบบของโรงงาน

ข้อแตกต่าง

  1. อินเตอร์เฟซ

    • กำหนดสัญญาสาธารณะที่รู้จักกันดีความสามารถของประเภท
    • ใช้สำหรับแสดงการสืบทอดแนวนอนเช่นการแยกในระดับแรกของการสืบทอด (เช่น ILog เพื่อกำหนดเครื่องมืออำนวยความสะดวกในการบันทึกไปยังฐานข้อมูล, ไฟล์ข้อความ, XML, SOAP เป็นต้น)
    • สมาชิกทั้งหมดเป็นแบบสาธารณะ
    • ไม่อนุญาตการใช้งาน
    • ลูกที่สืบทอดสามารถมีอินเตอร์เฟสจำนวนมากที่ต้องนำไปใช้
    • มีประโยชน์สำหรับการรวมบุคคลที่สาม
    • การตั้งชื่อมักเริ่มต้นด้วยฉัน
  2. ชั้นนามธรรม

    • กำหนดโครงสร้างข้อมูลประจำตัวและพฤติกรรมเริ่มต้นที่สนับสนุน
    • สามารถใช้เพื่อแสดงการสืบทอดแนวตั้งเช่นการแยกลึกในหลายระดับ (เช่นคลาส AbstractEntity ในการพัฒนาโดเมน)
    • สมาชิกสามารถมีทัศนวิสัยที่แตกต่างกัน (จากสาธารณะเป็นส่วนตัว)
    • คุณสามารถใช้สมาชิกบางคนได้ (เช่นคลาสผู้อ่าน *)
    • เด็กที่สืบทอดสามารถมีคลาสนามธรรมพื้นฐานเดียวเท่านั้น

มันเป็นจริงง่ายที่จะหาคำตอบจากแบบสอบถามของ Google ง่ายๆ


สิ่งที่คุณทำโดยการใช้งานไม่อนุญาต? คลาส java สามารถใช้อินเตอร์เฟส
Sajuuk

@Sajuuk บรรทัดนั้นหมายถึงส่วนต่อประสาน คุณไม่สามารถนำสัญญาไปปฏิบัติใช้กับส่วนต่อประสานได้ คุณอาจเริ่มต้นใช้งานสัญญาในระดับนามธรรม
oleksii

11

มันแตกต่างจากส่วนต่อประสานอย่างไร

ในคลาสนามธรรมคุณสามารถใช้วิธีการบางอย่างและปล่อย (บังคับ) ส่วนที่เหลือที่จะนำไปใช้โดยคลาสที่ขยาย คุณไม่สามารถใช้วิธีการในส่วนต่อประสาน คุณไม่สามารถบังคับให้ใครมาแทนที่อะไรก็ได้เมื่อขยายชั้นเรียนปกติ ด้วยคลาสนามธรรมคุณสามารถ


สิ่งนี้ต้องการการอัปเดตสำหรับ Java 8 โดยที่มีการแนะนำวิธีการเริ่มต้น
Haakon Løtveit

8

คลาสนามธรรมสำหรับ "คือ" ความสัมพันธ์และอินเทอร์เฟซสำหรับ "สามารถทำได้"

คลาสนามธรรมให้คุณเพิ่มลักษณะการทำงานพื้นฐานเพื่อให้โปรแกรมเมอร์ไม่จำเป็นต้องเขียนโค้ดทุกอย่างในขณะที่ยังคงบังคับให้พวกเขาทำตามการออกแบบของคุณ


3

นอกเหนือจากรายละเอียดทางเทคนิคอย่างลึกเช่นการใช้วิธีการบางอย่างสำหรับคลาสนามธรรมเป็นต้นความหมายก็เป็นเช่นนี้

อินเทอร์เฟซกำหนดความสามารถทั่วไป - IEnumerable กำหนดคลาสที่ใช้อินเทอร์เฟซนี้สามารถระบุได้ มันไม่ได้พูดอะไรเกี่ยวกับชั้นเรียนเอง

บทคัดย่อ (หรือฐาน) คลาสกำหนดพฤติกรรม - WebRequest กำหนดพฤติกรรมทั่วไปของชั้นเรียนเด็กทั้งหมดเช่น HttpWebRequest ฯลฯ มันกำหนดความหมายหลักของชั้นเรียนและเป็นวัตถุประสงค์ที่แท้จริง - เพื่อเข้าถึงแหล่งข้อมูลบนเว็บ


2

รายการวิกิพีเดีย

ความแตกต่างที่สำคัญระหว่างอินเทอร์เฟซและคลาสนามธรรมคือคลาสนามธรรมอาจมีวิธีการใช้งาน ด้วยอินเทอร์เฟซคุณสามารถประกาศเมธอดได้เท่านั้นให้เขียนลายเซ็น นี่คือตัวอย่างของคลาสที่ขยายคลาส abstract ที่ใช้สองอินเตอร์เฟส: (java)

interface MyInterface1 {
  string getValue1();
}

interface MyInterface2 {
  string getValue2();
}

abstract class MyAbstractClass implements MyInterface1, MyInterface2{
  void printValues() {
    System.out.println("Value 1: " + getValue1() + ", Value 2: " + getValue2() + 
                       ", Value 3: " + getValue3());
  }

  protected abstract string getValue3();
}

class ImpClass extends MyAbstractClass {
  public string getValue1() {
    return "1";
  }

  public string getValue2() {
    return "2";
  }

  protected string getValue3() {
    return "3";
  }
}

ในตัวอย่างนี้MyAbstractClassมีวิธีการสาธารณะที่พิมพ์ทั้งสามค่า ในImpClassคุณต้องใช้ getValue1 และ getValue2 ตามลำดับจากMyInterface1และMyInterface2และ getValue3 จากคลาสนามธรรม

voila

มีส่วนเพิ่มเติม (ส่วนต่อประสาน: วิธีสาธารณะเท่านั้น, ระดับนามธรรม: วิธีนามธรรมที่มีการป้องกันและนามธรรมสาธารณะ) แต่คุณสามารถอ่านได้ด้วยตัวคุณเอง

ในหมายเหตุสุดท้ายคลาสนามธรรมที่มีเพียงวิธีนามธรรมคือคลาสฐานนามธรรม "บริสุทธิ์" หรือที่รู้จักกันว่าอินเตอร์เฟส


2
  • อินเทอร์เฟซ - เมื่อสองสามคลาสใช้ API ร่วมกัน (ชื่อเมธอดและพารามิเตอร์)
  • คลาสนามธรรม - เมื่อสองสามคลาสแบ่งใช้โค้ดเดียวกัน (การนำไปใช้)

คุณควรเริ่มต้นด้วยคำถาม: "คลาสเหล่านี้จำเป็นต้องมีการใช้ร่วมกันหรือไม่หรือมีเพียงอินเทอร์เฟซทั่วไป"

หากคำตอบผสมกันเช่น - คลาสทั้งสามนี้ต้องแชร์การใช้งาน แต่อีกสองคลาสนี้แบ่งปัน API ของพวกเขาเท่านั้น - จากนั้นคุณสามารถสร้างอินเทอร์เฟซสำหรับทั้งห้าของพวกเขาและคลาสนามธรรมสำหรับทั้งสามที่มีร่วมกัน รหัส.

นอกจากนี้ยังมีวิธีอื่นในการแบ่งปันการนำไปใช้เช่นการห่อหุ้มวัตถุด้วยการนำไปใช้นั้น (เช่นในรูปแบบกลยุทธ์ )


1

คุณจะประกาศนามธรรมของคลาสเมื่อคุณไม่ต้องการให้นักพัฒนา (อาจเป็นตัวคุณเอง) ได้รับอนุญาตให้สร้างอินสแตนซ์เนื่องจากมันใช้งานไม่ได้หรือไม่สมเหตุสมผล

ตัวอย่างเช่นพิจารณาเกมที่มีเอนทิตีเกมประเภทต่าง ๆ พวกเขาทั้งหมดได้รับมรดกจากGameEntityชั้นฐาน

abstract class GameEntity{

    int lifePoint, speed, damage;

    public attack(GameEntity target){ target.damage(damage); }

    public damage(int damageInflicted){ lifePoint -= damageInflicted - speed; }

    // etc...

}

คลาสนี้ถูกประกาศabstractเนื่องจากไม่เหมาะสมที่จะสร้างอินสแตนซ์ มันประกาศการกระทำบางอย่างสำหรับเอนทิตีเกมและคุณลักษณะบางอย่าง แต่ไม่มีที่ใดในคลาสนี้ที่มีการเริ่มต้นแอตทริบิวต์เหล่านี้ คลาสนี้ทำหน้าที่เป็นเทมเพลตสำหรับเอนทิตีเกม แต่ไม่ได้มีไว้เพื่อสร้างอินสแตนซ์ของมันเองและเป็นไปตามที่ประกาศabstractไว้

เกี่ยวกับความแตกต่างในการใช้งานระหว่างคลาสนามธรรมและอินเตอร์เฟส:

ดังที่ฉันเห็นอินเทอร์เฟซเป็นวิธีการรับพฤติกรรม polymorphic โดยไม่ถูก จำกัด โดยกลไกการสืบทอดเดียวของบางภาษา

ลองกลับไปที่เกมเป็นตัวอย่าง พิจารณาในชั้นเรียนซึ่งได้มาจากEnemy ชั้นนี้มีวิธีการGameEntity attackMeFromDistance(RangedAttacker attacker)วิธีนี้มีไว้เพื่อให้เอนทิตีโจมตีศัตรูจากระยะไกล

อย่างที่คุณเห็นวิธีนี้ใช้RangedAttackerชนิดเป็นพารามิเตอร์ GameEntityแต่ทั้งหมดของหน่วยงานเกมรับมรดกแล้วจาก พวกเขาไม่สามารถขยายชั้นเรียนอีกชั้นได้

ใช้เวลาเรียนMageและArcherยกตัวอย่างเช่น เราต้องการที่จะช่วยให้ทั้งสองของพวกเขาที่จะได้รับการยอมรับเป็นพารามิเตอร์ในattackMeFromDistance(RangedAttacker attacker)วิธี GameEntityแต่พวกเขากำลังได้รับมาแล้วจาก

เพื่อแก้ปัญหานี้เราได้สร้างอินเทอร์เฟซใหม่:

interface RangedAttacker{
    public void attackFromDistance();
}

คลาสที่ใช้อินเตอร์เฟสนี้ต้องใช้attackFromDistance()เมธอดดังนั้นจึงมั่นใจได้ว่ามีความสามารถในการโจมตีระยะไกล ซึ่งหมายความว่าattackMeFromDistanceวิธีนี้สามารถยอมรับคลาสที่ใช้อินเทอร์เฟซนี้อย่างปลอดภัย ดังนั้นการสร้างMageและArcherใช้อินเทอร์เฟซนั้นช่วยแก้ปัญหาของเรา

สำหรับฉันนี่คือพลังของอินเตอร์เฟส

ดังนั้นโดยสรุปคุณมักจะใช้คลาสนามธรรมเมื่อคุณต้องการมีคลาสพื้นฐานสำหรับบางคลาส แต่มันไม่มีเหตุผลที่จะสร้างอินสแตนซ์ของมันเอง (หรือในกรณีที่มีabstractเมธอดซึ่งจะต้องมี ดำเนินการโดยคลาสย่อยและในกรณีนี้คอมไพเลอร์จะบังคับให้คุณสร้างคลาสabstract) คุณจะใช้อินเทอร์เฟซเพื่อรับพฤติกรรม polymorphic โดยไม่ถูก จำกัด โดยกลไกการสืบทอดเดี่ยว


0
  1. มีโอกาสน้อยเกินไปที่วิธีการทั่วไปสำหรับบางคลาส (ตรรกะทางธุรกิจ) และวิธีการที่เหลือจะแตกต่างกัน ในสถานการณ์แบบนั้นคุณสามารถใช้วิธีการทั่วไปทั้งหมดในชั้นเรียนหนึ่งและประกาศที่เหลือเป็นนามธรรม จากนั้นคุณควรประกาศคลาสให้เป็นนามธรรม
  2. บางครั้งคุณไม่อนุญาตให้สร้างวัตถุในชั้นเรียนโดยตรง คลาสแบบนั้นคุณต้องประกาศคลาสให้เป็นนามธรรม
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.