อินเทอร์เฟซ C # การใช้งานโดยนัยกับการนำไปใช้อย่างชัดเจน


632

อะไรคือความแตกต่างในการนำอินเตอร์เฟสไปใช้โดยนัยและชัดเจนใน C #?

เมื่อใดที่คุณควรใช้โดยนัยและเมื่อใดที่คุณควรใช้อย่างชัดเจน

มีข้อดีและ / หรือข้อเสียอย่างใดอย่างหนึ่งหรือไม่?


แนวทางอย่างเป็นทางการของ Microsoft (จากแนวทางการออกแบบกรอบงานฉบับที่ 1 ) ระบุว่าไม่แนะนำให้ใช้การใช้งานอย่างชัดเจนเนื่องจากจะให้รหัสพฤติกรรมที่ไม่คาดคิด

ฉันคิดว่าแนวทางนี้ใช้ได้ในเวลา IoC ก่อนเมื่อคุณไม่ผ่านสิ่งต่าง ๆ เป็นอินเทอร์เฟซ

ใครสามารถสัมผัสด้านนั้นเช่นกัน?


อ่านบทความเต็มเกี่ยวกับอินเทอร์เฟซ C #: planetofcoders.com/c-interfaces
Gaurav Agrawal

ใช่ควรหลีกเลี่ยงอินเทอร์เฟซที่ชัดเจนและมีวิธีการที่เป็นมืออาชีพมากขึ้นในการใช้ ISP (หลักการแยกอินเตอร์เฟส) ที่นี่เป็นบทความรายละเอียดเกี่ยวกับcodeproject.com/Articles/1000374/
Shivprasad Koirala

คำตอบ:


492

โดยปริยายคือเมื่อคุณกำหนดอินเทอร์เฟซของคุณผ่านสมาชิกในชั้นเรียนของคุณ ชัดเจนคือเมื่อคุณกำหนดวิธีการภายในชั้นเรียนของคุณบนอินเตอร์เฟซ ฉันรู้ว่าฟังดูสับสน แต่นี่คือสิ่งที่ฉันหมายถึง: IList.CopyToจะถูกนำไปใช้โดยปริยายในฐานะ:

public void CopyTo(Array array, int index)
{
    throw new NotImplementedException();
}

และชัดเจนว่า:

void ICollection.CopyTo(Array array, int index)
{
    throw new NotImplementedException();
}

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

MyClass myClass = new MyClass(); // Declared as concrete class
myclass.CopyTo //invalid with explicit
((IList)myClass).CopyTo //valid with explicit.

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

ฉันแน่ใจว่ามีเหตุผลเพิ่มเติมที่จะใช้ / ไม่ใช้อย่างชัดเจนว่าผู้อื่นจะโพสต์

ดูโพสต์ถัดไปในหัวข้อนี้สำหรับเหตุผลที่ดีเยี่ยมในแต่ละหลัง


8
ฉันรู้ว่าโพสต์นี้เก่า แต่ฉันคิดว่ามันมีประโยชน์มาก - สิ่งหนึ่งที่ควรทราบหากยังไม่ชัดเจนเพราะไม่ใช่สำหรับฉันนั่นคือในตัวอย่างหนึ่งโดยนัยมีpublicคำหลัก ... มิฉะนั้นคุณจะได้รับข้อผิดพลาด
jharr100

CLR ของ Jeffrey Richter ผ่าน C # 4 ed ch 13 แสดงตัวอย่างที่ไม่ต้องการใช้การหล่อ: โครงสร้างภายใน SomeValueType: IComparable {private Int32 m_x; SomeValueType สาธารณะ (Int32 x) {m_x = x; } public Int32 CompareTo (SomeValueType other) {... );} Int32 IComparable.CompareTo (Object อื่น ๆ ) {return CompareTo ((SomeValueType) อื่น ๆ ); }} โมฆะสาธารณะแบบคงที่ Main () {SomeValueType v = new SomeValueType (0); Object o = วัตถุใหม่ (); Int32 n = v.CompareTo (v); // ไม่ชกมวย n = v. เปรียบเทียบกับ (o); // ข้อผิดพลาดในการคอมไพล์เวลา}
Andy Dent

