มีอินเทอร์เฟซมากกว่าวิธีการที่ถูกต้องหรือไม่


159

ดังนั้นสมมติว่าฉันมีอินเทอร์เฟซนี้:

public interface IBox
{
   public void setSize(int size);
   public int getSize();
   public int getArea();
  //...and so on
}

และฉันมีคลาสที่ใช้งาน:

public class Rectangle implements IBox
{
   private int size;
   //Methods here
}

ถ้าฉันต้องการใช้อินเทอร์เฟซ IBox ฉันไม่สามารถสร้างอินสแตนซ์ของมันได้ตามจริง:

public static void main(String args[])
{
    Ibox myBox=new Ibox();
}

ขวา? ดังนั้นฉันต้องทำสิ่งนี้จริง:

public static void main(String args[])
{
    Rectangle myBox=new Rectangle();
}

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


2
โปรดจำไว้ว่าอินเทอร์เฟซไม่เฉพาะเจาะจงกับจาวา ภาษา OOP ทุกภาษามีรูปแบบหรืออื่น ๆ แต่ไม่ได้กำหนดไว้อย่างชัดเจนว่าเป็น Java
Herms

2
ในทางเทคนิคภาษา OOP ที่พิมพ์ออกมาอย่างรุนแรงทั้งหมดจะมีในบางรูปแบบหรืออย่างอื่น ไม่มีการพิมพ์หรือพิมพ์เป็ดภาษาไม่มีแนวคิดที่คล้ายกัน
Jared

1
@Jared คุณไม่สับสนกับการพิมพ์ที่แข็งแกร่งด้วยการพิมพ์แบบสแตติกและ "untyped" ด้วยการพิมพ์แบบไดนามิกหรือไม่
eljenso

ความหลากหลายสามารถทำได้ผ่านอินเทอร์เฟซเช่นกัน ตรวจสอบส่วนสุดท้ายของหน้านี้codenuggets.com/2014/06/20/java-interface
Jeff

คำตอบ:


143

การเชื่อมต่อเป็นวิธีที่จะทำให้โค้ดของคุณมีความยืดหยุ่นมากขึ้น สิ่งที่คุณทำคือ:

Ibox myBox=new Rectangle();

จากนั้นในภายหลังหากคุณตัดสินใจว่าคุณต้องการใช้กล่องรูปแบบอื่น (อาจมีห้องสมุดอื่นที่มีกล่องที่ดีกว่า) คุณเปลี่ยนรหัสของคุณเป็น:

Ibox myBox=new OtherKindOfBox();

เมื่อคุณชินกับมันแล้วคุณจะพบว่ามันเป็นวิธีการทำงานที่ยอดเยี่ยม (จำเป็นจริงๆ)

อีกเหตุผลหนึ่งคือตัวอย่างเช่นถ้าคุณต้องการสร้างรายการของกล่องและดำเนินการบางอย่างในแต่ละรายการ แต่คุณต้องการให้รายการมีกล่องประเภทต่างๆ ในแต่ละกล่องคุณสามารถทำได้:

myBox.close()

(สมมติว่า IBox มีวิธีปิด ()) แม้ว่าคลาส myBox ที่แท้จริงจะเปลี่ยนไปขึ้นอยู่กับว่าคุณอยู่ที่กล่องใดในการทำซ้ำ


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

16
สิ่งนี้ได้รับเลือกเป็นคำตอบอย่างไร มันเป็นคำอธิบายสั้น ๆ ว่าทำไม polymorphism จึงมีประโยชน์ แต่อย่างที่ผู้โพสต์กล่าวไว้ฉันคาดหวังว่าจะมีคำอธิบายที่ดีกว่าเกี่ยวกับอินเตอร์เฟสหลาย ๆ ตัวและที่สำคัญยิ่งกว่าเมื่อมันเหมาะสมที่จะใช้อินเตอร์เฟสกับคลาสนามธรรม
trevorkavanaugh

2
สิ่งนี้มีน้อยมากเกี่ยวกับการอธิบายอินเตอร์เฟสและทุกอย่างเกี่ยวกับพื้นฐานของ polymorphism
A-Developer-Has-No-Name

123

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

