Tại sao phải triển khai giao diện một cách rõ ràng?


122

Vì vậy, chính xác thì một trường hợp sử dụng tốt để triển khai một giao diện rõ ràng là gì?

Có phải chỉ để những người sử dụng lớp không phải xem tất cả các phương thức / thuộc tính đó trong intellisense không?

Câu trả lời:


146

Nếu bạn triển khai hai giao diện, cả hai đều có cùng một phương pháp và các triển khai khác nhau, thì bạn phải triển khai rõ ràng.

public interface IDoItFast
{
    void Go();
}
public interface IDoItSlow
{
    void Go();
}
public class JustDoIt : IDoItFast, IDoItSlow
{
    void IDoItFast.Go()
    {
    }

    void IDoItSlow.Go()
    {
    }
}

Có chính xác đây là một trường hợp mà EIMI giải quyết. Và các điểm khác được bao phủ bởi câu trả lời "Michael B".
crypto

10
Ví dụ xuất sắc. Thích giao diện / tên lớp! :-)
Brian Rogers

11
Tôi không thích nó, hai phương thức có cùng một chữ ký trong một lớp làm những việc rất khác nhau? Đây là thứ cực kỳ nguy hiểm và rất có thể sẽ gây tàn phá cho bất kỳ sự phát triển lớn nào. Nếu bạn có mã như thế này, tôi muốn nói rằng phân tích và thiết kế của bạn là phù hợp.
Mick

4
@Mike Các giao diện có thể thuộc về một số API hoặc hai API khác nhau. Có thể tình yêu hơi phóng đại ở đây nhưng ít nhất tôi cũng vui vì việc triển khai rõ ràng có sẵn.
TobiMcNamobi

@BrianRogers Và cả tên phương thức nữa ;-)
Sнаđошƒаӽ

66

Rất hữu ích để ẩn thành viên không được ưu tiên. Ví dụ: nếu bạn triển khai cả hai IComparable<T>IComparablethường tốt hơn là ẩn IComparablequá tải để không tạo cho mọi người ấn tượng rằng bạn có thể so sánh các đối tượng thuộc các loại khác nhau. Tương tự, một số giao diện không tuân thủ CLS, như IConvertiblevậy, vì vậy nếu bạn không triển khai giao diện một cách rõ ràng, người dùng cuối của các ngôn ngữ yêu cầu tuân thủ CLS không thể sử dụng đối tượng của bạn. (Sẽ rất tai hại nếu những người triển khai BCL không ẩn các thành viên IConvertible của các nguyên thủy :))

Một lưu ý thú vị khác là thông thường sử dụng một cấu trúc như vậy có nghĩa là cấu trúc triển khai rõ ràng một giao diện chỉ có thể gọi chúng bằng cách chọn kiểu giao diện. Bạn có thể giải quyết vấn đề này bằng cách sử dụng các ràng buộc chung ::

void SomeMethod<T>(T obj) where T:IConvertible

Sẽ không đóng hộp một int khi bạn chuyển một int vào nó.


1
Bạn có một lỗi đánh máy trong ràng buộc của bạn. Để làm rõ, đoạn mã trên hoạt động. Nó cần có trong phần khai báo ban đầu của chữ ký phương thức trong Giao diện. Bài viết ban đầu không chỉ rõ điều này. Ngoài ra, định dạng thích hợp là "void SomeMehtod <T> (T obj) where T: IConvertible. Lưu ý, có thêm dấu hai chấm giữa") "và" where "không nên ở đó. Vẫn, +1 để sử dụng thông minh chung chung để tránh quyền anh đắt tiền.
Zack Jannsen

1
Xin chào Michael B. Vậy tại sao trong quá trình triển khai chuỗi trong .NET lại có sự triển khai công khai của IComp so sánh được: public int CompareTo (Giá trị đối tượng) {if (value == null) {return 1; } if (! (giá trị là String)) {throw new ArgumentException (Environment.GetResourceString ("Arg_MustBeString")); } trả về giá trị String.Compare (this, (String), StringComparison.CurrentCulture); } Cảm ơn!
zzfima

