จุดประสงค์ของอินเทอร์เฟซเครื่องหมายคืออะไร?


คำตอบ:


77

นี่คือสัมผัสเล็กน้อยตามคำตอบของ "มิทช์วีท"

โดยทั่วไปเมื่อใดก็ตามที่ฉันเห็นผู้คนอ้างถึงแนวทางการออกแบบกรอบฉันมักจะพูดถึงว่า:

โดยทั่วไปคุณควรเพิกเฉยต่อแนวทางการออกแบบกรอบงานเกือบตลอดเวลา

นี่ไม่ได้เป็นเพราะปัญหาใด ๆ กับแนวทางการออกแบบกรอบ ฉันคิดว่า. NET framework เป็นไลบรารีคลาสที่ยอดเยี่ยม ความมหัศจรรย์จำนวนมากเกิดจากแนวทางการออกแบบกรอบ

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

คำแนะนำมากมายในนั้นสามารถแนะนำให้คุณทำสิ่งต่างๆที่:

  1. อาจไม่ใช่วิธีที่ตรงไปตรงมาที่สุดในการนำบางสิ่งไปใช้
  2. อาจส่งผลให้มีการทำซ้ำรหัสเพิ่มเติม
  3. อาจมีโอเวอร์เฮดรันไทม์เพิ่มเติม

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

ในกรณีนี้เป้าหมายหลักของนักออกแบบ API คือ:

  1. ทำให้สิ่งต่างๆสอดคล้องกับส่วนที่เหลือของกรอบ
  2. ขจัดความซับซ้อนที่ไม่จำเป็นในพื้นที่ผิว API

แนวทางการออกแบบเฟรมเวิร์คผลักดันให้นักพัฒนาสร้างโค้ดที่บรรลุเป้าหมายเหล่านั้น

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

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

หากมีนักพัฒนาหลายล้านคนใช้ API ของคุณหรือฐานรหัสของคุณใหญ่มาก (พูดมากกว่า 100K LOC) การปฏิบัติตามหลักเกณฑ์เหล่านั้นจะช่วยได้มาก

หากนักพัฒนา 5 ล้านคนใช้เวลา 15 นาทีในการเรียนรู้ API แทนที่จะใช้เวลา 60 นาทีในการเรียนรู้ผลที่ได้คือประหยัดเวลาได้ถึง 428 ปี นั่นเป็นเวลามาก

อย่างไรก็ตามโครงการส่วนใหญ่ไม่เกี่ยวข้องกับนักพัฒนาหลายล้านคนหรือ 100K + LOC ในโครงการทั่วไปที่มีผู้พัฒนา 4 รายและประมาณ 50K loc ชุดของสมมติฐานแตกต่างกันมาก นักพัฒนาในทีมจะมีความเข้าใจมากขึ้นว่าโค้ดทำงานอย่างไร นั่นหมายความว่าการปรับให้เหมาะสมสำหรับการผลิตโค้ดคุณภาพสูงอย่างรวดเร็วและเพื่อลดจำนวนข้อบกพร่องและความพยายามที่จำเป็นในการเปลี่ยนแปลง

การใช้เวลา 1 สัปดาห์ในการพัฒนาโค้ดที่สอดคล้องกับ. net framework เทียบกับ 8 ชั่วโมงในการเขียนโค้ดที่เปลี่ยนแปลงง่ายและมีบั๊กน้อยลงอาจส่งผลให้:

  1. โครงการล่าช้า
  2. โบนัสที่ต่ำกว่า
  3. จำนวนข้อผิดพลาดที่เพิ่มขึ้น
  4. ใช้เวลาอยู่ที่สำนักงานมากขึ้นและใช้เวลาบนชายหาดน้อยลงในการดื่มมาการิต้า

หากไม่มีนักพัฒนารายอื่น 4,999,999 รายเพื่อรับภาระค่าใช้จ่ายก็มักจะไม่คุ้มค่า

