Giao diện C #. Triển khai ngầm so với thực hiện rõ ràng


632

Sự khác biệt trong việc thực hiện các giao diện ngầmrõ ràng trong C # là gì?

Khi nào bạn nên sử dụng ngầm và khi nào bạn nên sử dụng rõ ràng?

Có bất kỳ ưu và / hoặc khuyết điểm nào với người này hay người kia không?


Các hướng dẫn chính thức của Microsoft (từ Nguyên tắc thiết kế khung phiên bản đầu tiên ) nói rằng việc sử dụng các triển khai rõ ràng không được khuyến nghị , vì nó mang lại cho mã hành vi bất ngờ.

Tôi nghĩ rằng hướng dẫn này rất hợp lệ trong thời kỳ tiền IoC , khi bạn không chuyển mọi thứ xung quanh dưới dạng giao diện.

Bất cứ ai cũng có thể chạm vào khía cạnh đó?


Đọc toàn bộ bài viết về giao diện C #: Planetofcoders.com/c-interfaces
Gaurav Agrawal

Có, nên tránh các giao diện rõ ràng và cách tiếp cận chuyên nghiệp hơn sẽ triển khai ISP (Nguyên tắc phân tách giao diện) ở đây là một bài viết chi tiết trên cùng một codeproject.com/Articles/1000374/
Shivprasad Koirala

Câu trả lời:


492

Tiềm ẩn là khi bạn xác định giao diện của mình thông qua một thành viên trong lớp. Rõ ràng là khi bạn xác định các phương thức trong lớp của bạn trên giao diện. Tôi biết điều đó nghe có vẻ khó hiểu nhưng đây là điều tôi muốn nói: IList.CopyTosẽ được thực hiện ngầm như:

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

và rõ ràng là:

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

Sự khác biệt là việc triển khai ngầm cho phép bạn truy cập vào giao diện thông qua lớp bạn đã tạo bằng cách chuyển giao diện thành lớp đó và như chính giao diện. Triển khai rõ ràng cho phép bạn truy cập vào giao diện chỉ bằng cách chọn nó làm giao diện.

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

Tôi sử dụng rõ ràng chủ yếu để giữ cho việc thực hiện sạch sẽ, hoặc khi tôi cần hai lần thực hiện. Bất kể, tôi hiếm khi sử dụng nó.

Tôi chắc chắn có nhiều lý do để sử dụng / không sử dụng rõ ràng mà người khác sẽ đăng.

Xem bài tiếp theo trong chủ đề này cho lý do tuyệt vời đằng sau mỗi.


8
Tôi biết bài đăng này đã cũ nhưng tôi thấy nó rất hữu ích - một điều cần lưu ý nếu nó không rõ ràng bởi vì trong ví dụ này, bài viết ẩn có publictừ khóa ... nếu không bạn sẽ gặp lỗi
jharr100

CLR của Jeffrey Richter qua C # 4 ed ch 13 cho thấy một ví dụ về việc không cần truyền: struct struct someValueType: IComparable {private Int32 m_x; công khai Một số loạiValue (Int32 x) {m_x = x; } công khai Int32 So sánh (Một số loại khác) {...);} Int32 IComparable.CompareTo (Đối tượng khác) {return so sánhTo ((Một số loại khác)); }} void static void Main () {someValueType v = new someValueType (0); Đối tượng o = đối tượng mới (); Int32 n = v.CompareTo (v); // Không có quyền anh n = v.CompareTo (o); // lỗi thời gian biên dịch}
Andy Dent