จุดที่แท้จริงนั้นมีอยู่ในชื่อแล้ว: พวกเขากำหนดอินเทอร์เฟซที่ทุกคนสามารถนำไปใช้เพื่อใช้รหัสทั้งหมดที่ทำงานบนอินเตอร์เฟส ตัวอย่างที่ดีที่สุดคือการjava.util.Collectionsที่ให้บริการทุกชนิดของวิธีการที่มีประโยชน์ที่ใช้งานเฉพาะในการเชื่อมต่อเช่นsort()หรือสำหรับreverse() Listจุดนี่คือรหัสนี้สามารถใช้ในการเรียงลำดับหรือย้อนกลับคลาสใด ๆที่ใช้Listอินเทอร์เฟซไม่เพียง แต่ArrayListและLinkedListยังเรียนที่คุณเขียนเองซึ่งอาจนำมาใช้ในวิธีที่คนที่java.util.Collectionsไม่เคยจินตนาการ

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

การใช้อินเทอร์เฟซทั่วไปสำหรับการโทรกลับ ตัวอย่างเช่นjava.swing.table.TableCellRendererซึ่งช่วยให้คุณมีอิทธิพลต่อวิธีที่ตาราง Swing แสดงข้อมูลในคอลัมน์บางคอลัมน์ คุณใช้อินเทอร์เฟซนั้นส่งผ่านอินสแตนซ์ไปที่JTableและในบางช่วงระหว่างการเรนเดอร์ตารางรหัสของคุณจะถูกเรียกให้ทำสิ่งต่าง ๆ


9
นั่นเป็นคำตอบที่ดีฉันชอบเมื่อคุณยกตัวอย่างจากคลาสจาวาแพ็คเกจ ...
Owais Qureshi

1
ฉันชอบyou can write code that operates on well-known interfaces, or interfaces you define
Manish Kumar

รอสักครู่ ... สิ่งที่ทำให้อินเทอร์เฟซมีประโยชน์ไม่ใช่ [ความสามารถในการใช้งานการดำเนินการใด ๆ ที่คุณชอบ] แต่แทนที่จะเป็น [ความสามารถในการใช้งานการใช้งานที่คุณชอบ]? การกล่าวถึงกรณีตรงกันข้ามนั้นค่อนข้างดีทีเดียว
Powerslave

4
@ Powerlave: ถอดความมันเหมือนสิ่งที่ทำให้อินเตอร์เฟสมีประโยชน์ไม่ได้ [ความสามารถในการเขียนโค้ดที่คุณต้องเปลี่ยนเพียงหนึ่งบรรทัดเมื่อเปลี่ยน impementation] แต่แทนที่จะเป็น [ความสามารถในการเขียนโค้ดที่คุณไม่ได้ระบุการใช้งานที่ ทั้งหมด].
Michael Borgwardt

@MichaelBorgwardt ฟังดูดีกว่า :) ขอบคุณสำหรับการชี้แจง!
Powerslave

119

หนึ่งในหลาย ๆ การใช้งานที่ฉันได้อ่านคือจุดที่ยากหากไม่มีหลายการสืบทอดโดยใช้อินเตอร์เฟสใน Java:

class Animal
{
void walk() { } 
....
.... //other methods and finally
void chew() { } //concentrate on this
} 

ตอนนี้ลองนึกภาพกรณีที่:

class Reptile extends Animal 
{ 
//reptile specific code here
} //not a problem here

แต่,

class Bird extends Animal
{
...... //other Bird specific code
} //now Birds cannot chew so this would a problem in the sense Bird classes can also call chew() method which is unwanted

การออกแบบที่ดีขึ้นจะเป็น:

class Animal
{
void walk() { } 
....
.... //other methods 
} 

Animal ไม่มีเมธอดเคี้ยว () และแทนที่อยู่ในอินเตอร์เฟสเป็น:

interface Chewable {
void chew();
}

และให้ชั้นสัตว์เลื้อยคลานใช้สิ่งนี้ไม่ใช่นก (เนื่องจากนกไม่สามารถเคี้ยวได้):

class Reptile extends Animal implements Chewable { } 

และกรณีของนกเพียง:

class Bird extends Animal { }