ตัวอย่างเช่นการทดสอบอินเทอร์เฟซของ marker จะมีนิพจน์ "is" เพียงรายการเดียวและส่งผลให้โค้ดที่ค้นหาแอตทริบิวต์น้อยลง

ดังนั้นคำแนะนำของฉันคือ:

  1. ปฏิบัติตามแนวทางของเฟรมเวิร์กอย่างเคร่งครัดหากคุณกำลังพัฒนาไลบรารีคลาส (หรือวิดเจ็ต UI) ที่มีไว้สำหรับการใช้งานแบบกระจายกว้าง
  2. ลองใช้บางส่วนหากคุณมี LOC มากกว่า 100K ในโครงการของคุณ
  3. มิฉะนั้นให้เพิกเฉยต่อสิ่งเหล่านี้โดยสิ้นเชิง

12
โดยส่วนตัวฉันเห็นโค้ดใด ๆ ที่ฉันเขียนเป็นไลบรารีฉันจะต้องใช้ในภายหลัง ฉันไม่สนใจว่าการบริโภคจะแพร่หลายหรือไม่ - แนวทางต่อไปนี้เพิ่มความสม่ำเสมอและลดความประหลาดใจเมื่อต้องดูรหัสของฉันและทำความเข้าใจในอีกหลายปีต่อมา ...
Reed Copsey

16
ฉันไม่ได้บอกว่าแนวทางไม่ดี ฉันกำลังบอกว่าควรจะแตกต่างกันขึ้นอยู่กับขนาดของฐานรหัสของคุณและจำนวนผู้ใช้ที่คุณมี แนวทางการออกแบบจำนวนมากขึ้นอยู่กับสิ่งต่างๆเช่นการปรับเทียบความสามารถในการปรับเทียบไบนารีซึ่งไม่สำคัญสำหรับไลบรารี "ภายใน" ที่ใช้โดยโครงการจำนวนมากเนื่องจากเป็นของบางอย่างเช่น BCL แนวทางอื่น ๆ เช่นเดียวกับแนวทางที่เกี่ยวข้องกับการใช้งานมักมีความสำคัญ คุณธรรมคืออย่าเคร่งศาสนามากเกินไปเกี่ยวกับแนวทางโดยเฉพาะอย่างยิ่งในโครงการขนาดเล็ก
Scott Wisniewski

6
+1 - ไม่ค่อยตอบคำถามของ OP - วัตถุประสงค์ของ MI - แต่ก็มีประโยชน์มาก
bzarah

5
@ScottWisniewski: ฉันคิดว่าคุณพลาดจุดที่ร้ายแรง แนวทางของ Framework ใช้ไม่ได้กับโครงการขนาดใหญ่ แต่จะใช้กับโครงการขนาดกลางและขนาดเล็กบางโครงการ พวกเขากลายเป็นคนฆ่ามากเกินไปเมื่อคุณพยายามนำมันไปใช้กับโปรแกรม Hello-World อยู่เสมอ ตัวอย่างเช่นการ จำกัด อินเทอร์เฟซไว้ที่ 5 วิธีนั้นเป็นกฎที่ดีเสมอไม่ว่าแอปจะมีขนาดเท่าใดก็ตาม อีกสิ่งหนึ่งที่คุณพลาดแอปขนาดเล็กในวันนี้อาจกลายเป็นแอปขนาดใหญ่ในวันพรุ่งนี้ ดังนั้นจึงเป็นการดีกว่าที่คุณจะสร้างโดยใช้หลักการที่ดีที่ใช้กับแอปขนาดใหญ่เพื่อที่ว่าเมื่อถึงเวลาต้องขยายขนาดคุณจะได้ไม่ต้องเขียนโค้ดซ้ำมากนัก
ฟิลิป