1
Hôm nay tôi gặp phải một tình huống hiếm gặp BẮT BUỘC khi sử dụng giao diện rõ ràng: Một lớp có trường được tạo bởi nhà xây dựng giao diện tạo trường dưới dạng riêng tư (Xamarin nhắm mục tiêu iOS, sử dụng bảng phân cảnh iOS). Và một giao diện nơi nó có ý nghĩa để phơi bày lĩnh vực đó (công khai chỉ đọc). Tôi có thể đã thay đổi tên của getter trong giao diện, nhưng tên hiện có là tên hợp lý nhất cho đối tượng. Vì vậy, thay vào đó tôi đã thực hiện một cách rõ ràng đề cập đến lĩnh vực riêng : UISwitch IScoreRegPlayerViewCell.markerSwitch { get { return markerSwitch; } }.
ToolmakerSteve

1
Sự khủng khiếp của lập trình. Vâng, độc đáo được gỡ lỗi!
Lõi lỏng

1
@ToolmakerSteve một tình huống khác (khá phổ biến hơn) yêu cầu triển khai rõ ràng ít nhất một thành viên giao diện đang triển khai nhiều giao diện có các thành viên có cùng chữ ký nhưng các kiểu trả về khác nhau. Điều này có thể xảy ra vì giao diện thừa kế, vì nó không có IEnumerator<T>.Current, IEnumerable<T>.GetEnumerator()ISet<T>.Add(T). Điều này được đề cập trong một câu trả lời khác .
phoog

201

Định nghĩa ngầm định sẽ chỉ là thêm các phương thức / thuộc tính, v.v. được giao diện yêu cầu trực tiếp vào lớp làm phương thức công khai.

Định nghĩa rõ ràng buộc các thành viên chỉ được tiếp xúc khi bạn làm việc trực tiếp với giao diện chứ không phải triển khai cơ bản. Điều này được ưa thích trong hầu hết các trường hợp.

  1. Bằng cách làm việc trực tiếp với giao diện, bạn không thừa nhận và ghép mã của mình với triển khai cơ bản.
  2. Trong trường hợp bạn đã có, giả sử, Tên thuộc tính công khai trong mã của bạn và bạn muốn triển khai một giao diện cũng có thuộc tính Tên, thực hiện một cách rõ ràng sẽ giữ hai tên riêng biệt. Ngay cả khi họ đang làm điều tương tự, tôi vẫn ủy thác cuộc gọi rõ ràng cho thuộc tính Tên. Bạn không bao giờ biết, bạn có thể muốn thay đổi cách Tên hoạt động cho lớp bình thường và cách Tên, thuộc tính giao diện hoạt động sau này.
  3. Nếu bạn thực hiện một giao diện hoàn toàn thì lớp của bạn bây giờ sẽ phơi bày các hành vi mới chỉ có thể liên quan đến máy khách của giao diện và điều đó có nghĩa là bạn không giữ cho lớp của mình đủ ngắn gọn (ý kiến ​​của tôi).

2
Bạn làm cho một số điểm tốt ở đây. đặc biệt là A. tôi thường vượt qua các lớp học của mình như giao diện, nhưng tôi chưa bao giờ thực sự nghĩ về nó hình thành quan điểm đó.
mattlant

5
Tôi không chắc là mình đồng ý với điểm C. Một đối tượng Cat có thể triển khai IEizable nhưng Eat () là một phần cơ bản của sự việc. Sẽ có trường hợp bạn chỉ muốn gọi Eat () trên Cat khi bạn đang sử dụng đối tượng 'thô' thay vì qua giao diện IEizable, phải không?
Truyền thuyết Bước sóng

59
Tôi biết một số nơi Catthực sự IEatablekhông có sự phản đối.
Humberto

26
Tôi hoàn toàn không đồng ý với tất cả những điều trên, và sẽ nói rằng sử dụng các giao diện rõ ràng là công thức của thảm họa và không phải là OOP hay OOD theo định nghĩa của chúng (xem câu trả lời của tôi về đa hình)
Valentin Kuzub


68

Ngoài các câu trả lời xuất sắc đã được cung cấp, có một số trường hợp thực hiện rõ ràng là BẮT BUỘC để trình biên dịch có thể tìm ra những gì được yêu cầu. Hãy xem IEnumerable<T>như một ví dụ điển hình có thể sẽ xuất hiện khá thường xuyên.