1
วันนี้ฉันพบสถานการณ์ที่ไม่ค่อยเกิดขึ้นซึ่งจำเป็นต้องใช้อินเทอร์เฟซที่ชัดเจน: คลาสที่มีฟิลด์ที่สร้างโดยตัวสร้างอินเตอร์เฟสที่สร้างฟิลด์เป็นส่วนตัว (Xamarin กำหนดเป้าหมาย iOS โดยใช้สตอรี่บอร์ด iOS) และอินเทอร์เฟซที่ทำให้รู้สึกถึงการเปิดเผยฟิลด์นั้น (สาธารณะอ่านได้อย่างเดียว) ฉันสามารถเปลี่ยนชื่อของ getter ในอินเตอร์เฟสได้ แต่ชื่อที่มีอยู่คือชื่อที่สมเหตุสมผลที่สุดสำหรับวัตถุ UISwitch IScoreRegPlayerViewCell.markerSwitch { get { return markerSwitch; } }ดังนั้นแทนที่จะฉันไม่ได้มีการใช้งานอย่างชัดเจนหมายถึงข้อมูลส่วนตัว:
ToolmakerSteve

1
ความน่ากลัวของการเขียนโปรแกรม ดี debunked อย่าง!
Liquid Core

1
@ToolmakerSteve สถานการณ์อื่น (ค่อนข้างบ่อย) ที่ต้องมีการใช้งานอย่างชัดเจนของสมาชิกอินเตอร์เฟสอย่างน้อยหนึ่งรายกำลังใช้หลายอินเตอร์เฟสที่มีสมาชิกที่มีลายเซ็นเดียวกัน แต่มีประเภทการส่งคืนที่แตกต่างกัน นี้สามารถเกิดขึ้นเพราะมรดกอินเตอร์เฟซที่เป็นมันจะมีIEnumerator<T>.Current, และIEnumerable<T>.GetEnumerator() ISet<T>.Add(T)นี้ถูกกล่าวถึงในคำตอบอื่น
phoog

201

คำจำกัดความโดยนัยจะเป็นเพียงการเพิ่มวิธีการ / คุณสมบัติ ฯลฯ ต้องการโดยอินเตอร์เฟซโดยตรงกับชั้นเรียนเป็นวิธีการสาธารณะ

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

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

2
คุณทำคะแนนได้ดีที่นี่ โดยเฉพาะอย่างยิ่ง A. ฉันมักจะผ่านชั้นเรียนของฉันเป็นอินเตอร์เฟสอย่างไรก็ตาม แต่ฉันไม่เคยคิดเกี่ยวกับมันในมุมมองที่
mattlant

5
ฉันไม่แน่ใจว่าฉันเห็นด้วยกับจุด C วัตถุ Cat อาจใช้ IEatable ได้ แต่ Eat () เป็นส่วนพื้นฐานของสิ่งต่าง ๆ จะมีหลายกรณีที่คุณต้องการเรียกใช้ Eat () บน Cat เมื่อคุณใช้วัตถุ 'raw' แทนการผ่านส่วนต่อประสาน IEatable ใช่ไหม
LegendLength

59
ฉันรู้ว่าสถานที่บางแห่งไม่มีการคัดค้านCatอย่างแน่นอน IEatable
อุมแบร์โต

26
ฉันไม่เห็นด้วยกับสิ่งที่กล่าวมาทั้งหมดและจะบอกว่าการใช้อินเทอร์เฟซที่ชัดเจนเป็นสูตรของความหายนะและไม่ใช่คำจำกัดความของ OOP หรือ OOD (ดูคำตอบของฉันเกี่ยวกับความหลากหลาย)
Valentin Kuzub

2
ดูเพิ่มเติม: stackoverflow.com/questions/4103300/…
Zack Jannsen

68

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

นี่คือตัวอย่าง:

public abstract class StringList : IEnumerable<string>
{
    private string[] _list = new string[] {"foo", "bar", "baz"};

    // ...

    #region IEnumerable<string> Members
    public IEnumerator<string> GetEnumerator()
    {
        foreach (string s in _list)
        { yield return s; }
    }
    #endregion