stringkhoảng trước khi thuốc generic và cách làm này đã trở nên thịnh hành. Khi .net 2 xuất hiện, họ không muốn phá vỡ giao diện công khai của stringnó vì vậy họ để nó như cũ với các biện pháp bảo vệ tại chỗ.
Michael B

37

Một số lý do bổ sung để triển khai giao diện một cách rõ ràng:

khả năng tương thích ngược : Trong trường hợp ICloneablegiao diện thay đổi, các thành viên lớp phương thức triển khai không phải thay đổi chữ ký phương thức của chúng.

mã sạch hơn : sẽ có lỗi trình biên dịch nếu Clonephương thức bị xóa khỏi ICloneable, tuy nhiên nếu bạn triển khai phương pháp này một cách ngầm định, bạn có thể kết thúc với các phương thức công khai 'mồ côi' không được sử dụng

đánh máy mạnh : Để minh họa câu chuyện của supercat bằng một ví dụ, đây sẽ là mã mẫu ưa thích của tôi, việc triển khai ICloneablerõ ràng cho phép Clone()được nhập mạnh khi bạn gọi nó trực tiếp dưới dạngMyObject thành viên cá thể:

public class MyObject : ICloneable
{
  public MyObject Clone()
  {
    // my cloning logic;  
  }

  object ICloneable.Clone()
  {
    return this.Clone();
  }
}

Đối với cái đó, tôi muốn tôi thích hơn interface ICloneable<out T> { T Clone(); T self {get;} }. Lưu ý rằng cố tình không có ICloneable<T>ràng buộc nào đối với T. Mặc dù một đối tượng thường chỉ có thể được sao chép an toàn nếu cơ sở của nó có thể được, người ta có thể muốn dẫn xuất từ ​​một lớp cơ sở có thể được sao chép an toàn một đối tượng của một lớp mà không thể. Để cho phép điều đó, tôi khuyên bạn không nên để các lớp có thể kế thừa hiển thị một phương thức sao chép công khai. Thay vào đó, các lớp có thể kế thừa với một protectedphương thức nhân bản và các lớp được niêm phong dẫn xuất từ ​​chúng và hiển thị nhân bản công khai.
supercat

Chắc chắn điều đó sẽ đẹp hơn, ngoại trừ không có phiên bản đồng biến của ICloneable trong BCL, vì vậy bạn phải tạo một phiên bản đúng không?
Wiebe Tijsma

Tất cả 3 ví dụ phụ thuộc vào các tình huống không mong muốn và các phương pháp hay nhất về ngắt giao diện.
MickyD

13

Một kỹ thuật hữu ích khác là việc triển khai công khai một phương thức của một hàm trả về một giá trị cụ thể hơn giá trị được chỉ định trong giao diện.

Ví dụ, một đối tượng có thể triển khai ICloneable, nhưng vẫn có Clonephương thức hiển thị công khai trả về kiểu riêng của nó.

Tương tự như vậy, an IAutomobileFactorycó thể có một Manufacturephương thức trả về một Automobile, nhưng a FordExplorerFactory, phương thức triển khai IAutomobileFactory, có thể có Manufacturephương thức của nó trả về a FordExplorer(dẫn xuất từ Automobile). Mã biết rằng nó có FordExplorerFactorythể sử dụng FordExplorercác thuộc tính cụ thể trên một đối tượng được trả về bởi a FordExplorerFactorymà không cần phải đánh máy, trong khi mã chỉ đơn thuần biết rằng nó có một số kiểu IAutomobileFactorysẽ đơn giản giải quyết việc trả về của nó dưới dạng một Automobile.


2
1 ... Đây sẽ là sử dụng ưa thích của tôi về việc triển khai giao diện rõ ràng, mặc dù một mẫu mã nhỏ sẽ có thể là một chút rõ ràng hơn câu chuyện này :)
Wiebe Tijsma