Đây là một ví dụ:

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
}

Ở đây, IEnumerable<string>thực hiện IEnumerable, do đó chúng ta cần phải quá. Nhưng chờ đợi, cả phiên bản chung và phiên bản bình thường đều thực hiện các chức năng có cùng chữ ký phương thức (C # bỏ qua kiểu trả về cho việc này). Điều này là hoàn toàn hợp pháp và tốt đẹp. Làm thế nào để trình biên dịch giải quyết mà sử dụng? Nó buộc bạn chỉ có tối đa một định nghĩa ngầm, sau đó nó có thể giải quyết bất cứ điều gì nó cần.

I E.

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: Một phần nhỏ của định hướng trong định nghĩa rõ ràng cho IEnumerable hoạt động vì bên trong hàm, trình biên dịch biết rằng loại thực của biến là StringList và đó là cách nó giải quyết lệnh gọi hàm. Thực tế nhỏ để thực hiện một số lớp trừu tượng một số giao diện lõi .NET dường như đã tích lũy.


5
@Tassadaque: Sau 2 năm, tất cả những gì tôi có thể nói là "Câu hỏi hay". Tôi không biết, ngoại trừ có lẽ tôi đã sao chép mã đó từ một cái gì đó mà tôi đang làm việc ở đó abstract.
Matthew Scharley

4
@Tassadaque, bạn đúng, nhưng tôi nghĩ rằng quan điểm của bài viết của Matthew Scharley ở trên không bị mất.
funkymushroom 7/07/2016

35

Để báo Jeffrey Richter từ CLR thông qua C #
( EIMI nghĩa E xplicit tôi nterface M ethod tôi chi ệ n)

Điều cực kỳ quan trọng là bạn phải hiểu một số phân nhánh tồn tại khi sử dụng EIMIs. Và vì những phân nhánh này, bạn nên cố gắng tránh EIMI càng nhiều càng tốt. May mắn thay, giao diện chung giúp bạn tránh EIMIs khá nhiều. Nhưng có thể vẫn có lúc bạn sẽ cần sử dụng chúng (chẳng hạn như thực hiện hai phương thức giao diện có cùng tên và chữ ký). Dưới đây là những vấn đề lớn với EIMIs:

  • Không có tài liệu nào giải thích cách thức một loại cụ thể thực hiện phương pháp EIMI và không có hỗ trợ Microsoft Visual Studio IntelliSense .
  • Các trường hợp loại giá trị được đóng hộp khi chuyển sang giao diện.
  • Một EIMI không thể được gọi bởi một loại dẫn xuất.

Nếu bạn sử dụng tham chiếu giao diện, BẤT K chain chuỗi ảo nào có thể được thay thế rõ ràng bằng EIMI trên bất kỳ lớp dẫn xuất nào và khi một đối tượng thuộc loại đó được truyền tới giao diện, chuỗi ảo của bạn sẽ bị bỏ qua và việc triển khai rõ ràng được gọi. Đó là bất cứ điều gì nhưng đa hình.

EIMIs cũng có thể được sử dụng để ẩn các thành viên giao diện không được gõ mạnh khỏi các triển khai của Giao diện khung cơ bản như IEnumerable <T> để lớp của bạn không hiển thị trực tiếp phương thức được gõ mạnh, nhưng đúng về mặt cú pháp.


2
Việc thực hiện lại các giao diện, mặc dù hợp pháp, nói chung là không rõ ràng. Việc triển khai rõ ràng thường sẽ chuỗi thành một phương thức ảo trực tiếp hoặc thông qua logic gói nên được ràng buộc trên các lớp dẫn xuất. Mặc dù chắc chắn có thể sử dụng các giao diện theo cách thù địch với các quy ước OOP thích hợp, điều đó không có nghĩa là chúng không thể được sử dụng tốt hơn.
supercat

4
@Valentin EIMI và IEMI là viết tắt của từ gì?
Dzienny

Thực hiện phương pháp giao diện rõ ràng
scobi

5
-1 cho "Nói chung tôi thấy Giao diện là tính năng OOP Semi (tốt nhất), nó cung cấp tính kế thừa, nhưng nó không cung cấp tính đa hình thực sự." Tôi rất không đồng ý. Hoàn toàn ngược lại, các giao diện là tất cả về đa hình và không phải chủ yếu về kế thừa. Họ gán nhiều phân loại cho một loại. Tránh IEMI, khi bạn có thể và ủy thác, như @supercat đề xuất, khi bạn không thể. Đừng tránh giao diện.
Aluan Haddad

1
"Một EIMI không thể được gọi bởi một loại dẫn xuất." << CÁI GÌ? Đo không phải sự thật. Nếu tôi thực hiện rõ ràng một giao diện trên một loại, sau đó tôi xuất phát từ loại đó, tôi vẫn có thể sử dụng nó cho giao diện để gọi phương thức, chính xác như tôi sẽ phải thực hiện cho loại được triển khai trên đó. Vì vậy, không chắc chắn những gì bạn đang nói về. Ngay cả trong loại dẫn xuất, tôi chỉ có thể chuyển "cái này" sang giao diện được đề cập để tiếp cận phương thức được triển khai rõ ràng.
Triynko

34

Lý do số 1

Tôi có xu hướng sử dụng thực hiện giao diện rõ ràng khi tôi muốn làm nản lòng "lập trình để thực hiện một" ( nguyên tắc thiết kế từ mẫu thiết kế ).

Ví dụ: trong ứng dụng web dựa trên MVP :

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

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

Bây giờ một lớp khác (chẳng hạn như người trình bày ) ít có khả năng phụ thuộc vào việc triển khai StandardNavigator và nhiều khả năng phụ thuộc vào giao diện INavigator (vì việc triển khai sẽ cần được chuyển sang giao diện để sử dụng phương thức Redirect).

Lý do số 2

Một lý do khác mà tôi có thể đi với việc triển khai giao diện rõ ràng là để giữ sạch giao diện "mặc định" của một lớp. Ví dụ: nếu tôi đang phát triển một điều khiển máy chủ ASP.NET , tôi có thể muốn có hai giao diện:

  1. Giao diện chính của lớp, được sử dụng bởi các nhà phát triển trang web; và
  2. Giao diện "ẩn" được sử dụng bởi người trình bày mà tôi phát triển để xử lý logic của điều khiển

Một ví dụ đơn giản sau đây. Đó là một điều khiển hộp tổ hợp liệt kê khách hàng. Trong ví dụ này, nhà phát triển trang web không quan tâm đến việc điền danh sách; thay vào đó, họ chỉ muốn có thể chọn một khách hàng bằng GUID hoặc để có được GUID của khách hàng đã chọn. Người thuyết trình sẽ điền vào ô trên tải trang đầu tiên và người thuyết trình này được đóng gói bởi điều khiển.

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; }
}