2
ฉันไม่ค่อยเห็นว่าการปฏิบัติตามแนวทางการออกแบบ (ส่วนใหญ่) จะส่งผลให้โครงการ 8 ชั่วโมงใช้เวลา 1 สัปดาห์ได้อย่างไร เช่นการตั้งชื่อvirtual protectedเมธอด template DoSomethingCoreแทนDoSomethingไม่ใช่งานเพิ่มเติมและคุณสื่อสารอย่างชัดเจนว่าเป็นวิธีเทมเพลต ... IMNSHO คนที่เขียนแอปพลิเคชันโดยไม่คำนึงถึง API ( But.. I'm not a framework developer, I don't care about my API!) คือคนที่เขียนซ้ำกันมาก ( และยังไม่มีเอกสารและมักจะอ่านไม่ได้) รหัสไม่ใช่วิธีอื่น
Laoujin

46

Marker Interfaces ใช้เพื่อทำเครื่องหมายความสามารถของคลาสเป็นการใช้อินเทอร์เฟซเฉพาะในขณะรันไทม์

แนวทางการออกแบบอินเทอร์เฟซและ. NET ประเภท - การออกแบบอินเทอร์เฟซไม่สนับสนุนการใช้อินเทอร์เฟซของเครื่องหมายเพื่อสนับสนุนการใช้แอตทริบิวต์ใน C # แต่เนื่องจาก @Jay Bazuzi ชี้ให้เห็นว่าการตรวจสอบอินเทอร์เฟซของเครื่องหมายนั้นง่ายกว่าสำหรับแอตทริบิวต์:o is I

แทนที่จะเป็นสิ่งนี้:

public interface IFooAssignable {} 

public class FooAssignableAttribute : IFooAssignable 
{
    ...
}

แนวทาง. NET แนะนำให้คุณทำสิ่งนี้:

public class FooAssignableAttribute : Attribute 
{
    ...
}

[FooAssignable]
public class Foo 
{    
   ...
} 

28
นอกจากนี้เรายังสามารถใช้ generics กับ marker interface ได้อย่างเต็มที่ แต่ใช้กับแอตทริบิวต์ไม่ได้
Jordão

18
ในขณะที่ฉันรักคุณลักษณะและวิธีที่พวกเขามองจากมุมมองที่เปิดเผยพวกเขาไม่ใช่พลเมืองชั้นหนึ่งที่รันไทม์และต้องการระบบประปาระดับค่อนข้างต่ำจำนวนมากในการทำงานด้วย
Jesse C. Slicer

4
@ Jordão - นี่คือความคิดของฉันอย่างแน่นอน ตัวอย่างเช่นถ้าฉันต้องการรหัสการเข้าถึงฐานข้อมูลแบบนามธรรม (พูดว่า Linq เป็น Sql) การมีอินเทอร์เฟซทั่วไปทำให้ A LOT ง่ายขึ้นมาก อันที่จริงฉันไม่คิดว่ามันจะเป็นไปได้ที่จะเขียนสิ่งที่เป็นนามธรรมด้วยแอตทริบิวต์แบบนั้นเนื่องจากคุณไม่สามารถแคสต์เป็นแอตทริบิวต์และไม่สามารถใช้ในลักษณะทั่วไปได้ ฉันคิดว่าคุณสามารถใช้คลาสพื้นฐานที่ว่างเปล่าซึ่งคลาสอื่น ๆ ได้มาทั้งหมด แต่ให้ความรู้สึกเหมือนกันมากหรือน้อยเหมือนกับการมีอินเทอร์เฟซที่ว่างเปล่า นอกจากนี้หากคุณรู้ในภายหลังว่าคุณต้องการฟังก์ชั่นที่ใช้ร่วมกันกลไกดังกล่าวก็ถูกใช้
tandrewnichols

23

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

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

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

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

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

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