7

Nó cũng hữu ích khi bạn có hai giao diện có cùng tên thành viên và chữ ký, nhưng muốn thay đổi hành vi của nó tùy thuộc vào cách nó được sử dụng. (Tôi không khuyên bạn nên viết mã như thế này):

interface Cat
{
    string Name {get;}
}

interface Dog
{
    string Name{get;}
}

public class Animal : Cat, Dog
{
    string Cat.Name
    {
        get
        {
            return "Cat";
        }
    }

    string Dog.Name
    {
        get
        {
            return "Dog";
        }
    }
}
static void Main(string[] args)
{
    Animal animal = new Animal();
    Cat cat = animal; //Note the use of the same instance of Animal. All we are doing is picking which interface implementation we want to use.
    Dog dog = animal;
    Console.WriteLine(cat.Name); //Prints Cat
    Console.WriteLine(dog.Name); //Prints Dog
}

60
kỳ lạ OO ví dụ liên quan mà tôi từng thấy:public class Animal : Cat, Dog
mbx

38
@mbx: Nếu Animal cũng thực hiện Parrot, nó sẽ là động vật biến hình Polly.
RenniePet 13/09/13

3
Tôi nhớ một nhân vật hoạt hình đó là một Cát ở một đầu và một con chó ở đầu kia ;-)
George Birbilis

2
đã có một bộ phim truyền hình trong '80 của .. 'Manimal' .. nơi một người đàn ông có thể biến thành một .. oh nevermind
bkwdesign

Morkies trông giống mèo, và cũng giống mèo ninja.
samis

6

Nó có thể giữ cho giao diện công khai sạch hơn để triển khai một giao diện một cách rõ ràng, tức là Filelớp của bạn có thể triển khai IDisposablerõ ràng và cung cấp một phương thức công khai Close()có thể có ý nghĩa hơn đối với người tiêu dùng Dispose().

F # chỉ cung cấp triển khai giao diện rõ ràng, vì vậy bạn luôn phải truyền đến giao diện cụ thể để truy cập chức năng của nó, điều này tạo ra việc sử dụng giao diện rất rõ ràng (không có ý định chơi chữ).


Tôi nghĩ rằng hầu hết các phiên bản VB cũng chỉ hỗ trợ các định nghĩa giao diện rõ ràng.
Gabe

1
@Gabe - nó tinh tế hơn so với VB - việc đặt tên và khả năng truy cập của các thành viên triển khai giao diện tách biệt với việc chỉ ra rằng họ là một phần của việc triển khai. Vì vậy, trong VB, và nhìn vào câu trả lời của @ Iain (câu trả lời hàng đầu hiện tại), bạn có thể triển khai IDoItFast và IDoItSlow với các thành viên công khai "GoFast" và "GoSlow", tương ứng.
Damien_The_Un Believer

2
Tôi không thích ví dụ cụ thể của bạn (IMHO, những thứ duy nhất nên ẩn Disposelà những thứ sẽ không bao giờ yêu cầu dọn dẹp); một ví dụ tốt hơn sẽ là một cái gì đó giống như triển khai của bộ sưu tập bất biến IList<T>.Add.
supercat

5

Nếu bạn có giao diện nội bộ và bạn không muốn triển khai công khai các thành viên trên lớp của mình, bạn sẽ triển khai chúng một cách rõ ràng. Triển khai ngầm được yêu cầu công khai.


Ok, điều đó giải thích tại sao dự án sẽ không biên dịch với một triển khai ngầm định.
pauloya

4

Một lý do khác cho việc triển khai rõ ràng là vì khả năng bảo trì .

Khi một lớp "bận" - vâng, điều đó xảy ra, tất cả chúng ta không có điều kiện để cấu trúc lại mã của các thành viên khác trong nhóm - khi đó, việc triển khai rõ ràng sẽ làm rõ rằng một phương thức ở đó để đáp ứng hợp đồng giao diện.

Vì vậy, nó cải thiện "khả năng đọc" của mã.