Người trình bày điền vào nguồn dữ liệu và nhà phát triển trang web không bao giờ cần phải biết về sự tồn tại của nó.

Nhưng đó không phải là một khẩu súng thần công bạc

Tôi sẽ không khuyên bạn nên luôn sử dụng triển khai giao diện rõ ràng. Đó chỉ là hai ví dụ mà chúng có thể hữu ích.


19

Ngoài các lý do khác đã nêu, đây là tình huống trong đó một lớp đang thực hiện hai giao diện khác nhau có thuộc tính / phương thức có cùng tên và chữ ký.

/// <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
}

Mã này biên dịch và chạy OK, nhưng thuộc tính Title được chia sẻ.

Rõ ràng, chúng tôi muốn giá trị của Tiêu đề được trả về tùy thuộc vào việc chúng tôi coi Class1 là Sách hay Người. Đây là khi chúng ta có thể sử dụng giao diện rõ ràng.

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

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

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

Lưu ý rằng các định nghĩa giao diện rõ ràng được suy luận là Công khai - và do đó bạn không thể tuyên bố chúng là công khai (hoặc nói cách khác) một cách rõ ràng.

Cũng lưu ý rằng bạn vẫn có thể có phiên bản "dùng chung" (như được hiển thị ở trên), nhưng trong khi điều này là có thể, thì sự tồn tại của một tài sản như vậy là đáng nghi ngờ. Có lẽ nó có thể được sử dụng như một cách triển khai Tiêu đề mặc định - để mã hiện tại sẽ không phải sửa đổi để chuyển Class1 thành IBook hoặc IPerson.