    #region IEnumerable Members
    IEnumerator IEnumerable.GetEnumerator()
    {
        return this.GetEnumerator();
    }
    #endregion
}

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

กล่าวคือ

StringList sl = new StringList();

// uses the implicit definition.
IEnumerator<string> enumerableString = sl.GetEnumerator();
// same as above, only a little more explicit.
IEnumerator<string> enumerableString2 = ((IEnumerable<string>)sl).GetEnumerator();
// returns the same as above, but via the explicit definition
IEnumerator enumerableStuff = ((IEnumerable)sl).GetEnumerator();

PS: ชิ้นส่วนทางอ้อมในคำจำกัดความที่ชัดเจนสำหรับ IEnumerable ทำงานได้เนื่องจากภายในฟังก์ชั่นคอมไพเลอร์รู้ว่าตัวแปรชนิดที่แท้จริงคือ StringList และนั่นคือวิธีที่จะแก้ไขการเรียกใช้ฟังก์ชัน ความจริงเล็ก ๆ น้อย ๆ ที่ดีสำหรับการนำเลเยอร์บางส่วนของสิ่งที่เป็นนามธรรมบางส่วนของ.


5
@Tassadaque: หลังจาก 2 ปีทั้งหมดที่ฉันสามารถพูดได้คือ "คำถามที่ดี" abstractฉันมีความคิดยกเว้นบางทีที่ผมคัดลอกโค้ดที่ได้จากสิ่งที่ฉันกำลังทำงานอยู่บนที่มันเป็น
Matthew Scharley

4
@Tassadaque คุณพูดถูก แต่ฉันคิดว่าจุดโพสต์ของ Matthew Scharley ไม่ได้หายไป
funkymushroom

35

เพื่ออ้าง Jeffrey Richter จาก CLR ผ่าน C #
( EIMIหมายถึงE xplicit I nterface M ethod I mplementation)

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

  • ไม่มีเอกสารอธิบายถึงประเภทที่ใช้วิธีการ EIMI โดยเฉพาะและไม่มีการสนับสนุนMicrosoft Visual Studio IntelliSense
  • อินสแตนซ์ชนิดของค่าจะถูกทำกล่องเมื่อส่งไปยังอินเตอร์เฟส
  • EIMI ไม่สามารถเรียกใช้ตามประเภทที่ได้รับ

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

EIMIs ยังสามารถใช้เพื่อซ่อนสมาชิกอินเทอร์เฟซที่ไม่ได้พิมพ์อย่างรุนแรงจากการใช้งาน Framework Interfaces ขั้นพื้นฐานเช่น IEnumerable <T> ดังนั้นคลาสของคุณจะไม่เปิดเผยวิธีที่ไม่ได้พิมพ์อย่างรุนแรงโดยตรง แต่เป็นการสร้างประโยคที่ถูกต้อง


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

4
@Valentin EIMI และ IEMI คืออะไร
Dzienny

การใช้วิธีการอินเทอร์เฟซที่ชัดเจน
scobi

5
-1 สำหรับ "โดยทั่วไปฉันเห็นว่าการเชื่อมต่อเป็นคุณลักษณะแบบกึ่ง (ที่ดีที่สุด) OOP มันให้การสืบทอด แต่มันไม่ได้ให้ความหลากหลายที่แท้จริง" ฉันไม่เห็นด้วยอย่างยิ่ง อินเตอร์เฟซนั้นเกี่ยวกับความหลากหลายและไม่เกี่ยวกับการสืบทอดเป็นหลัก พวกเขาอธิบายการจำแนกประเภทหลายประเภท หลีกเลี่ยง IEMI เมื่อคุณสามารถและมอบสิทธิ์ตามที่ @supercat แนะนำเมื่อคุณทำไม่ได้ อย่าหลีกเลี่ยงการเชื่อมต่อ
Aluan Haddad

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

34

เหตุผลที่ # 1

ฉันมักจะใช้อินเทอร์เฟซที่ชัดเจนเมื่อฉันต้องการกีดกัน "การเขียนโปรแกรมเพื่อการใช้งาน" ( หลักการออกแบบจากรูปแบบการออกแบบ )

