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?
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:
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()
{
}
}
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>
và IComparable
thường tốt hơn là ẩn IComparable
quá 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ư IConvertible
vậ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ó.
string
khoả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 string
nó vì vậy họ để nó như cũ với các biện pháp bảo vệ tại chỗ.
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 ICloneable
giao 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 Clone
phươ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 ICloneable
rõ 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();
}
}
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 protected
phươ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.
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ó Clone
phươ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 IAutomobileFactory
có thể có một Manufacture
phương thức trả về một Automobile
, nhưng a FordExplorerFactory
, phương thức triển khai IAutomobileFactory
, có thể có Manufacture
phương thức của nó trả về a FordExplorer
(dẫn xuất từ Automobile
). Mã biết rằng nó có FordExplorerFactory
thể sử dụng FordExplorer
các thuộc tính cụ thể trên một đối tượng được trả về bởi a FordExplorerFactory
mà 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 IAutomobileFactory
sẽ đơn giản giải quyết việc trả về của nó dưới dạng một Automobile
.
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
}
public class Animal : Cat, Dog
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à File
lớp của bạn có thể triển khai IDisposable
rõ 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ữ).
Dispose
là 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
.
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ã.
#region
là 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.
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>
có 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();
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
Đâ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();
}
}
}