Nếu bạn không xác định Tiêu đề "được chia sẻ" (ẩn), người tiêu dùng của Class1 phải truyền rõ ràng các phiên bản của Class1 sang IBook hoặc IPerson trước - nếu không, mã sẽ không được biên dịch.


16

Tôi sử dụng thực hiện giao diện rõ ràng hầu hết thời gian. Dưới đây là những lý do chính.

Tái cấu trúc là an toàn hơn

Khi thay đổi một giao diện, sẽ tốt hơn nếu trình biên dịch có thể kiểm tra nó. Điều này là khó khăn hơn với việc thực hiện ngầm.

Hai trường hợp phổ biến đến với tâm trí:

  • Thêm một chức năng vào một giao diện, trong đó một lớp hiện có thực hiện giao diện này đã có một phương thức có cùng chữ ký với giao diện mới . Điều này có thể dẫn đến hành vi bất ngờ, và đã cắn tôi nhiều lần. Thật khó để "nhìn thấy" khi gỡ lỗi vì chức năng đó có thể không nằm trong các phương thức giao diện khác trong tệp (vấn đề tự ghi lại được đề cập dưới đây).

  • Loại bỏ một chức năng từ một giao diện . Các phương thức được triển khai ngầm sẽ đột nhiên là mã chết, nhưng các phương thức được triển khai rõ ràng sẽ bị bắt bởi lỗi biên dịch. Ngay cả khi mã chết là tốt để giữ xung quanh, tôi muốn bị buộc phải xem lại và quảng bá nó.

Thật không may là C # không có từ khóa buộc chúng ta phải đánh dấu một phương thức là một triển khai ngầm, vì vậy trình biên dịch có thể thực hiện các kiểm tra bổ sung. Các phương thức ảo không có một trong các vấn đề nêu trên do yêu cầu sử dụng 'ghi đè' và 'mới'.

Lưu ý: đối với các giao diện cố định hoặc hiếm khi thay đổi (thường là từ API của nhà cung cấp), đây không phải là vấn đề. Tuy nhiên, đối với các giao diện của riêng tôi, tôi không thể dự đoán khi nào / chúng sẽ thay đổi như thế nào.

Đó là tài liệu tự

Nếu tôi thấy 'công khai bool Execute ()' trong một lớp, nó sẽ mất thêm công việc để tìm ra rằng đó là một phần của giao diện. Ai đó có thể sẽ phải bình luận nó nói như vậy, hoặc đặt nó trong một nhóm các triển khai giao diện khác, tất cả dưới một khu vực hoặc nhóm nhận xét nói "triển khai ITask". Tất nhiên, điều đó chỉ hoạt động nếu tiêu đề nhóm không ở ngoài màn hình ..

Trong khi đó: 'bool ITask.Execute ()' rõ ràng và không mơ hồ.

Phân tách rõ ràng thực hiện giao diện

Tôi nghĩ rằng các giao diện là 'công khai' hơn các phương thức công cộng vì chúng được chế tạo để chỉ lộ ra một chút diện tích bề mặt của loại bê tông. Họ giảm loại thành khả năng, hành vi, tập hợp các đặc điểm, v.v. Và trong quá trình thực hiện, tôi nghĩ việc giữ sự tách biệt này là hữu ích.