ตัวอย่างเช่นในแอปพลิเคชันบนเว็บMVP :

public interface INavigator {
    void Redirect(string url);
}

public sealed class StandardNavigator : INavigator {
    void INavigator.Redirect(string url) {
        Response.Redirect(url);
    }
}

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

เหตุผลที่ # 2

อีกเหตุผลหนึ่งที่ฉันอาจไปกับการใช้อินเทอร์เฟซที่ชัดเจนก็คือการทำให้อินเทอร์เฟซ "เริ่มต้น" ของคลาสสะอาดขึ้น ตัวอย่างเช่นถ้าฉันพัฒนาตัวควบคุมเซิร์ฟเวอร์ASP.NETฉันอาจต้องการอินเทอร์เฟซสองรายการ:

  1. อินเทอร์เฟซหลักของคลาสซึ่งใช้โดยผู้พัฒนาเว็บเพจ และ
  2. อินเทอร์เฟซ "ซ่อน" ที่ใช้โดยผู้นำเสนอที่ฉันพัฒนาเพื่อจัดการกับตรรกะของตัวควบคุม

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

public sealed class CustomerComboBox : ComboBox, ICustomerComboBox {
    private readonly CustomerComboBoxPresenter presenter;

    public CustomerComboBox() {
        presenter = new CustomerComboBoxPresenter(this);
    }

    protected override void OnLoad() {
        if (!Page.IsPostBack) presenter.HandleFirstLoad();
    }

    // Primary interface used by web page developers
    public Guid ClientId {
        get { return new Guid(SelectedItem.Value); }
        set { SelectedItem.Value = value.ToString(); }
    }

    // "Hidden" interface used by presenter
    IEnumerable<CustomerDto> ICustomerComboBox.DataSource { set; }
}

ผู้นำเสนอเติมแหล่งข้อมูลและผู้พัฒนาเว็บเพจไม่จำเป็นต้องตระหนักถึงการมีอยู่ของมัน

แต่มันไม่ใช่ลูกกระสุนปืนเงิน

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


19

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

/// <summary>
/// This is a Book
/// </summary>
interface IBook
{
    string Title { get; }
    string ISBN { get; }
}

/// <summary>
/// This is a Person
/// </summary>
interface IPerson
{
    string Title { get; }
    string Forename { get; }
    string Surname { get; }
}

/// <summary>
/// This is some freaky book-person.
/// </summary>
class Class1 : IBook, IPerson
{
    /// <summary>
    /// This method is shared by both Book and Person
    /// </summary>
    public string Title
    {
        get
        {
            string personTitle = "Mr";
            string bookTitle = "The Hitchhikers Guide to the Galaxy";

            // What do we do here?
            return null;
        }
    }

    #region IPerson Members

    public string Forename
    {
        get { return "Lee"; }
    }

    public string Surname
    {
        get { return "Oades"; }
    }

    #endregion

    #region IBook Members

    public string ISBN
    {
        get { return "1-904048-46-3"; }
    }

    #endregion
}

รหัสนี้คอมไพล์และรัน OK แต่คุณสมบัติหัวเรื่องถูกแชร์

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

string IBook.Title
{
    get
    {
        return "The Hitchhikers Guide to the Galaxy";
    }
}

string IPerson.Title
{
    get
    {
        return "Mr";
    }
}

public string Title
{
    get { return "Still shared"; }
}

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

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

หากคุณไม่ได้กำหนดชื่อ "ที่ใช้ร่วมกัน" (โดยนัย) ผู้บริโภคของ Class1 จะต้องส่งอินสแตนซ์ของ Class1 ไปยัง IBook หรือ IPerson อย่างชัดเจนก่อน - มิฉะนั้นรหัสจะไม่รวบรวม


16

ฉันใช้การปรับใช้อินเตอร์เฟสอย่างชัดเจนเกือบทุกครั้ง นี่คือเหตุผลหลัก

การปรับโครงสร้างใหม่นั้นปลอดภัยยิ่งขึ้น

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

สองกรณีทั่วไปที่นึกถึง:

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

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