IMHO, điều quan trọng hơn là quyết định xem lớp có nên hay không nên hiển thị phương thức cho các máy khách của nó. Điều đó dẫn đến việc nên rõ ràng hay ngầm hiểu. Để ghi lại rằng một số phương thức thuộc về nhau, trong trường hợp này là vì chúng thỏa mãn một hợp đồng - nghĩa #regionlà dùng cho, với một chuỗi tiêu đề thích hợp. Và một nhận xét về phương pháp.
ToolmakerSteve

1

Một ví dụ khác được đưa ra bởi System.Collections.Immutable , trong đó các tác giả đã chọn sử dụng kỹ thuật để duy trì một API quen thuộc cho các loại bộ sưu tập trong khi loại bỏ các phần của giao diện không có ý nghĩa đối với các loại mới của chúng.

Cụ thể, ImmutableList<T>dụng cụ IList<T>và do đó ICollection<T>( theo thứ tự để cho phép ImmutableList<T>được sử dụng dễ dàng hơn với mã di sản), nhưng void ICollection<T>.Add(T item)làm cho không có ý nghĩa đối với một ImmutableList<T>: kể từ khi thêm một phần tử vào một danh sách bất biến không phải thay đổi danh sách hiện có, ImmutableList<T>cũng xuất phát từ IImmutableList<T>IImmutableList<T> Add(T item)thể được sử dụng cho danh sách bất biến.

Do đó, trong trường hợp của Add, các triển khai ImmutableList<T>cuối cùng trông như sau:

public ImmutableList<T> Add(T item)
{
    // Create a new list with the added item
}

IImmutableList<T> IImmutableList<T>.Add(T value) => this.Add(value);

void ICollection<T>.Add(T item) => throw new NotSupportedException();

int IList.Add(object value) => throw new NotSupportedException();

0

Trong trường hợp các giao diện được xác định rõ ràng, tất cả các phương thức đều tự động ở chế độ riêng tư, bạn không thể công khai công cụ sửa đổi quyền truy cập cho chúng. Giả sử:

interface Iphone{

   void Money();

}

interface Ipen{

   void Price();
}


class Demo : Iphone, Ipen{

  void Iphone.Money(){    //it is private you can't give public               

      Console.WriteLine("You have no money");
  }

  void Ipen.Price(){    //it is private you can't give public

      Console.WriteLine("You have to paid 3$");
  }

}


// So you have to cast to call the method


    class Program
    {
        static void Main(string[] args)
        {
            Demo d = new Demo();

            Iphone i1 = (Iphone)d;

            i1.Money();

            ((Ipen)i1).Price();

            Console.ReadKey();
        }
    }

  // You can't call methods by direct class object

1
"tất cả các phương thức đều tự động ở chế độ riêng tư" - điều này không đúng về mặt kỹ thuật, b / c nếu chúng thực sự là riêng tư thì chúng sẽ không thể gọi được, truyền hoặc không.
samis

0

Đây là cách chúng ta có thể tạo Giao diện rõ ràng: Nếu chúng ta có 2 giao diện và cả hai giao diện đều có cùng một phương thức và một lớp đơn kế thừa 2 giao diện này, vì vậy khi chúng ta gọi một phương thức giao diện, trình biên dịch sẽ nhầm lẫn phương thức nào được gọi, vì vậy chúng ta có thể quản lý vấn đề này bằng Giao diện rõ ràng. Đây là một ví dụ tôi đã đưa ra bên dưới.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace oops3
{
    interface I5
    {
        void getdata();    
    }
    interface I6
    {
        void getdata();    
    }

    class MyClass:I5,I6
    {
        void I5.getdata()
        {
           Console.WriteLine("I5 getdata called");
        }
        void I6.getdata()
        {
            Console.WriteLine("I6 getdata called");
        }
        static void Main(string[] args)
        {
            MyClass obj = new MyClass();
            ((I5)obj).getdata();                     

            Console.ReadLine();    
        }
    }
}
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.