Không có chương trình nào cho các giao diện, không triển khai, có nghĩa là gì?


Câu trả lời:


148

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
}

văn bản thay thế

Đâ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.

BIÊN TẬP

Tôi đã cập nhật ví dụ trên và thêm một Speakerlớ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 Speakerlớp trừu tượng thực hiện ISpeakergiao 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ừ Speakertớ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 Speakertriển khai sẽ rút ra SayHello()phương thức.

Hãy xem xét một trường hợp SpanishSpeakerkhô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
}

văn bản thay thế


19
Lập trình cho giao diện không chỉ là về loại biến tham chiếu. Điều đó cũng có nghĩa là bạn không sử dụng bất kỳ giả định ngầm nào về việc triển khai của mình. Ví dụ: nếu bạn sử dụng Listnhư 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).
Joachim Sauer

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

@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 để 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 triển khai giao diện IFlyable trên một vài lớp chim và động vật.
cái này __cpered_geek

@điều này. thay vào đó, nếu tôi sử dụng một lớp trừu tượng hoặc một mẫu mặt tiền, nó vẫn sẽ được gọi là "chương trình cho một giao diện" chứ? hoặc tôi rõ ràng phải sử dụng một giao diện và thực hiện nó trên một lớp?
never_had_a_name

1
Bạn đang sử dụng công cụ uml nào để tạo ra hình ảnh?
Adam Arold

29

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:

  1. nó che giấu những điều bạn không cần biết để làm cho đối tượng đơn giản hơn để sử dụng.
  2. nó cung cấp hợp đồng về cách đối tượng sẽ hành xử để bạn có thể phụ thuộc vào điều đó

Điều đó có nghĩa là bạn cần nhận thức được những gì bạn đang ký hợp đồng với đối tượng cần làm: trong ví dụ với điều kiện 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

Rất giống với cách tài liệu thư viện không đề cập đến việc triển khai, chúng chỉ là mô tả về các giao diện lớp được bao gồm.
Joe Iddon

17

Đ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 SOLIDnguyên tắc.

Một ví dụ trong .NET sẽ là mã bằng IListthay vì Listhoặc Dictionary, vì vậy bạn có thể sử dụng bất kỳ lớp nào thực hiện IListthay 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à ProviderBaselớ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ó.


nhưng làm thế nào 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ó?
never_had_a_name

1
Máy khách 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 rằng đối tượng (người) nào đó 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 Ý), vì 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 tiếng Ý nếu bạn hiểu tiếng Anh).
Gabriel čerbák

BTW. Nguyên tắc thay thế IMHO Liskov là về ngữ nghĩa của sự kế thừa và không liên quan gì đến các giao diện, có thể được tìm thấy trong các ngôn ngữ không có sự kế thừa (Đi từ Google).
Gabriel čerbák

5

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.

  1. Bạn xác định hợp đồng (Giao diện) phù hợp với tất cả các nhà cung cấp dịch vụ và xe hơi của bạn.
  2. Các nhà cung cấp dịch vụ đưa ra một 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 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ế.

  4. Bạn xác định công việc (có thể là Giao diện giao diện mới) phù hợp với tất cả các xe của bạn.
  5. Bạn đi ra với một 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ì 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.


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 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,


2

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


1

giao diện mô tả khả năng. 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 các lớp cụ thể.

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.