Người ta vấp phải cụm từ này khi đọc về các mẫu thiết kế.
Nhưng tôi không hiểu điều đó, ai đó có thể giải thích điều này cho tôi không?
Người ta vấp phải cụm từ này khi đọc về các mẫu thiết kế.
Nhưng tôi không hiểu điều đó, ai đó có thể giải thích điều này cho tôi không?
Câu trả lời:
Các giao diện chỉ là hợp đồng hoặc chữ ký và họ không biết gì về việc triển khai.
Mã hóa đối với phương tiện giao diện, mã máy khách luôn giữ một đối tượng Giao diện được cung cấp bởi một nhà máy. Bất kỳ trường hợp nào được trả về bởi nhà máy sẽ thuộc loại Giao diện mà bất kỳ lớp ứng cử viên nào của nhà máy phải thực hiện. Bằng cách này, chương trình máy khách không lo lắng về việc triển khai và chữ ký giao diện xác định tất cả các hoạt động có thể được thực hiện. Điều này có thể được sử dụng để thay đổi hành vi của một chương trình trong thời gian chạy. Nó cũng giúp bạn viết các chương trình tốt hơn từ quan điểm bảo trì.
Đây là một ví dụ cơ bản cho bạn.
public enum Language
{
English, German, Spanish
}
public class SpeakerFactory
{
public static ISpeaker CreateSpeaker(Language language)
{
switch (language)
{
case Language.English:
return new EnglishSpeaker();
case Language.German:
return new GermanSpeaker();
case Language.Spanish:
return new SpanishSpeaker();
default:
throw new ApplicationException("No speaker can speak such language");
}
}
}
[STAThread]
static void Main()
{
//This is your client code.
ISpeaker speaker = SpeakerFactory.CreateSpeaker(Language.English);
speaker.Speak();
Console.ReadLine();
}
public interface ISpeaker
{
void Speak();
}
public class EnglishSpeaker : ISpeaker
{
public EnglishSpeaker() { }
#region ISpeaker Members
public void Speak()
{
Console.WriteLine("I speak English.");
}
#endregion
}
public class GermanSpeaker : ISpeaker
{
public GermanSpeaker() { }
#region ISpeaker Members
public void Speak()
{
Console.WriteLine("I speak German.");
}
#endregion
}
public class SpanishSpeaker : ISpeaker
{
public SpanishSpeaker() { }
#region ISpeaker Members
public void Speak()
{
Console.WriteLine("I speak Spanish.");
}
#endregion
}
Đây chỉ là một ví dụ cơ bản và giải thích thực tế về nguyên tắc nằm ngoài phạm vi của câu trả lời này.
Tôi đã cập nhật ví dụ trên và thêm một Speaker
lớp cơ sở trừu tượng . Trong bản cập nhật này, tôi đã thêm một tính năng cho tất cả các Loa vào "SayHello". Tất cả các diễn giả nói "Xin chào thế giới". Vì vậy, đó là một tính năng phổ biến với chức năng tương tự. Hãy tham khảo các sơ đồ lớp và bạn sẽ thấy rằng Speaker
lớp trừu tượng thực hiện ISpeaker
giao diện và đánh dấu Speak()
như trừu tượng mà phương tiện mà mỗi thực hiện loa có trách nhiệm thực hiện các Speak()
phương pháp kể từ khi nó thay đổi từ Speaker
tới Speaker
. Nhưng tất cả các diễn giả đều nói "Xin chào" nhất trí. Vì vậy, trong lớp Loa trừu tượng, chúng tôi định nghĩa một phương thức có nội dung "Hello World" và mỗi Speaker
triển khai sẽ rút ra SayHello()
phương thức.
Hãy xem xét một trường hợp SpanishSpeaker
không thể nói Xin chào vì vậy trong trường hợp đó, bạn có thể ghi đè SayHello()
phương thức cho Người nói tiếng Tây Ban Nha và đưa ra ngoại lệ phù hợp.
Xin lưu ý rằng, chúng tôi chưa thực hiện bất kỳ thay đổi nào đối với Giao diện ISpeaker. Và mã máy khách và LoaFactory cũng không bị ảnh hưởng không thay đổi. Và đây là những gì chúng ta đạt được bằng Lập trình-Giao diện .
Và chúng ta có thể đạt được hành vi này bằng cách thêm một loa trừu tượng cơ sở và một số sửa đổi nhỏ trong mỗi lần thực hiện, do đó làm cho chương trình gốc không thay đổi. Đây là một tính năng mong muốn của bất kỳ ứng dụng nào và nó làm cho ứng dụng của bạn dễ dàng bảo trì.
public enum Language
{
English, German, Spanish
}
public class SpeakerFactory
{
public static ISpeaker CreateSpeaker(Language language)
{
switch (language)
{
case Language.English:
return new EnglishSpeaker();
case Language.German:
return new GermanSpeaker();
case Language.Spanish:
return new SpanishSpeaker();
default:
throw new ApplicationException("No speaker can speak such language");
}
}
}
class Program
{
[STAThread]
static void Main()
{
//This is your client code.
ISpeaker speaker = SpeakerFactory.CreateSpeaker(Language.English);
speaker.Speak();
Console.ReadLine();
}
}
public interface ISpeaker
{
void Speak();
}
public abstract class Speaker : ISpeaker
{
#region ISpeaker Members
public abstract void Speak();
public virtual void SayHello()
{
Console.WriteLine("Hello world.");
}
#endregion
}
public class EnglishSpeaker : Speaker
{
public EnglishSpeaker() { }
#region ISpeaker Members
public override void Speak()
{
this.SayHello();
Console.WriteLine("I speak English.");
}
#endregion
}
public class GermanSpeaker : Speaker
{
public GermanSpeaker() { }
#region ISpeaker Members
public override void Speak()
{
Console.WriteLine("I speak German.");
this.SayHello();
}
#endregion
}
public class SpanishSpeaker : Speaker
{
public SpanishSpeaker() { }
#region ISpeaker Members
public override void Speak()
{
Console.WriteLine("I speak Spanish.");
}
public override void SayHello()
{
throw new ApplicationException("I cannot say Hello World.");
}
#endregion
}
List
như một loại, bạn vẫn có thể cho rằng truy cập ngẫu nhiên nhanh bằng cách gọi liên tục get(i)
.
Hãy nghĩ về một giao diện như một hợp đồng giữa một đối tượng và khách hàng của nó. Đó là giao diện chỉ định những điều mà một đối tượng có thể làm và chữ ký để truy cập vào những thứ đó.
Thực hiện là những hành vi thực tế. Nói ví dụ bạn có một phương thức sort (). Bạn có thể triển khai QuickSort hoặc MergeSort. Điều đó không quan trọng đối với việc gọi mã máy khách miễn là giao diện không thay đổi.
Các thư viện như API Java và .NET Framework sử dụng nhiều giao diện vì hàng triệu lập trình viên sử dụng các đối tượng được cung cấp. Người tạo ra các thư viện này phải rất cẩn thận rằng họ không thay đổi giao diện thành các lớp trong các thư viện này vì nó sẽ ảnh hưởng đến tất cả các lập trình viên sử dụng thư viện. Mặt khác, họ có thể thay đổi việc thực hiện bao nhiêu tùy thích.
Nếu, là một lập trình viên, bạn mã chống lại việc thực hiện thì ngay khi nó thay đổi, mã của bạn sẽ ngừng hoạt động. Vì vậy, hãy nghĩ về những lợi ích của giao diện theo cách này:
Điều đó có nghĩa là bạn nên cố gắng viết mã của mình để nó sử dụng một sự trừu tượng hóa (lớp trừu tượng hoặc giao diện) thay vì thực hiện trực tiếp.
Thông thường việc triển khai được đưa vào mã của bạn thông qua hàm tạo hoặc gọi phương thức. Vì vậy, mã của bạn biết về giao diện hoặc lớp trừu tượng và có thể gọi bất cứ điều gì được xác định trong hợp đồng này. Khi một đối tượng thực tế (triển khai lớp giao diện / lớp trừu tượng) được sử dụng, các cuộc gọi đang hoạt động trên đối tượng.
Đây là một tập hợp con của Liskov Substitution Principle
(LSP), L của các SOLID
nguyên tắc.
Một ví dụ trong .NET sẽ là mã bằng IList
thay vì List
hoặc Dictionary
, vì vậy bạn có thể sử dụng bất kỳ lớp nào thực hiện IList
thay thế cho nhau trong mã của bạn:
// myList can be _any_ object that implements IList
public int GetListCount(IList myList)
{
// Do anything that IList supports
return myList.Count();
}
Một ví dụ khác từ Thư viện lớp cơ sở (BCL) là ProviderBase
lớp trừu tượng - điều này cung cấp một số cơ sở hạ tầng và điều quan trọng là tất cả các triển khai của nhà cung cấp có thể được sử dụng thay thế cho nhau nếu bạn mã hóa nó.
Nếu bạn đã viết một Lớp xe hơi trong kỷ nguyên Xe hơi, thì rất có thể bạn sẽ triển khai OilChange () như một phần của Lớp này. Nhưng, khi những chiếc xe điện được giới thiệu, bạn sẽ gặp rắc rối vì không có thay dầu liên quan đến những chiếc xe này, và không có triển khai.
Giải pháp cho vấn đề này là có Giao diện PerformanceMaintenance () trong lớp Xe hơi và ẩn chi tiết bên trong triển khai phù hợp. Mỗi loại Xe sẽ cung cấp cách triển khai riêng cho PerformanceMaintenance (). Là chủ sở hữu của Xe, tất cả những gì bạn phải giải quyết là PerformanceMaintenance () và không lo lắng về việc thích ứng khi có THAY ĐỔI.
class MaintenanceSpecialist {
public:
virtual int performMaintenance() = 0;
};
class CombustionEnginedMaintenance : public MaintenanceSpecialist {
int performMaintenance() {
printf("combustionEnginedMaintenance: We specialize in maintenance of Combustion engines \n");
return 0;
}
};
class ElectricMaintenance : public MaintenanceSpecialist {
int performMaintenance() {
printf("electricMaintenance: We specialize in maintenance of Electric Cars \n");
return 0;
}
};
class Car {
public:
MaintenanceSpecialist *mSpecialist;
virtual int maintenance() {
printf("Just wash the car \n");
return 0;
};
};
class GasolineCar : public Car {
public:
GasolineCar() {
mSpecialist = new CombustionEnginedMaintenance();
}
int maintenance() {
mSpecialist->performMaintenance();
return 0;
}
};
class ElectricCar : public Car {
public:
ElectricCar() {
mSpecialist = new ElectricMaintenance();
}
int maintenance(){
mSpecialist->performMaintenance();
return 0;
}
};
int _tmain(int argc, _TCHAR* argv[]) {
Car *myCar;
myCar = new GasolineCar();
myCar->maintenance(); /* I dont know what is involved in maintenance. But, I do know the maintenance has to be performed */
myCar = new ElectricCar();
myCar->maintenance();
return 0;
}
Giải thích thêm: Bạn là chủ xe sở hữu nhiều xe. Bạn khắc ra dịch vụ mà bạn muốn thuê ngoài. Trong trường hợp của chúng tôi, chúng tôi muốn thuê ngoài công việc bảo trì của tất cả các xe.
Bạn không muốn lo lắng về việc liên kết loại xe với nhà cung cấp dịch vụ. Bạn chỉ cần xác định khi bạn muốn lên lịch bảo trì và gọi nó. Công ty dịch vụ phù hợp nên nhảy vào và thực hiện công việc bảo trì.
Cách tiếp cận thay thế.
Bạn gọi công việc và tự làm. Ở đây bạn sẽ làm công việc bảo trì thích hợp.
Nhược điểm của phương pháp thứ 2 là gì? Bạn có thể không phải là chuyên gia trong việc tìm ra cách tốt nhất để bảo trì. Công việc của bạn là lái xe và tận hưởng nó. Không được trong kinh doanh để duy trì nó.
Nhược điểm của phương pháp đầu tiên là gì? Có rất nhiều chi phí tìm kiếm một công ty, trừ khi bạn là một công ty cho thuê xe, nó có thể không đáng để nỗ lực.
Tuyên bố này là về khớp nối. Một lý do tiềm năng để sử dụng lập trình hướng đối tượng là tái sử dụng. Vì vậy, ví dụ bạn có thể chia thuật toán của mình cho hai đối tượng cộng tác A và B. Điều này có thể hữu ích cho việc tạo sau này của thuật toán khác, có thể sử dụng lại một hoặc một trong hai đối tượng. Tuy nhiên, khi các đối tượng đó giao tiếp (gửi tin nhắn - phương thức gọi), chúng tạo ra sự phụ thuộc lẫn nhau. Nhưng nếu bạn muốn sử dụng cái này mà không có cái kia, bạn cần xác định một số đối tượng C nên làm gì cho đối tượng A nếu chúng ta thay thế B. Những mô tả đó được gọi là giao diện. Điều này cho phép đối tượng A giao tiếp mà không thay đổi với các đối tượng khác nhau dựa trên giao diện. Tuyên bố mà bạn đề cập nói rằng nếu bạn dự định sử dụng lại một phần của thuật toán (hay nói chung hơn là một chương trình), bạn nên tạo giao diện và dựa vào chúng,
Như những người khác đã nói, điều đó có nghĩa là mã gọi của bạn chỉ nên biết về một phụ huynh trừu tượng, KHÔNG phải là lớp triển khai thực tế sẽ thực hiện công việc.
Điều giúp hiểu điều này là TẠI SAO bạn nên luôn lập trình cho một giao diện. Có nhiều lý do, nhưng hai trong số những lý do dễ giải thích nhất là
1) Kiểm tra.
Hãy nói rằng tôi có toàn bộ mã cơ sở dữ liệu của mình trong một lớp. Nếu chương trình của tôi biết về lớp cụ thể, tôi chỉ có thể kiểm tra mã của mình bằng cách thực sự chạy nó với lớp đó. Tôi đang sử dụng -> có nghĩa là "nói chuyện với".
WorkerClass -> DALClass Tuy nhiên, hãy thêm giao diện vào hỗn hợp.
WorkerClass -> IDAL -> DALClass.
Vì vậy, DALClass thực hiện giao diện IDAL và lớp worker chỉ gọi thông qua điều này.
Bây giờ nếu chúng ta muốn viết các bài kiểm tra cho mã, thay vào đó chúng ta có thể tạo một lớp đơn giản chỉ hoạt động như một cơ sở dữ liệu.
WorkerClass -> IDAL -> IFakeDAL.
2) Tái sử dụng
Theo ví dụ trên, giả sử chúng tôi muốn chuyển từ SQL Server (mà DALClass cụ thể của chúng tôi sử dụng) sang MonogoDB. Điều này sẽ mất nhiều công sức, nhưng KHÔNG nếu chúng ta đã lập trình đến một giao diện. Trong trường hợp đó, chúng ta chỉ cần viết lớp DB mới và thay đổi (thông qua nhà máy)
WorkerClass -> IDAL -> DALClass
đến
WorkerClass -> IDAL -> MongoDBClass