Khi tôi đang xem qua mã của một lớp, khi tôi bắt gặp các triển khai giao diện rõ ràng, não tôi chuyển sang chế độ "hợp đồng mã". Thông thường các triển khai này chỉ đơn giản chuyển tiếp sang các phương thức khác, nhưng đôi khi chúng sẽ thực hiện kiểm tra trạng thái / param bổ sung, chuyển đổi các tham số đến để phù hợp hơn với các yêu cầu nội bộ hoặc thậm chí dịch cho các mục đích phiên bản (nghĩa là nhiều thế hệ giao diện chuyển sang triển khai chung).

(Tôi nhận ra rằng công chúng cũng là hợp đồng mã, nhưng giao diện mạnh hơn nhiều, đặc biệt là trong một cơ sở mã điều khiển giao diện nơi sử dụng trực tiếp các loại cụ thể thường là dấu hiệu của mã chỉ nội bộ.)

Liên quan: Lý do 2 ở trên của Jon .

Và như thế

Cộng với những lợi thế đã được đề cập trong các câu trả lời khác ở đây:

Các vấn đề

Đó không phải là tất cả niềm vui và hạnh phúc. Có một số trường hợp tôi dính vào ẩn ý:

  • Các loại giá trị, bởi vì điều đó sẽ đòi hỏi quyền anh và sự hoàn hảo thấp hơn. Đây không phải là một quy tắc nghiêm ngặt và phụ thuộc vào giao diện và cách sử dụng. Có thể cạnh tranh? Ngụ ý. Có thể khử được? Có lẽ rõ ràng.
  • Các giao diện hệ thống tầm thường có các phương thức thường được gọi trực tiếp (như IDisftime.Dispose).

Ngoài ra, có thể là một khó khăn khi thực hiện việc đúc khi bạn thực tế có loại cụ thể và muốn gọi một phương thức giao diện rõ ràng. Tôi giải quyết vấn đề này theo một trong hai cách:

  1. Thêm công chúng và có các phương thức giao diện chuyển tiếp cho họ để thực hiện. Thông thường xảy ra với các giao diện đơn giản hơn khi làm việc nội bộ.
  2. (Phương pháp ưa thích của tôi) Thêm một public IMyInterface I { get { return this; } }(cần được nội tuyến) và gọi foo.I.InterfaceMethod(). Nếu nhiều giao diện cần khả năng này, hãy mở rộng tên ngoài tôi (theo kinh nghiệm của tôi, hiếm khi tôi có nhu cầu này).

8

Nếu bạn triển khai rõ ràng, bạn sẽ chỉ có thể tham chiếu các thành viên giao diện thông qua một tham chiếu thuộc loại giao diện. Một tham chiếu là kiểu của lớp triển khai sẽ không làm lộ ra các thành viên giao diện đó.

Nếu lớp triển khai của bạn không công khai, ngoại trừ phương thức được sử dụng để tạo lớp (có thể là nhà máy hoặc bộ chứa IoC ) và ngoại trừ các phương thức giao diện (tất nhiên), thì tôi không thấy bất kỳ lợi thế nào để thực hiện rõ ràng giao diện.

Mặt khác, các giao diện triển khai rõ ràng đảm bảo rằng các tham chiếu đến lớp triển khai cụ thể của bạn không được sử dụng, cho phép bạn thay đổi triển khai đó sau. "Chắc chắn", tôi cho rằng, là "lợi thế". Một triển khai thực hiện tốt có thể thực hiện điều này mà không cần thực hiện rõ ràng.

Theo tôi, nhược điểm là bạn sẽ thấy mình truyền các kiểu đến / từ giao diện trong mã triển khai có quyền truy cập vào các thành viên không công khai.

Giống như nhiều thứ, ưu điểm là nhược điểm (và ngược lại). Các giao diện triển khai rõ ràng sẽ đảm bảo rằng mã triển khai lớp cụ thể của bạn không bị lộ.


