2010-04-23 46 views
87

Một tình cờ xảy ra khi cụm từ này khi đọc về các mẫu thiết kế."Chương trình giao diện, không triển khai" nghĩa là gì?

Nhưng tôi không hiểu, ai đó có thể giải thích điều này cho tôi?

+2

Bản sao có thể có của [Chương trình cho giao diện "có nghĩa là gì?] (Http://stackoverflow.com/questions/383947/what-does-it-mean-to-program-to- một giao diện) –

Trả lời

106

Giao diện chỉ là hợp đồng hoặc chữ ký và họ không biết bất kỳ điều gì về triển khai.

Mã hóa dựa trên phương tiện giao diện, mã máy khách luôn chứa đối tượng Giao diện do nhà máy cung cấp. Bất kỳ trường hợp nào được trả về bởi nhà máy sẽ có kiểu Giao diện mà bất kỳ lớp ứng cử viên nhà máy nào cũng phải thực hiện. Bằng cách này chương trình khách hàng không phải lo lắng về việc thực hiện 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 tại thời gian chạy. Nó cũng giúp bạn viết các chương trình tốt hơn nhiều từ quan điểm bảo trì.

Đây là 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 
} 

alt text http://ruchitsurati.net/myfiles/interface.png

Đây chỉ là một ví dụ cơ bản và giải thích thực tế của nguyên tắc là ngoài phạm vi của câu trả lời này.

EDIT

Tôi đã cập nhật ví dụ trên và thêm một lớp cơ sở trừu tượng loa. Trong bản cập nhật này, tôi đã thêm một tính năng cho tất cả Spakers vào "SayHello". Tất cả người nói đều nói "Hello World". Vì vậy, đó là một tính năng phổ biến với chức năng tương tự. Tham khảo sơ đồ lớp và bạn sẽ thấy rằng lớp trừu tượng của loa triển khai giao diện ISpeaker và đánh dấu Speak() là trừu tượng có nghĩa là mỗi việc thực hiện Speaker chịu trách nhiệm thực hiện phương thức Speak vì nó khác với Speaker to Speaker. Nhưng tất cả người nói đều nói "Xin chào" một cách 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 cài đặt Loa sẽ lấy được phương thức SayHello.

Hãy xem xét trường hợp SpanishSpeaker không thể nói Xin chào trong trường hợp đó, bạn có thể ghi đè phương thức SayHello cho Loa tiếng Tây Ban Nha và tăng ngoại lệ thích hợp.

Xin lưu ý rằng, chúng tôi đã không được thực hiện bất kỳ thay đổi giao diện ISpeaker. Và mã khách hàng và SpeakerFactory cũng không bị ảnh hưởng không thay đổi. Và đây là những gì chúng tôi đạt được bằng cách Lập trình-giao diện.

Và chúng tôi có thể đạt được hành vi này bằng cách thêm một lớp trừu tượng cơ sở Loa và một số sửa đổi nhỏ trong mỗi lần thực hiện như vậy, để nguyên 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ễ 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 
} 

alt text http://demo.ruchitsurati.net/myfiles/interface1.png

+15

Lập trình cho giao diện không chỉ ** về loại biến tham chiếu. Nó cũng có nghĩa là bạn không sử dụng bất kỳ giả định tiềm ẩn nào về việc triển khai của bạn. Ví dụ, nếu bạn sử dụng một 'List' làm kiểu, bạn vẫn có thể giả định rằng truy cập ngẫu nhiên là nhanh bằng cách liên tục gọi' get (i) '. –

+11

Các nhà máy trực giao với lập trình để giao diện, nhưng tôi nghĩ rằng lời giải thích này làm cho nó có vẻ như thể họ là một phần của nó. –

+0

@Toon: đồng ý với bạn. Tôi muốn cung cấp một ví dụ rất cơ bản và đơn giản cho lập trình-giao diện. Tôi không muốn gây nhầm lẫn cho người hỏi bằng cách thực hiện giao diện IFlyable trên vài lớp học của các loài chim và động vật. –

13

Điều đó có nghĩa là bạn nên cố gắng viết mã của mình để mã sử dụng trừu tượng (lớp trừu tượng hoặc giao diện) thay vì triển khai 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 một cuộ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 kỳ thứ gì được xác định trong hợp đồng này. Là một đối tượng thực tế (thực hiện 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à tập con của Liskov Substitution Principle (LSP), L của nguyên tắc SOLID.

Một ví dụ trong .NET sẽ được mã hóa với IList thay vì List hoặc Dictionary, vì vậy bạn có thể sử dụng bất kỳ lớp mà 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ừ cơ sở Class Library (BCL) là lớp trừu tượng ProviderBase - điều này cung cấp một số cơ sở hạ tầng và quan trọng là tất cả các triển khai nhà cung cấp có thể được sử dụng thay thế cho nhau nếu bạn viết mã chống lại nó.

+0

nhưng làm thế nào một khách hàng có thể tương tác với một giao diện và sử dụng các phương thức trống của nó? –

+0

Khách hàng không tương tác với giao diện, nhưng thông qua giao diện :) Các đối tượng tương tác với các đối tượng khác thông qua các phương thức (tin nhắn) và giao diện là một loại ngôn ngữ - khi bạn biết một đối tượng nào đó (người) thực hiện (nói) tiếng Anh (IList), bạn có thể sử dụng nó với bất kỳ nhu cầu nào để biết thêm về đối tượng đó (rằng anh ta cũng là người Ý), bởi vì nó không cần thiết trong bối cảnh đó (nếu bạn muốn nhờ giúp đỡ, bạn không cần biết anh ta cũng nói Ý nếu bạn hiểu tiếng Anh). –