6
@CHEBURASHKA และการตั้งชื่อไม่ดี ถ้าReptile"เคี้ยว" มากกว่าตัวมันเองก็คือ "เคี้ยว" แบบแผนของการตั้งชื่ออินเทอร์เฟซ (บางครั้ง) สิ่งที่ควรนำมาใช้ก็ต่อเมื่อมันเหมาะสมอย่างสมบูรณ์ การตั้งชื่ออินเทอร์เฟซPredatorจะเหมาะสมกว่าที่นี่
Powerslave

6
@ Powerlave มันถูกต้อง imho สัตว์เลื้อยคลานคือ "สามารถเคี้ยว" / "เคี้ยว" เหยี่ยวเป็นนักล่า แต่ก็ยังไม่สามารถเคี้ยว ... เพียงแค่ nitpicking แต่สามารถกำหนด "chewable" ได้ดีขึ้นในเอกสารของอินเทอร์เฟซ
Madmenyo

สุดยอด .. อธิบายได้ดีมาก ขอบคุณ..!
Gurusinghe

1
ฉันไม่เข้าใจ สิ่งที่ดูเหมือนว่าคืออินเตอร์เฟสเป็นวิธีที่ดีในการตรวจสอบให้แน่ใจว่าคุณใช้วิธีการเดียวกันในแต่ละชั้นเรียนที่ใช้มัน (เช่นดังนั้นจึงป้องกันไม่ให้ฉันเลือกตัวเลือกโง่ ๆ ของคลาส Bird ด้วย run () และ Dog class ด้วย runn () - พวกเขาเหมือนกันทั้งหมด) แต่โดยให้ความสนใจและทำให้ชั้นเรียนของฉันมีรูปแบบ / โครงสร้างที่เหมือนกันฉันจะไม่สามารถทำสิ่งเดียวกันนี้ได้หรือไม่? ดูเหมือนว่าอินเทอร์เฟซเพียงตรวจสอบให้แน่ใจว่าโปรแกรมเมอร์ไม่ได้ถูกลืม นอกจากนี้อินเตอร์เฟสดูเหมือนจะไม่ช่วยฉันประหยัดเวลา ฉันยังคงต้องกำหนดวิธีการในแต่ละชั้นเรียนที่ใช้มัน
Alex G

@AlexG - ฉันบอกหนึ่งในหลาย ๆ คนใช้ :) มีมากขึ้นเรามีรอยขีดข่วนพื้นผิวแทบจะไม่ตอบคำถามในแบบที่เรียบง่าย!
peevesy

47

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

public void scale(IBox b, int i) {
   b.setSize(b.getSize() * i);
}

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


8
เหตุใดวัตถุประสงค์ของการเชื่อมต่อหลายรูปแบบคือถ้าฉันสามารถบรรลุสิ่งนั้นใน Java ด้วยการทำคลาสย่อยและการแทนที่เมธอด?
eljenso

1
เป็นสิ่งเดียวกันยกเว้นว่าส่วนต่อประสานนั้นจะต้องละเว้นการใช้งานใด ๆ คลาสจึงสามารถประยุกต์ใช้มากกว่าหนึ่งอินเตอร์เฟส
Apocalisp

4
เฮ้ฉันไม่เคยพูดว่า Java มีความสมบูรณ์ของแนวความคิดใด ๆ การทดแทนชนิดเป็นวัตถุประสงค์ของการพิมพ์ย่อยทั้งหมด Java เกิดขึ้นมีกลไกการพิมพ์ย่อยมากกว่าหนึ่งกลไกซึ่งไม่มีสิ่งใดที่ดีเป็นพิเศษ
Apocalisp

1
ฉันไม่เคยพูดอะไรเกี่ยวกับความสมบูรณ์ของแนวคิดเช่นกัน แต่เราไปต่อกัน หากคุณสามารถปรับขนาด IBox ทุกครั้งด้วยวิธีการของคุณมันจะเป็นการประกาศใน IBox: IBox.scale (int) หรือไม่?
eljenso

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

33

