อะไรคือความแตกต่างระหว่าง 'ขยาย' และ 'การใช้งาน' ใน TypeScript


128

ฉันอยากรู้ว่าผู้ชายกับเด็กมีอะไรเหมือนกันและแตกต่างกันอย่างไร

class Person {
  name: string;
  age: number;
}
class child extends Person {}
class man implements Person {}

1
อย่างที่อธิบายไว้ที่นี่: stackoverflow.com/a/35990799/452213 โดยเฉพาะอย่างยิ่งตัวอย่างtypescriptlang.org/play/…
sudip

คำตอบ:


156

เวอร์ชั่นสั้น

  • extends หมายถึง:

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

  • implements หมายถึง:

คลาสใหม่สามารถจะถือว่าเป็นเดียวกัน"รูปร่าง"ในขณะที่มันไม่ได้เป็นเด็ก สามารถส่งผ่านไปยังเมธอดใดก็ได้ที่Personจำเป็นโดยไม่คำนึงว่ามีพาเรนต์ต่างจากPerson

มากกว่า ...

ในOOP (ภาษาเช่น C #, Java)เราจะใช้

extendsเพื่อหากำไรจากมรดก (ดูวิกิพีเดีย ) การอ้างอิงเล็ก ๆ :

... การสืบทอดในภาษาเชิงวัตถุที่อิงคลาสส่วนใหญ่เป็นกลไกที่อ็อบเจ็กต์หนึ่งได้รับคุณสมบัติและพฤติกรรมทั้งหมดของอ็อบเจ็กต์พาเรนต์ การสืบทอดช่วยให้โปรแกรมเมอร์สามารถ: สร้างคลาสที่สร้างขึ้นจากคลาสที่มีอยู่ ...

implementsจะมีมากขึ้นสำหรับความหลากหลาย (ดูวิกิพีเดีย ) การอ้างอิงเล็ก ๆ :

... ความหลากหลายคือการจัดเตรียมอินเทอร์เฟซเดียวสำหรับเอนทิตีประเภทต่างๆ ...

ดังนั้นเราสามารถมีแผนผังการสืบทอดที่แตกต่างกันclass Manไป

class Man extends Human ...

แต่ถ้าเราประกาศด้วยว่าเราสามารถแสร้งทำเป็นประเภทอื่นได้ - Person:

class Man extends Human 
          implements Person ...

.. แล้วเราสามารถใช้งานได้ทุกที่ที่Personจำเป็น เราก็ต้องตอบสนองคนของ(เช่นใช้สิ่งที่ประชาชนทุกคน)"interface"

implementชั้นอื่น ๆ ? นั่นคือสิ่งที่เจ๋งจริงๆ

ใบหน้าที่สวยงามของ Javascript (ประโยชน์อย่างหนึ่ง)คือการรองรับการพิมพ์ Duck ในตัว ( ดูวิกิพีเดีย ) การอ้างอิงเล็ก ๆ :

“ ถ้ามันเดินเหมือนเป็ดและมันไม่เหมือนเป็ดมันก็ต้องเป็นเป็ด”

ดังนั้นใน Javascript หากวัตถุสองชิ้นที่แตกต่างกัน ... จะมีวิธีการที่คล้ายกัน(เช่นrender())พวกเขาสามารถส่งผ่านไปยังฟังก์ชันที่คาดหวังไว้:

function(engine){
  engine.render() // any type implementing render() can be passed
}

เพื่อไม่ให้เสียสิ่งนั้น - เราสามารถทำได้ใน typescript ทำเช่นเดียวกัน - ด้วยการสนับสนุนที่พิมพ์มากขึ้น และนั่นคือที่

class implements class

มีบทบาทในที่ที่สมเหตุสมผล

ในภาษา OOP เป็นC#... ไม่มีทางทำเช่นนั้น ...

นอกจากนี้เอกสารควรช่วยได้ที่นี่:

อินเทอร์เฟซการขยายคลาส

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

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

class Control {
    private state: any;
}

interface SelectableControl extends Control {
    select(): void;
}

class Button extends Control implements SelectableControl {
    select() { }
}

class TextBox extends Control {
    select() { }
}

// Error: Property 'state' is missing in type 'Image'.
class Image implements SelectableControl {
    private state: any;
    select() { }
}

class Location {

}

ดังนั้นในขณะที่

  • extends หมายถึง - ได้รับทั้งหมดจากผู้ปกครอง
  • implementsในกรณีนี้ก็เหมือนกับการใช้อินเทอร์เฟซ วัตถุลูกสามารถแสร้งทำเป็นว่าเป็นผู้ปกครอง ... แต่ไม่ได้รับการนำไปใช้งานใด ๆ

เมื่อคุณพูดว่า " extends- ได้รับทั้งหมดจากผู้ปกครอง" จะใช้กับสมาชิกส่วนตัวหรือไม่ ยกตัวอย่างเช่นclass Person {private name: string} class man extends Person{gender: string;}จะmanมีชื่อทรัพย์สินหรือไม่
davejoem

ส่วนตัวก็มีเช่นกัน TS ไม่สามารถเข้าถึงได้ ทำให้พวกเขาได้รับการปกป้องและคุณสามารถใช้งานได้ ในกรณีที่ "ดำเนินการ" เพียงส่วนสาธารณะก็สมเหตุสมผลแล้ว หวังว่ามันจะช่วยได้บ้าง
Radim Köhler

คำตอบที่ยอดเยี่ยม เพียงแค่ไม่แน่ใจในความคิดเห็นของคุณสำหรับ "ส่วนตัวอยู่ที่นั่น แต่ TS ไม่สามารถเข้าถึงได้" คุณหมายถึงคุณสมบัติส่วนตัวถูกคัดลอกในวัตถุลูกที่สร้างขึ้นใหม่หรือไม่? และในกรณีของการดำเนินการจะมีการคัดลอกเฉพาะสมบัติสาธารณะ?
kushalvm

นอกจากนี้ฉันยังมีอีกหนึ่งประเด็น ถ้านี่คือคำจำกัดความของการขยาย ถ้าอย่างนั้นช่วยอธิบายstackoverflow.com/questions/60390454/…
kushalvm

99

ใน typescript (และภาษา OO อื่น ๆ ) คุณมีคลาสและอินเทอร์เฟซ

อินเทอร์เฟซไม่มีการใช้งาน แต่เป็นเพียง "สัญญา" ของสิ่งที่สมาชิก / วิธีการประเภทนี้มี
ตัวอย่างเช่น:

interface Point {
    x: number;
    y: number;
    distance(other: Point): number;
}

อินสแตนซ์ที่ใช้Pointอินเทอร์เฟซนี้ต้องมีสมาชิกของ type number สองตัว: xและyและอีกวิธีหนึ่งdistanceซึ่งได้รับPointอินสแตนซ์อื่นและส่งกลับ a number.
อินเทอร์เฟซไม่ได้ใช้งานใด ๆ

ชั้นเรียนคือการนำไปใช้:

class PointImplementation implements Point {
    public x: number;
    public y: number;

    constructor(x: number, y: number) {
        this.x = x;
        this.y = y;
    }

    public distance(other: Point): number {
        return Math.sqrt(Math.pow(this.x - other.x, 2) + Math.pow(this.y - other.y, 2));
    }
}

( รหัสในสนามเด็กเล่น )

ในตัวอย่างของคุณคุณถือว่าPersonคลาสของคุณหนึ่งครั้งเป็นคลาสเมื่อคุณขยายคลาสและอีกครั้งเป็นอินเทอร์เฟซเมื่อคุณนำไปใช้
รหัสของคุณ:

class Person {
    name: string;
    age: number;
}
class Child  extends Person {}

class Man implements Person {}

มีข้อผิดพลาดในการคอมไพล์แจ้งว่า:

คลาส 'ชาย' ใช้อินเทอร์เฟซ 'บุคคล' ไม่ถูกต้อง
ไม่มี "ชื่อ" คุณสมบัติในประเภท "ชาย"

และนั่นเป็นเพราะอินเทอร์เฟซขาดการใช้งาน
ดังนั้นหากคุณimplementเป็นชั้นเรียนคุณจะทำเพียง "สัญญา" โดยไม่มีการใช้งานดังนั้นคุณจะต้องทำสิ่งนี้

class NoErrorMan implements Person {
    name: string;
    age: number;
}

( รหัสในสนามเด็กเล่น )

บรรทัดล่างคือในกรณีส่วนใหญ่คุณต้องการไปextendยังชั้นเรียนอื่นและไม่ต้องการimplementเรียน


6

คำตอบที่ดีจาก @ nitzan-tomer! ช่วยฉันได้มาก ... ฉันขยายการสาธิตของเขาเล็กน้อยด้วย:

IPoint interface;
Point implements IPoint;
Point3D extends Point;

และพฤติกรรมของพวกเขาในฟังก์ชันที่คาดหวังIPointประเภท

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

นี่คือการสาธิตเพิ่มเติม


สิ่งนี้ไม่ได้ให้คำตอบสำหรับคำถาม หากต้องการวิจารณ์หรือขอคำชี้แจงจากผู้เขียนโปรดแสดงความคิดเห็นใต้โพสต์ของพวกเขา - จากรีวิว
aronisstav

1
@aronisstav ฉันโพสต์เพียงการสาธิตเพิ่มเติมเกี่ยวกับสิ่งที่ฉันพบคำตอบที่ดีที่ช่วยฉันได้แล้ว แต่อาจมีคนอื่นพบว่างานที่ฉันขยายการสาธิตมีประโยชน์ นั่นคือทั้งหมด ความคิดเห็นไม่ได้มีไว้สำหรับการใส่รหัสบล็อกดังนั้นฉันจึงพบว่ามันเข้าใจได้ดีกว่าในโพสต์คำตอบ แล้วปัญหาของมันคืออะไร?
andzep

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

@andzep ตัวอย่างการสาธิตเพิ่มเติมของคุณมีประโยชน์มาก
Namit

3
  1. อินเทอร์เฟซขยายส่วนต่อประสานกับรูปร่าง
  2. อินเทอร์เฟซขยายคลาสด้วยรูปร่าง
  3. อินเทอร์เฟซการใช้งานคลาสควรใช้ฟิลด์ทั้งหมดที่อินเทอร์เฟซมีให้
  4. คลาสใช้คลาสที่มีรูปร่าง
  5. คลาสขยายคลาสพร้อมฟิลด์ทั้งหมด

extendsมุ่งเน้นไปที่การสืบทอดและimplementsมุ่งเน้นไปที่ข้อ จำกัด ไม่ว่าจะเป็นอินเทอร์เฟซหรือคลาส


0

ขยายการใช้งาน VS

  • extends: คลาสย่อย (ซึ่งขยาย) จะสืบทอดคุณสมบัติและวิธีการทั้งหมดของคลาสที่ขยาย
  • implements: คลาสที่ใช้implementsคีย์เวิร์ดจะต้องใช้คุณสมบัติและเมธอดทั้งหมดของคลาสที่มันimplements

เพื่อให้เข้าใจง่ายขึ้น:

  • extends: ที่นี่คุณจะได้รับเมธอด / คุณสมบัติเหล่านี้ทั้งหมดจากคลาสพาเรนต์ดังนั้นคุณจึงไม่ต้องดำเนินการด้วยตัวเอง
  • implements: นี่คือสัญญาที่ชั้นต้องปฏิบัติตาม คลาสต้องใช้วิธีการ / คุณสมบัติต่อไปนี้เป็นอย่างน้อย

ตัวอย่าง:

class Person {
  name: string;
  age: number;

  walk(): void {
    console.log('Walking (person Class)')
  }

  constructor(name: string, age: number) {
    this.name = name;
    this.age = age;
  }
}
class child extends Person { }

// Man has to implements at least all the properties
// and methods of the Person class
class man implements Person {
  name: string;
  age: number

  constructor(name: string, age: number) {
    this.name = name;
    this.age = age;
  }

  walk(): void {
    console.log('Walking (man class)')
  }

}

(new child('Mike', 12)).walk();
// logs: Walking(person Class)

(new man('Tom', 12)).walk();
// logs: Walking(man class)

ในตัวอย่างเราสามารถสังเกตได้ว่าคลาสย่อยสืบทอดทุกอย่างจาก Person ในขณะที่คลาส man ต้องใช้ทุกอย่างจาก Person เอง

หากเราจะลบบางสิ่งออกจากคลาส man ตัวอย่างเช่นวิธีการเดินเราจะได้รับข้อผิดพลาดเวลาคอมไพล์ดังต่อไปนี้:

คลาส 'man' ใช้คลาส 'Person' อย่างไม่ถูกต้อง คุณต้องการขยาย 'บุคคล' และสืบทอดสมาชิกเป็นคลาสย่อยหรือไม่? คุณสมบัติ 'walk' หายไปในประเภท 'man' แต่จำเป็นต้องใช้ในประเภท 'Person' (2720)

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