1
Bill trả lời tốt đẹp. Các câu trả lời khác rất hay nhưng bạn đã cung cấp một số quan điểm khách quan bổ sung (trên ý kiến ​​của bạn) giúp tôi dễ nắm bắt hơn. Giống như hầu hết mọi thứ, có những ưu và nhược điểm với việc triển khai ngầm hoặc rõ ràng, do đó bạn chỉ cần sử dụng cách tốt nhất cho kịch bản cụ thể hoặc trường hợp sử dụng. Tôi muốn nói rằng những người cố gắng tìm hiểu rõ hơn sẽ có lợi khi đọc câu trả lời của bạn.
Daniel Eagle

6

Việc triển khai giao diện ngầm là nơi bạn có một phương thức có cùng chữ ký của giao diện.

Việc triển khai giao diện rõ ràng là nơi bạn khai báo rõ ràng giao diện mà phương thức đó thuộc về.

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: triển khai giao diện ngầm và rõ ràng


6

Mỗi thành viên lớp thực hiện một giao diện xuất một khai báo tương tự về mặt ngữ nghĩa với cách viết khai báo giao diện VB.NET, ví dụ:

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

Mặc dù tên của thành viên lớp thường sẽ trùng với tên của thành viên giao diện và thành viên lớp thường sẽ được công khai, nhưng những điều đó không bắt buộc. Người ta cũng có thể tuyên bố:

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

Trong trường hợp đó, lớp và các dẫn xuất của nó sẽ được phép truy cập vào một thành viên của lớp bằng cách sử dụng tên đó IFoo_Foo, nhưng thế giới bên ngoài sẽ chỉ có thể truy cập vào thành viên cụ thể đó bằng cách truyền tới IFoo. Cách tiếp cận như vậy thường tốt trong trường hợp phương thức giao diện sẽ có hành vi được chỉ định trên tất cả các triển khai, nhưng hành vi hữu ích chỉ trên một số [ví dụ: hành vi được chỉ định cho IList<T>.Addphương pháp của bộ sưu tập chỉ đọc là ném NotSupportedException]. Thật không may, cách thích hợp duy nhất để thực hiện giao diện trong C # là:

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

Không đẹp bằng.


2

Một sử dụng quan trọng của việc thực hiện giao diện rõ ràng là khi cần thực hiện các giao diện với khả năng hiển thị hỗn hợp .

Vấn đề và giải pháp được giải thích rõ trong bài viết Giao diện nội bộ C # .

Ví dụ: nếu bạn muốn bảo vệ rò rỉ các đối tượng giữa các lớp ứng dụng, kỹ thuật này cho phép bạn chỉ định mức độ hiển thị khác nhau của các thành viên có thể gây rò rỉ.


2

Các câu trả lời trước giải thích lý do tại sao thực hiện giao diện rõ ràng trong C # có thể thích hợp hơn (vì lý do chính thức). Tuy nhiên, có một tình huống bắt buộc phải thực hiện rõ ràng : Để tránh rò rỉ việc đóng gói khi giao diện không phải public, nhưng lớp triển khai là public.

// 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() { } }

Việc rò rỉ ở trên là không thể tránh khỏi bởi vì, theo đặc tả của C # , "Tất cả các thành viên giao diện đều có quyền truy cập công khai." Kết quả là, việc triển khai ngầm cũng phải cấp publicquyền truy cập, ngay cả khi chính giao diện là ví dụ internal.

Triển khai giao diện ngầm trong C # là một thuận tiện lớn. Trong thực tế, nhiều lập trình viên sử dụng nó mọi lúc / mọi nơi mà không cần xem xét thêm. Điều này dẫn đến các bề mặt lộn xộn ở mức tốt nhất và đóng gói bị rò rỉ ở mức tồi tệ nhất. Các ngôn ngữ khác, chẳng hạn như F #, thậm chí không cho phép nó .

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.