+0

BTW. Nguyên tắc thay thế IMHO Liskov là về ngữ nghĩa thừa kế và không liên quan gì đến giao diện, mà cũng có thể được tìm thấy trong các ngôn ngữ không thừa kế (Go from Google). –

4

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 bạn giữa 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 một thuật toán khác sau này, có thể sử dụng lại một hoặc hai đối tượng khác. Tuy nhiên, khi các đối tượng đó giao tiếp (gửi tin nhắn - các 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 không có cái kia, bạn cần phải xác định những gì nên làm một số đối tượng C khác làm cho đối tượng A nếu chúng ta thay thế B. Các mô tả đó được gọi là các giao diện. Điều này cho phép đối tượng A giao tiếp mà không thay đổi với đối tượng khác nhau dựa trên giao diện. Câu lệnh mà bạn đã đề cập nói rằng nếu bạn định sử dụng lại một phần thuật toán (hoặc thường là chương trình), bạn nên tạo giao diện và dựa vào chúng, vì vậy bạn có thể thay đổi việc triển khai cụ thể bất cứ lúc nào mà không thay đổi các đối tượng khác nếu bạn sử dụng giao diện được khai báo.

26

Hãy nghĩ về giao diện dưới dạng hợp đồng giữa đối tượng và ứng dụng khách. Đó là giao diện xác định những thứ mà một đối tượng có thể làm và các chữ ký để truy cập vào những thứ đó.

Triển khai là 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 mã khách hàng gọi là sắp xếp 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. Những người sáng tạo của 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 cho các lớp trong các thư viện này bởi 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 nhiều như họ muốn.

Nếu, với tư cách là lập trình viên, bạn viết mã chống lại việc triển khai sau đó ngay khi thay đổi mã của bạn ngừng hoạt động. Vì vậy, hãy nghĩ đến lợi ích của giao diện theo cách này:

  1. nó ẩn những thứ bạn không cần biết làm cho đối tượng dễ sử dụng hơn.
  2. nó cung cấp hợp đồng về cách đối tượng sẽ hoạt động để bạn có thể phụ thuộc vào các giao diện
+0

Nó có nghĩa là bạn cần phải nhận thức được những gì bạn đang ký kết hợp đồng các đối tượng để làm: trong ví dụ cung cấp bạn chỉ ký hợp đồng cho một loại, không nhất thiết phải là một loại ổn định. – penguat

1

mô tả các khả năng này.khi viết mã bắt buộc, hãy nói về các khả năng bạn đang sử dụng, thay vì các loại hoặc lớp cụ thể.

2

Như những người khác đã nói, điều đó có nghĩa là mã gọi điện của bạn chỉ nên biết về một phụ huynh trừu tượng, KHÔNG phải lớp thực hiện thực tế sẽ thực hiện công việc.

Điều gì giúp hiểu đây là lý do tại sao bạn nên luôn lập trình cho giao diện. Có nhiều lý do, nhưng hai cách dễ nhất để giải thích là

1) Thử nghiệm.

Giả sử tôi có toàn bộ mã cơ sở dữ liệu 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 danh sách kết hợp.

WorkerClass -> IDAL -> DALClass.

Vì vậy, DALClass triển khai giao diện IDAL và lớp người lao động CHỈ gọi qua điều này.

Bây giờ nếu chúng ta muốn viết kiểm tra 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

Tiếp theo ví dụ trên, giả sử chúng ta muốn di chuyển từ SQL Server (mà DALClass bê tông của chúng tôi sử dụng) để MonogoDB. Điều này sẽ có tác dụng lớn, nhưng KHÔNG nếu chúng ta đã lập trình cho một giao diện. Trong trường hợp đó chúng ta chỉ cần viết các lớp mới DB, và thay đổi (thông qua các nhà máy)

WorkerClass -> IDAL -> DALClass

để

WorkerClass -> IDAL -> MongoDBClass

4

Nếu bạn đã viết một Class Car trong kỷ nguyên đốt xe, sau đó có một cơ hội tuyệt vời bạn sẽ thực hiện oilChange() như là một phần của lớp này. Nhưng, khi xe điện được giới thiệu, bạn sẽ gặp rắc rối vì không có sự thay đổi 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 đề là có giao diện performMaintenance() trong lớp Xe và ẩn các chi tiết bên trong việc triển khai thích hợp. Mỗi loại xe sẽ cung cấp việc thực hiện riêng của nó cho performMaintenance(). Là một chủ sở hữu của một chiếc xe tất cả các bạn phải đối phó với performMaintenance() và không phải lo lắng về việc thích nghi khi có một 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 bổ sung: Bạn là chủ sở hữu xe hơi sở hữu nhiều xe ô tô. Bạn khắc phục 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.

  1. Bạn xác định hợp đồng (Giao diện) phù hợp cho tất cả ô tô và nhà cung cấp dịch vụ.
  2. Nhà cung cấp dịch vụ đi kèm với cơ chế cung cấp dịch vụ.
  3. 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 chỉ định khi nào 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ế.

  4. Bạn xác định công việc (có thể là Giao diện giao diện mới) phù hợp cho tất cả các xe ô tô của bạn.
  5. Bạn xuất hiện với cơ chế cung cấp dịch vụ. Về cơ bản bạn sẽ cung cấp việc thực hiện.
  6. 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ì phù 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 để thực hiện việc 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 của việc duy trì nó.

    Nhược điểm của phương pháp đầu tiên là gì? Có phí tổn của việc tìm kiếm một công ty, trừ khi bạn là một công ty cho thuê xe hơi, nó có thể không có giá trị nỗ lực.

Các vấn đề liên quan