1
จุดประสงค์หลักของอินเทอร์เฟซใด ๆ คือเพื่อแยกความแตกต่างระหว่างคลาสที่สัญญาว่าจะปฏิบัติตามสัญญาที่เกี่ยวข้องกับอินเทอร์เฟซนั้นและสิ่งที่ไม่ทำ แม้ว่าอินเทอร์เฟซจะรับผิดชอบในการส่งลายเซ็นการโทรของสมาชิกที่จำเป็นในการปฏิบัติตามสัญญา แต่ก็เป็นสัญญาแทนที่จะเป็นสมาชิกซึ่งกำหนดว่าควรใช้อินเทอร์เฟซเฉพาะโดยคลาสใดคลาสหนึ่งหรือไม่ หากสัญญาIConstructableFromString<T>ระบุว่าคลาสTสามารถใช้งานIConstructableFromString<T>ได้ก็ต่อเมื่อมีสมาชิกแบบคงที่ ...
supercat

... public static T ProduceFromString(String params);คลาสที่แสดงร่วมกับอินเทอร์เฟซสามารถเสนอวิธีการpublic static T ProduceFromString<T>(String params) where T:IConstructableFromString<T>; ถ้ารหัสไคลเอนต์มีวิธีการเช่นT[] MakeManyThings<T>() where T:IConstructableFromString<T>หนึ่งสามารถกำหนดประเภทใหม่ที่สามารถทำงานกับรหัสไคลเอนต์โดยไม่ต้องแก้ไขรหัสไคลเอนต์เพื่อจัดการกับพวกเขา หากข้อมูลเมตาอยู่ในรหัสไคลเอ็นต์จะไม่สามารถสร้างประเภทใหม่เพื่อใช้โดยไคลเอ็นต์ที่มีอยู่ได้
supercat

แต่สัญญาระหว่างTและคลาสที่ใช้เป็นIConstructableFromString<T>ที่ที่คุณมีวิธีการในอินเทอร์เฟซที่อธิบายพฤติกรรมบางอย่างดังนั้นจึงไม่ใช่อินเทอร์เฟซของเครื่องหมาย
Tom B

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

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

8

อินเทอร์เฟซเครื่องหมายบางครั้งอาจเป็นสิ่งชั่วร้ายที่จำเป็นเมื่อภาษาไม่รองรับประเภทสหภาพที่เลือกปฏิบัติ