โชคไม่ดีที่ C # ไม่มีคำหลักที่บังคับให้เราทำเครื่องหมายวิธีเป็นการติดตั้งโดยปริยายดังนั้นคอมไพเลอร์สามารถทำการตรวจสอบพิเศษได้ วิธีเสมือนไม่มีปัญหาใด ๆ ข้างต้นเนื่องจากต้องใช้ 'override' และ 'new'

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

มันเป็นเอกสารด้วยตนเอง

ถ้าฉันเห็น 'public bool Execute ()' ในชั้นเรียนมันจะต้องใช้ความพยายามเป็นพิเศษในการหาว่ามันเป็นส่วนหนึ่งของส่วนต่อประสาน ใครบางคนอาจจะต้องแสดงความคิดเห็นมันพูดอย่างนั้นหรือวางไว้ในกลุ่มของการใช้งานอินเทอร์เฟซอื่น ๆ ทั้งหมดภายใต้ภูมิภาคหรือความคิดเห็นการจัดกลุ่มพูดว่า "การใช้ ITask" แน่นอนว่าใช้งานได้เฉพาะในกรณีที่ส่วนหัวของกลุ่มไม่ได้อยู่นอกจอ ..

โดยที่: 'bool ITask.Execute ()' ชัดเจนและไม่คลุมเครือ

การแยกการใช้อินเตอร์เฟสอย่างชัดเจน

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

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

(ฉันตระหนักว่าสาธารณะเป็นสัญญารหัส แต่ส่วนต่อประสานนั้นแข็งแกร่งกว่ามากโดยเฉพาะอย่างยิ่งในฐานข้อมูลที่ขับเคลื่อนด้วยอินเตอร์เฟสซึ่งการใช้งานโดยตรงประเภทคอนกรีตมักเป็นสัญลักษณ์ของรหัสภายในเท่านั้น)

ที่เกี่ยวข้อง: เหตุผลที่ 2 ข้างต้นโดยจอน

และอื่น ๆ

บวกกับข้อดีที่ได้กล่าวไปแล้วในคำตอบอื่น ๆ ที่นี่:

ปัญหาที่เกิดขึ้น

มันไม่ใช่เรื่องสนุกและมีความสุข มีบางกรณีที่ฉันติดกับ implicits:

  • ประเภทค่าเพราะนั่นจะต้องใช้มวยและลดความสมบูรณ์ นี่ไม่ใช่กฎที่เข้มงวดและขึ้นอยู่กับส่วนต่อประสานและวิธีการใช้งาน IComparable? โดยปริยาย IFormattable? อาจชัดเจน
  • ส่วนต่อประสานระบบเล็กน้อยที่มีวิธีการที่เรียกบ่อยๆโดยตรง (เช่น IDisposable.Dispose)

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

  1. เพิ่มสาธารณะและให้วิธีการอินเทอร์เฟซส่งต่อเพื่อนำไปใช้งาน มักเกิดขึ้นกับอินเทอร์เฟซที่ง่ายขึ้นเมื่อทำงานภายใน
  2. (วิธีที่แนะนำของฉัน) เพิ่มpublic IMyInterface I { get { return this; } }(ซึ่งควรจะได้รับ inlined) foo.I.InterfaceMethod()และโทร หากมีหลายอินเตอร์เฟสที่ต้องการความสามารถนี้ให้ขยายชื่อเกินกว่าที่ฉัน (จากประสบการณ์ของฉันมันหายากที่ฉันมีความต้องการนี้)

8

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

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

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

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

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


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

6

การใช้อินเทอร์เฟซโดยนัยคือที่ที่คุณมีวิธีการที่มีลายเซ็นของอินเทอร์เฟซเดียวกัน

การใช้อินเทอร์เฟซที่ชัดเจนเป็นที่ที่คุณประกาศอย่างชัดเจนว่าเป็นวิธีการอินเตอร์เฟซ

interface I1
{
    void implicitExample();
}

interface I2
{
    void explicitExample();
}


class C : I1, I2
{
    void implicitExample()
    {
        Console.WriteLine("I1.implicitExample()");
    }


    void I2.explicitExample()
    {
        Console.WriteLine("I2.explicitExample()");
    }
}