อินเตอร์เฟสอนุญาตให้ใช้ภาษาที่พิมพ์แบบสแตติกเพื่อสนับสนุน polymorphism ผู้พิถีพิถันในการใช้ Object Oriented จะยืนยันว่าภาษาควรจัดให้มีการสืบทอดการห่อหุ้มแบบแยกส่วนและแบบ polymorphism เพื่อให้เป็นภาษาเชิงวัตถุที่มีคุณลักษณะครบถ้วน ในแบบไดนามิก - พิมพ์ - หรือเป็ดพิมพ์ - ภาษา (เช่น Smalltalk,) ความแตกต่างเป็นเรื่องไม่สำคัญ; อย่างไรก็ตามในภาษาที่พิมพ์แบบคงที่ (เช่น Java หรือ C #,) polymorphism อยู่ไกลจากเรื่องเล็กน้อย (อันที่จริงบนพื้นผิวดูเหมือนว่าจะขัดแย้งกับความคิดของการพิมพ์ที่แข็งแกร่ง)

ให้ฉันสาธิต:

ในภาษาแบบไดนามิก (หรือพิมพ์เป็ด) แบบไดนามิก (เช่น Smalltalk) ตัวแปรทั้งหมดอ้างอิงถึงวัตถุ (ไม่มีอะไรน้อยและไม่มีอะไรเพิ่มเติม) ดังนั้นใน Smalltalk ฉันสามารถทำสิ่งนี้:

|anAnimal|    
anAnimal := Pig new.
anAnimal makeNoise.

anAnimal := Cow new.
anAnimal makeNoise.

รหัสนั้น:

  1. ประกาศตัวแปรท้องถิ่นที่เรียกว่า anAnimal (โปรดทราบว่าเราไม่ได้ระบุประเภทของตัวแปร - ตัวแปรทั้งหมดอ้างอิงถึงวัตถุไม่มากและไม่น้อยไปกว่านี้)
  2. สร้างอินสแตนซ์ใหม่ของคลาสที่ชื่อว่า "Pig"
  3. กำหนดอินสแตนซ์ใหม่ของ Pig ให้กับตัวแปร anAnimal
  4. ส่งข้อความ makeNoiseไปยังหมู
  5. ทำซ้ำสิ่งทั้งปวงโดยใช้วัว แต่กำหนดให้ตัวแปรเดียวกับหมู

โค้ด Java เดียวกันจะมีลักษณะดังนี้ (ทำให้สมมติฐานว่า Duck และ Cow เป็นคลาสย่อยของ Animal:

Animal anAnimal = new Pig();
duck.makeNoise();

anAnimal = new Cow();
cow.makeNoise();

นั่นคือทั้งหมดที่ดีและดีจนกว่าเราจะแนะนำคลาสผัก ผักมีพฤติกรรมเช่นเดียวกับสัตว์ แต่ไม่ใช่ทั้งหมด ตัวอย่างเช่นทั้งสัตว์และผักอาจโตได้ แต่ผักอย่างชัดเจนไม่ส่งเสียงและไม่สามารถเก็บเกี่ยวสัตว์ได้

ใน Smalltalk เราสามารถเขียนสิ่งนี้:

|aFarmObject|
aFarmObject := Cow new.
aFarmObject grow.
aFarmObject makeNoise.

aFarmObject := Corn new.
aFarmObject grow.
aFarmObject harvest.

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

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

พร้อมอินเทอร์เฟซมา ...

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

List<Growable> list = new ArrayList<Growable>();
list.add(new Cow());
list.add(new Corn());
list.add(new Pig());

for(Growable g : list) {
   g.grow();
}

และมันช่วยให้เราทำสิ่งนี้เพื่อสร้างเสียงสัตว์:

List<Animal> list = new ArrayList<Animal>();
list.add(new Cow());
list.add(new Pig());
for(Animal a : list) {
  a.makeNoise();
}

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

|aFarmObject|
aFarmObject := Corn new.
aFarmObject makeNoise. // No compiler error - not checked until runtime.

ภาษาที่พิมพ์แบบคงที่ให้ "การเขียนโปรแกรมตามสัญญา" ได้ดีกว่ามากเพราะพวกเขาจะจับข้อผิดพลาดสองประเภทด้านล่างนี้ในเวลารวบรวม:

// Compiler error: Corn cannot be cast to Animal.
Animal farmObject = new Corn();  
farmObject makeNoise();

-

// Compiler error: Animal doesn't have the harvest message.
Animal farmObject = new Cow();
farmObject.harvest(); 

ดังนั้น .... เพื่อสรุป:

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

  2. การเชื่อมต่อทำให้เราได้รับประโยชน์มากมายจาก "ความจริง" ที่หลากหลายโดยไม่เสียสละการตรวจสอบประเภทคอมไพเลอร์


2
นี่คือข้อความของคำตอบของฉันสำหรับคำถามอื่น: stackoverflow.com/questions/379282/… . แต่พวกเขาเกี่ยวข้องกับคำตอบ
Jared

2
ดังนั้นฉันขอถามได้ไหมว่าเป็ดที่พิมพ์ภาษาแยกความแตกต่างระหว่าง Animal.water () อย่างไร (ซึ่งชาวนาที่ฉลาดแกมโกงเคยพูดว่าใช้น้ำรั่ว) และ Plant.water () ซึ่งเขาใช้ในการรดน้ำต้นไม้ ความกำกวมเป็นศัตรู จำนวนของความฟุ่มเฟื่อยใด ๆ ที่จำเป็นในการเอาชนะความคลุมเครือเป็น IMO ที่ยอมรับได้
Bill K

1
ใช่ .. ความคลุมเครือเป็นชื่อของเกมที่มีภาษาที่พิมพ์เป็ด เมื่อทำงานอย่างมืออาชีพในภาษาที่พิมพ์ด้วยเป็ดไม่ใช่เรื่องแปลกที่จะเห็นสมาชิก (วิธีการและตัวแปร) ด้วยชื่อที่มีความยาว 50-100 ตัวอักษร
Jared

1
ข้อเสียที่สำคัญอีกอย่างของภาษาที่พิมพ์เป็ดคือการไม่สามารถทำการปรับโครงสร้างแบบโปรแกรมโดยใช้การวิเคราะห์แบบคงที่ - ลองถามภาพ Smalltalk สำหรับรายชื่อผู้โทรทั้งหมดของวิธีการพิมพ์ของคุณ ... คุณจะได้รับรายชื่อผู้โทรทั้งหมด ...
Jared

... เนื่องจากผู้โทรของ Automobile # printString ไม่สามารถแยกความแตกต่างทางโปรแกรมจากผู้เรียก NearEarthOrbit # printString
Jared

9

โดยปกติอินเตอร์เฟสจะกำหนดอินเทอร์เฟซที่คุณควรใช้ (ตามชื่อแจ้งว่า ;-)) ตัวอย่าง


public void foo(List l) {
   ... do something
}

ตอนนี้ฟังก์ชั่นของคุณfooยอมรับArrayLists, LinkedLists, ... ไม่เพียง แต่ประเภทเดียว

สิ่งที่สำคัญที่สุดใน Java คือคุณสามารถใช้หลายอินเตอร์เฟส แต่คุณสามารถขยายได้เพียงหนึ่งคลาสเท่านั้น! ตัวอย่าง:


class Test extends Foo implements Comparable, Serializable, Formattable {
...
}
เป็นไปได้ แต่

class Test extends Foo, Bar, Buz {
...
}
ไม่ใช่!

IBox myBox = new Rectangle();รหัสของคุณดังกล่าวยังอาจจะ: สิ่งที่สำคัญคือตอนนี้ที่ myBox มีเพียงวิธีการ / เขตข้อมูลจาก IBox และไม่ (อาจมีอยู่) วิธีการอื่น ๆ Rectangleจาก


1
'รายการ' ควรเป็นสมาชิกส่วนต่อประสานหรือไม่
คลิกโหวต

1
รายการเป็นส่วนต่อประสานในไลบรารีคอลเลกชัน java
rmeador

List เป็นส่วนต่อประสานในไลบรารี Java มาตรฐาน ( java.sun.com/javase/6/docs/api/java/util/List.html ) เขาแค่ใช้มันเพื่ออธิบายประเด็นของเขา
Michael Myers

6

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

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

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

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

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


6

คุณสามารถทำได้

Ibox myBox = new Rectangle();

ด้วยวิธีนี้คุณใช้วัตถุนี้เป็น Ibox และคุณไม่สนใจว่ามันจะเป็นRectangleอย่างไร


นั่นหมายความว่าเราสามารถเขียนแบบนี้ได้! > Rectangle inst = new Rectangle ();
Dr.jacky

@ Mr.Hyde หากคุณต้องการเพิ่มในภายหลังSquareคุณมีปัญหา .... หากคุณลองทำโดยไม่มีส่วนต่อประสานคุณไม่สามารถรับประกันได้SquareและRectangleมีวิธีการเดียวกัน ... ซึ่งอาจส่งผลให้ฝันร้ายเมื่อคุณมี ฐานรหัสที่ใหญ่กว่า ... จำไว้ว่าอินเตอร์เฟสจะกำหนดแม่แบบ
Kolob Canyon

6

ทำไมต้องมีอินเตอร์เฟซ ??????

มันเริ่มต้นด้วยสุนัข โดยเฉพาะอย่างยิ่งปั๊

ปั๊กมีพฤติกรรมหลากหลาย:

public class Pug { 
private String name;
public Pug(String n) { name = n; } 
public String getName() { return name; }  
public String bark() { return  "Arf!"; } 
public boolean hasCurlyTail() { return true; } }

และคุณมีลาบราดอร์ซึ่งมีชุดของพฤติกรรม

public class Lab { 
private String name; 
public Lab(String n) { name = n; } 
public String getName() { return name; } 
public String bark() { return "Woof!"; } 
public boolean hasCurlyTail() { return false; } }

เราสามารถทำดัชชุนด์และแล็บ:

Pug pug = new Pug("Spot"); 
Lab lab = new Lab("Fido");

และเราสามารถเรียกใช้พฤติกรรมของพวกเขา:

pug.bark() -> "Arf!" 
lab.bark() -> "Woof!" 
pug.hasCurlyTail() -> true 
lab.hasCurlyTail() -> false 
pug.getName() -> "Spot"

สมมติว่าฉันใช้สุนัขสุนัขและฉันต้องติดตามสุนัขทั้งหมดที่ฉันอยู่อาศัย ฉันต้องเก็บดัชชุนด์และลาบราดอร์ของฉันในอาร์เรย์แยกต่างหาก :

public class Kennel { 
Pug[] pugs = new Pug[10]; 
Lab[] labs = new Lab[10];  
public void addPug(Pug p) { ... } 
public void addLab(Lab l) { ... } 
public void printDogs() { // Display names of all the dogs } }

แต่นี่ไม่ชัดเจน ถ้าฉันต้องการบ้านพุดเดิ้ลด้วยฉันต้องเปลี่ยนคำจำกัดความของ Kennel เพื่อเพิ่มพุดเดิ้ลมากมาย ที่จริงแล้วฉันต้องการชุดสุนัขแยกต่างหากสำหรับสุนัขแต่ละประเภท

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

public interface Dog 
{
public String bark(); 
public String getName(); 
public boolean hasCurlyTail(); }

จากนั้นฉันเปลี่ยนคลาส Pug และ Lab เล็กน้อยเพื่อใช้พฤติกรรมสุนัข เราสามารถพูดได้ว่าปั๊กเป็นสุนัขและแล็บเป็นสุนัข

public class Pug implements Dog {
// the rest is the same as before } 

public class Lab implements Dog { 
// the rest is the same as before 
}

ฉันยังสามารถยกตัวอย่าง Pugs และ Labs เหมือนที่เคยทำ แต่ตอนนี้ฉันได้รับวิธีใหม่ในการทำเช่นนี้:

Dog d1 = new Pug("Spot"); 
Dog d2 = new Lab("Fido");

นี่บอกว่า d1 ไม่ได้เป็นเพียงสุนัข แต่เป็นปั๊กโดยเฉพาะ และ d2 นั้นก็เป็น Dog โดยเฉพาะแล็บ เราสามารถเรียกใช้พฤติกรรมและพวกเขาทำงานเหมือนก่อนหน้านี้:

d1.bark() -> "Arf!" 
d2.bark() -> "Woof!" 
d1.hasCurlyTail() -> true 
d2.hasCurlyTail() -> false 
d1.getName() -> "Spot"

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

public class Kennel {
Dog[] dogs = new Dog[20]; 
public void addDog(Dog d) { ... } 
public void printDogs() {
// Display names of all the dogs } }

นี่คือวิธีการใช้งาน:

Kennel k = new Kennel(); 
Dog d1 = new Pug("Spot"); 
Dog d2 = new Lab("Fido"); 
k.addDog(d1); 
k.addDog(d2); 
k.printDogs();

คำสั่งสุดท้ายจะแสดง: Spot Fido

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


@niranjan kurambhatti ฉันสามารถสร้างทุกชั้นเรียนเพื่อขยายสุนัข
Jeeva

3

ตัวอย่างที่ดีของวิธีการใช้อินเตอร์เฟสอยู่ในกรอบงาน Collections ถ้าคุณเขียนฟังก์ชั่นที่ใช้เวลาเป็นListแล้วมันไม่สำคัญว่าถ้าผู้ใช้ผ่านในVectorหรือArrayListหรือHashListหรืออะไรก็ตาม และคุณสามารถส่งสิ่งนั้นListไปยังฟังก์ชั่นใด ๆ ที่ต้องการCollectionหรือIterableอินเตอร์เฟซด้วย

สิ่งนี้ทำให้ฟังก์ชั่นCollections.sort(List list)เป็นไปได้โดยไม่คำนึงถึงวิธีการListใช้งาน


3

นี่คือเหตุผลที่Factory Patternsและรูปแบบการสร้างสรรค์อื่น ๆ เป็นที่นิยมใน Java คุณถูกต้องที่ไม่มี Java ไม่ได้ให้กลไกนอกกรอบเพื่อให้ง่ายต่อการเริ่มต้นอินสแตนซ์ แต่ถึงกระนั้นคุณก็จะได้นามธรรมที่คุณไม่ได้สร้างวัตถุในวิธีการของคุณซึ่งควรจะเป็นรหัสของคุณ

นอกจากนี้โดยทั่วไปฉันแนะนำให้คนไม่ทำตามกลไก "IRealname" สำหรับการตั้งชื่ออินเทอร์เฟซ นั่นเป็นสิ่งที่ Windows / COM ที่ทำให้เท้าของคุณเป็นสัญลักษณ์ของฮังการีและไม่จำเป็นจริงๆ (Java พิมพ์ออกมาอย่างรุนแรงแล้วและจุดรวมของการมีอินเตอร์เฟสก็คือการทำให้พวกมันแยกไม่ออกจากประเภทของชั้นเรียนมากที่สุด)


1
คุณกำลังสับสนในการพิมพ์ที่แข็งแกร่งด้วยการพิมพ์แบบคงที่
eljenso

3

อย่าลืมว่าในภายหลังคุณสามารถใช้คลาสที่มีอยู่แล้วและทำให้มันใช้งานIBoxได้และมันจะพร้อมใช้งานสำหรับรหัสที่รับรู้กล่องทั้งหมดของคุณ

นี้จะกลายเป็นบิตชัดเจนถ้าอินเตอร์เฟซที่มีชื่อน่า เช่น

public interface Saveable {
....

public interface Printable {
....

เป็นต้น (รูปแบบการตั้งชื่อไม่ได้ผลเสมอไปเช่นฉันไม่แน่ใจว่าBoxableมีความเหมาะสมที่นี่)


3

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

ฉันกำลังอัปเดตคำตอบด้วยคุณสมบัติใหม่ของอินเทอร์เฟซซึ่งได้นำมาใช้กับจาวาเวอร์ชัน8

จากหน้าเอกสารของ Oracle ในการสรุปอินเทอร์เฟซ :

การประกาศอินเตอร์เฟสสามารถมีได้

  1. ลายเซ็นวิธีการ
  2. วิธีการเริ่มต้น
  3. วิธีการคงที่
  4. คำจำกัดความคงที่

วิธีการเดียวที่มีการใช้งานเป็นวิธีการเริ่มต้นและคงที่

การใช้อินเตอร์เฟซ :

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

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

อะไรคือความแตกต่างระหว่างอินเตอร์เฟสและคลาสนามธรรม?

ฉันจะอธิบายความแตกต่างระหว่างอินเทอร์เฟซกับคลาสนามธรรมได้อย่างไร

มีลักษณะที่เอกสารหน้าจะเข้าใจคุณสมบัติใหม่ที่เพิ่มเข้ามาใน java 8: วิธีการเริ่มต้นและวิธีการแบบคงที่


ฉันลบแท็ก java-8 เนื่องจากคำถามไม่ได้ถามอะไรเกี่ยวกับ java-8 (และจริงๆแล้วถูกถามมานานก่อนหน้า java-8) แท็กใช้สำหรับคำถามไม่ใช่คำตอบ
Tagir Valeev

2

วัตถุประสงค์ของอินเทอร์เฟซคือนามธรรมหรือแยกจากการใช้งาน

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


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

1
หากการเขียนโปรแกรมที่มีโครงสร้างทั้งหมดเป็นนามธรรม (การอ้างสิทธิ์ของคุณ) ดังนั้นอินเทอร์เฟซจะเป็นนามธรรมในสิ่งที่เป็นนามธรรม
eljenso

1

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


1

อินเทอร์เฟซที่เพิ่ม fetature ไปยังจาวาเพื่ออนุญาตการสืบทอดหลาย ๆ นักพัฒนาของ Java แม้ว่า / ตระหนักว่ามีหลายมรดกเป็นคุณสมบัติ "อันตราย" นั่นคือเหตุผลที่เกิดขึ้นกับความคิดของอินเทอร์เฟซ

การสืบทอดหลายอย่างเป็นอันตรายเพราะคุณอาจมีคลาสดังต่อไปนี้:


class Box{
    public int getSize(){
       return 0;
    }
    public int getArea(){
       return 1;
    }

}

class Triangle{
    public int getSize(){
       return 1;
    }
    public int getArea(){
       return 0;
    }

}

class FunckyFigure extends Box, Triable{
   // we do not implement the methods we will used the inherited ones
}

ซึ่งจะเป็นวิธีที่ควรเรียกเมื่อเราใช้


   FunckyFigure.GetArea(); 

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


คุณอาจต้องการสร้างความแตกต่างระหว่างการสืบทอดหลาย ๆ การใช้งานและการเชื่อมต่อหลายอินเทอร์เฟซในคำตอบของคุณมิฉะนั้นจะทำให้เกิดความสับสน
eljenso

0

นี่คือความเข้าใจของฉันเกี่ยวกับข้อได้เปรียบของอินเทอร์เฟซ ถูกต้องฉันถ้าฉันผิด ลองนึกภาพเรากำลังพัฒนาระบบปฏิบัติการและทีมอื่นกำลังพัฒนาไดรเวอร์สำหรับอุปกรณ์บางอย่าง ดังนั้นเราจึงได้พัฒนาส่วนต่อประสาน StorageDevice เรามีสองการใช้งานของมัน (FDD และ HDD) โดยทีมงานนักพัฒนาอื่น ๆ

จากนั้นเรามีคลาส OperatingSystem ซึ่งสามารถเรียกใช้เมธอดอินเตอร์เฟสเช่น saveData เพียงแค่ส่งอินสแตนซ์ของคลาสที่ใช้อินเตอร์เฟส StorageDevice

ข้อได้เปรียบที่นี่คือเราไม่สนใจการใช้อินเทอร์เฟซ ทีมอื่นจะทำงานโดยใช้ส่วนต่อประสาน StorageDevice

package mypack;

interface StorageDevice {
    void saveData (String data);
}


class FDD implements StorageDevice {
    public void saveData (String data) {
        System.out.println("Save to floppy drive! Data: "+data);
    }
}

class HDD implements StorageDevice {
    public void saveData (String data) {
        System.out.println("Save to hard disk drive! Data: "+data);
    }
}

class OperatingSystem {
    public String name;
    StorageDevice[] devices;
    public OperatingSystem(String name, StorageDevice[] devices) {

        this.name = name;
        this.devices = devices.clone();

        System.out.println("Running OS " + this.name);
        System.out.println("List with storage devices available:");
        for (StorageDevice s: devices) {
            System.out.println(s);
        }

    }

    public void saveSomeDataToStorageDevice (StorageDevice storage, String data) {
        storage.saveData(data);
    }
}

public class Main {

    public static void main(String[] args) {

        StorageDevice fdd0 = new FDD();
        StorageDevice hdd0 = new HDD();     
        StorageDevice[] devs = {fdd0, hdd0};        
        OperatingSystem os = new OperatingSystem("Linux", devs);
        os.saveSomeDataToStorageDevice(fdd0, "blah, blah, blah...");    
    }
}

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