สมมติว่าคุณต้องการกำหนดวิธีการที่คาดหวังอาร์กิวเมนต์ซึ่งประเภทต้องเป็นหนึ่งใน A, B หรือ C ในภาษาที่ใช้งานได้เป็นอันดับแรก (เช่นF # ) ประเภทดังกล่าวสามารถกำหนดได้อย่างสมบูรณ์เป็น:

type Arg = 
    | AArg of A 
    | BArg of B 
    | CArg of C

อย่างไรก็ตามในภาษา OO-first เช่น C # ไม่สามารถทำได้ วิธีเดียวที่จะบรรลุสิ่งที่คล้ายกันที่นี่คือการกำหนดอินเทอร์เฟซ IArg และ "ทำเครื่องหมาย" A, B และ C ด้วย

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

ประเภทสหภาพแรงงานที่เลือกปฏิบัติมีประโยชน์อย่างยิ่งและมีอยู่ในภาษาที่ใช้งานได้เป็นเวลาอย่างน้อย 30 ปี น่าแปลกที่จนถึงทุกวันนี้ภาษา OO กระแสหลักทั้งหมดไม่สนใจคุณลักษณะนี้ - แม้ว่าจริงๆแล้วจะไม่เกี่ยวข้องกับการเขียนโปรแกรมเชิงฟังก์ชันต่อ se แต่เป็นของระบบ type


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

6

อินเทอร์เฟซเครื่องหมายเป็นเพียงอินเทอร์เฟซที่ว่างเปล่า คลาสจะใช้อินเทอร์เฟซนี้เป็นข้อมูลเมตาเพื่อใช้ด้วยเหตุผลบางประการ ในภาษา C # คุณมักจะใช้แอตทริบิวต์เพื่อมาร์กอัปคลาสด้วยเหตุผลเดียวกับที่คุณใช้อินเทอร์เฟซเครื่องหมายในภาษาอื่น


4

อินเทอร์เฟซเครื่องหมายช่วยให้สามารถแท็กคลาสในลักษณะที่จะนำไปใช้กับคลาสที่สืบทอดทั้งหมด อินเทอร์เฟซเครื่องหมาย "บริสุทธิ์" จะไม่กำหนดหรือสืบทอดสิ่งใด ๆ อินเทอร์เฟซ marker ที่มีประโยชน์มากกว่าอาจเป็นอินเทอร์เฟซอื่นที่ "สืบทอด" แต่ไม่ได้กำหนดสมาชิกใหม่ ตัวอย่างเช่นหากมีอินเทอร์เฟซ "IReadableFoo" เราอาจกำหนดอินเทอร์เฟซ "IImmutableFoo" ซึ่งจะทำงานเหมือน "Foo" แต่จะสัญญากับทุกคนที่ใช้อินเทอร์เฟซนี้ว่าจะไม่มีอะไรเปลี่ยนแปลงค่า รูทีนที่ยอมรับ IImmutableFoo จะสามารถใช้งานได้เหมือนกับ IReadableFoo แต่รูทีนจะยอมรับเฉพาะคลาสที่ประกาศว่าใช้ IImmutableFoo เท่านั้น

ฉันไม่สามารถนึกถึงการใช้งานอินเทอร์เฟซเครื่องหมาย "บริสุทธิ์" ได้มากนัก สิ่งเดียวที่ฉันคิดได้คือถ้า EqualityComparer (ของ T) ค่าเริ่มต้นจะส่งคืน Object.Equals สำหรับประเภทใด ๆ ที่ใช้ IDoNotUseEqualityComparer แม้ว่าประเภทนั้นจะใช้ IEqualityComparer ด้วยก็ตาม สิ่งนี้จะช่วยให้มีชนิดที่ไม่สามารถปิดผนึกได้โดยไม่ละเมิดหลักการแทนที่ของ Liskov: ถ้าประเภทนั้นผนึกวิธีการทั้งหมดที่เกี่ยวข้องกับการทดสอบความเท่าเทียมกันประเภทที่ได้รับสามารถเพิ่มฟิลด์เพิ่มเติมและทำให้เปลี่ยนแปลงได้ แต่การกลายพันธุ์ของฟิลด์ดังกล่าวจะไม่ ไม่สามารถมองเห็นได้โดยใช้วิธีการประเภทฐานใด ๆ อาจไม่น่ากลัวที่จะมีคลาสที่ไม่สามารถปิดผนึกไม่ได้และหลีกเลี่ยงการใช้ EqualityComparer.Default หรือ trust ที่ได้รับคลาสที่ไม่ใช้ IEqualityComparer


4

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

public static bool HasAttribute<T>(this ICustomAttributeProvider self)
    where T : Attribute
{
    return self.GetCustomAttributes(true).Any(o => o is T);
}

public static bool HasAttribute<T>(this object self)
    where T : Attribute
{
    return self != null && self.GetType().HasAttribute<T>()
}

ตอนนี้คุณมี:

if (o.HasAttribute<FooAssignableAttribute>())
{
    //...
}

เทียบกับ:

if (o is IFooAssignable)
{
    //...
}

ฉันไม่เห็นว่าการสร้าง API จะใช้เวลานานกว่ารูปแบบแรกถึง 5 เท่าเมื่อเทียบกับรูปแบบที่สองตามที่ Scott กล่าวอ้าง


1
ยังไม่มียาชื่อสามัญ
Ian Kemp

1

เครื่องหมายคืออินเทอร์เฟซที่ว่างเปล่า เครื่องหมายอยู่ที่นั่นหรือไม่มี

คลาส Foo: IConfidential

ที่นี่เราทำเครื่องหมาย Foo ว่าเป็นความลับ ไม่จำเป็นต้องมีคุณสมบัติหรือแอตทริบิวต์เพิ่มเติมที่แท้จริง


0

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


0

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

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