MSDN: การใช้อินเทอร์เฟซโดยนัยและชัดเจน


6

สมาชิกคลาสทุกคนที่ใช้อินเตอร์เฟซส่งออกประกาศซึ่งมีความหมายคล้ายกับวิธีการเขียนประกาศอินเตอร์เฟซ VB.NET เช่น

Public Overridable Function Foo() As Integer Implements IFoo.Foo

แม้ว่าชื่อของสมาชิกคลาสมักจะตรงกับชื่อของสมาชิกอินเทอร์เฟซและสมาชิกคลาสมักจะเป็นสาธารณะไม่จำเป็นต้องมีสิ่งเหล่านั้น หนึ่งอาจประกาศ:

Protected Overridable Function IFoo_Foo() As Integer Implements IFoo.Foo

ซึ่งในกรณีนี้ชั้นและอนุพันธ์จะได้รับอนุญาตในการเข้าถึงสมาชิกชั้นเรียนโดยใช้ชื่อแต่โลกภายนอกเท่านั้นที่จะสามารถเข้าถึงได้โดยเฉพาะอย่างยิ่งที่สมาชิกโดยการหล่อไปIFoo_Foo IFooวิธีการดังกล่าวมักจะดีในกรณีที่วิธีการอินเทอร์เฟซจะมีพฤติกรรมที่ระบุในการใช้งานทั้งหมด แต่พฤติกรรมที่เป็นประโยชน์ในบาง [เช่นพฤติกรรมที่ระบุสำหรับIList<T>.Addวิธีการของคอลเลกชันอ่านอย่างเดียวคือการโยนNotSupportedException] น่าเสียดายวิธีเดียวที่เหมาะสมในการนำอินเตอร์เฟสไปใช้ใน C # คือ:

int IFoo.Foo() { return IFoo_Foo(); }
protected virtual int IFoo_Foo() { ... real code goes here ... }

ไม่ค่อยดีเท่าไหร่


2

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

ปัญหาและวิธีการแก้ปัญหาจะอธิบายได้ดีในบทความC # อินเตอร์เฟสภายใน

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


2

คำตอบก่อนหน้านี้อธิบายได้ว่าทำไมการใช้อินเตอร์เฟซที่ชัดเจนใน C # อาจจะpreferrable (ด้วยเหตุผลอย่างเป็นทางการส่วนใหญ่) แต่มีเป็นหนึ่งในสถานการณ์ที่การดำเนินงานอย่างชัดเจนคือบังคับ : เพื่อหลีกเลี่ยงการรั่วไหล encapsulation เมื่ออินเตอร์เฟซที่ไม่ใช่แต่ระดับการดำเนินการคือpublicpublic

// Given:
internal interface I { void M(); }

// Then explicit implementation correctly observes encapsulation of I:
// Both ((I)CExplicit).M and CExplicit.M are accessible only internally.
public class CExplicit: I { void I.M() { } }

// However, implicit implementation breaks encapsulation of I, because
// ((I)CImplicit).M is only accessible internally, while CImplicit.M is accessible publicly.
public class CImplicit: I { public void M() { } }

การรั่วไหลข้างต้นไม่สามารถหลีกเลี่ยงได้เพราะตามข้อกำหนดC # "สมาชิกส่วนต่อประสานทั้งหมดมีการเข้าถึงสาธารณะโดยปริยาย" เป็นผลให้การใช้งานโดยปริยายต้องให้publicinternalเข้าถึงแม้ว่าอินเตอร์เฟซที่ตัวเองเป็นเช่น

การใช้อินเตอร์เฟสโดยนัยใน C # นั้นสะดวกสบายอย่างยิ่ง ในทางปฏิบัติโปรแกรมเมอร์หลายคนใช้มันตลอดเวลา / ทุกที่โดยไม่ต้องพิจารณาเพิ่มเติม สิ่งนี้นำไปสู่พื้นผิวประเภทที่ยุ่งเหยิงที่สุดและรั่วไหลออกมาห่อหุ้มที่เลวร้ายที่สุด ภาษาอื่น ๆ เช่น F #, ไม่ได้อนุญาต

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