Giao diện - Điểm gì?


269

Lý do cho các giao diện thực sự trốn tránh tôi. Theo những gì tôi hiểu, đó là một công việc xoay quanh sự đa thừa kế không tồn tại mà không tồn tại trong C # (hoặc vì vậy tôi đã nói).

Tất cả những gì tôi thấy là, bạn xác định trước một số thành viên và hàm, sau đó phải được định nghĩa lại trong lớp một lần nữa. Do đó làm cho giao diện dư thừa. Nó chỉ cảm thấy như cú pháp tốt, rác đối với tôi (Xin vui lòng không có ý xúc phạm. Rác như trong những thứ vô dụng).

Trong ví dụ được đưa ra dưới đây được lấy từ một luồng giao diện C # khác trên tràn ngăn xếp, tôi sẽ chỉ tạo một lớp cơ sở gọi là Pizza thay vì giao diện.

ví dụ dễ dàng (lấy từ một đóng góp tràn ngăn xếp khác nhau)

public interface IPizza
{
    public void Order();
}

public class PepperoniPizza : IPizza
{
    public void Order()
    {
        //Order Pepperoni pizza
    }
}

public class HawaiiPizza : IPizza
{
    public void Order()
    {
        //Order HawaiiPizza
    }
}

Tôi có cảm giác có những bản sao của câu hỏi này ở đây trên SO, nhưng tất cả chúng dường như chỉ giải thích phần hợp đồng của một giao diện nên tôi không chắc họ áp dụng.
Lasse V. Karlsen

7
cố gắng trở thành một người dùng tốt và gọn gàng, tôi có xu hướng tìm kiếm câu trả lời của mình trước trong các diễn đàn khác nhau trước khi tôi đăng một cái gì đó. Thật không may, hầu hết trong số họ bắt đầu ở giai đoạn sau và phần còn lại không giúp được gì. Tôi đã phải vật lộn với "Tại sao làm điều đó?" Dường như với tôi như không cần thiết quá mức nó. Btw. Cảm ơn tất cả các câu trả lời rất nhanh. Tôi phải tiêu hóa tất cả chúng trước, nhưng tôi nghĩ bây giờ tôi đã có một ý tưởng khá hợp lý về quan điểm của chúng. Có vẻ như tôi đã luôn nhìn nó từ một góc độ khác. Cảm ơn sự giúp đỡ của bạn.
Nebelhom


1
Ngoài ra các giao diện giúp thiết lập sự kế thừa như cho structcác loại.
ja72

3
Hmm, OP đã hỏi "Theo những gì tôi hiểu, giao diện là một công việc xoay quanh sự đa thừa kế không tồn tại không tồn tại trong C #. (Khác với điều đó, trong ví dụ về sách giáo khoa pizza được trích dẫn) tôi sẽ chỉ sử dụng lớp cơ sở thay vì giao diện ". Và sau đó, hầu hết các câu trả lời đều đưa ra một ví dụ có thể được thực hiện bởi một lớp cơ sở (trừu tượng) hoặc đưa ra một ví dụ để cho thấy giao diện là cần thiết cho kịch bản đa kế thừa. Những câu trả lời đó đều tốt, nhưng không phải họ chỉ nhắc lại điều mà OP đã biết sao? Không có gì ngạc nhiên khi OP kết thúc việc chọn một câu trả lời mà không có ví dụ. LOL
RayLuo

Câu trả lời:


176

Điểm quan trọng là giao diện đại diện cho một hợp đồng . Một tập hợp các phương thức công khai mà bất kỳ lớp thực hiện nào cũng phải có. Về mặt kỹ thuật, giao diện chỉ chi phối cú pháp, tức là phương thức nào có, phương thức họ nhận được và nội dung họ trả về. Thông thường họ cũng gói gọn ngữ nghĩa, mặc dù điều đó chỉ bằng tài liệu.

Sau đó, bạn có thể có các triển khai khác nhau của một giao diện và trao đổi chúng theo ý muốn. Trong ví dụ của bạn, vì mỗi phiên bản pizza là một trường hợp IPizzabạn có thể sử dụng IPizzabất cứ nơi nào bạn xử lý một thể hiện của một loại pizza không xác định. Bất kỳ trường hợp nào có kiểu kế thừa IPizzađược đảm bảo là có thể sắp xếp được, vì nó có một Order()phương thức.

Python không được gõ tĩnh, do đó các kiểu được giữ và tra cứu trong thời gian chạy. Vì vậy, bạn có thể thử gọi một Order()phương thức trên bất kỳ đối tượng. Thời gian chạy là hạnh phúc miễn là đối tượng có một phương thức như vậy và có lẽ chỉ nhún vai và nói »Meh.« Nếu nó không. Không như vậy trong C #. Trình biên dịch chịu trách nhiệm thực hiện các cuộc gọi chính xác và nếu nó chỉ có một số ngẫu nhiên objectthì trình biên dịch không biết liệu cá thể trong thời gian chạy sẽ có phương thức đó hay không. Từ quan điểm của nhà soạn nhạc, nó không hợp lệ vì nó không thể xác minh nó. (Tôi có thể làm những việc như vậy với sự phản chiếu hoặc dynamictừ khóa, nhưng điều đó sẽ đi hơi xa ngay bây giờ, tôi đoán vậy.)

Cũng lưu ý rằng một giao diện theo nghĩa thông thường không nhất thiết phải là C # interface, nó có thể là một lớp trừu tượng hoặc thậm chí là một lớp bình thường (có thể có ích nếu tất cả các lớp con cần chia sẻ một số mã chung - trong hầu hết các trường hợp , tuy nhiên, interfaceđủ).


1
+1, mặc dù tôi không nói rằng giao diện (theo nghĩa của hợp đồng) có thể là một lớp trừu tượng hoặc bình thường.
Groo

3
Tôi sẽ thêm rằng bạn không thể hiểu giao diện trong một vài phút. Tôi nghĩ sẽ không hợp lý khi hiểu các giao diện nếu bạn không có nhiều năm kinh nghiệm lập trình hướng đối tượng. Bạn có thể thêm một số liên kết đến sách. Tôi muốn đề xuất: Dependency Injection trong .NET , đây thực sự là lỗ sâu của rabit, không chỉ là một lời giới thiệu nhẹ nhàng.
knut

2
À, có vấn đề với tôi không có manh mối về DI. Nhưng tôi đoán vấn đề chính của người hỏi là tại sao chúng lại cần thiết khi trong Python tất cả đều hoạt động mà không có. Đó là những gì đoạn văn lớn nhất trong câu trả lời của tôi đã cố gắng cung cấp. Tôi không nghĩ cần phải đào sâu vào mọi mô hình và thực hành sử dụng các giao diện ở đây.
Joey

1
Chà, sau đó câu hỏi của bạn trở thành: "Tại sao nên sử dụng các ngôn ngữ được gõ tĩnh khi các ngôn ngữ được gõ động dễ lập trình hơn?". Mặc dù tôi không phải là chuyên gia để trả lời câu hỏi đó, tôi có thể mạo hiểm nói rằng hiệu suất là vấn đề quyết định. Khi một cuộc gọi đến một đối tượng python được thực hiện, quá trình phải quyết định trong thời gian chạy xem đối tượng đó có một phương thức gọi là "Đặt hàng" hay không, trong khi nếu bạn thực hiện cuộc gọi đến một đối tượng C #, thì nó đã được thiết lập rằng nó thực hiện phương thức đó và cuộc gọi có thể được thực hiện đến một địa chỉ như vậy.
Boluc Papuccuoglu

3
@BolucPapuccuoglu: Ngoài ra, với một đối tượng gõ tĩnh nếu ai đó biết foothực hiện IWidget, sau đó một lập trình viên nhìn thấy một cuộc gọi để foo.woozle()có thể xem tài liệu IWidgetvà biết phương pháp đó phải làm gì . Lập trình viên có thể không có cách nào để biết mã thực hiện thực tế sẽ đến từ đâu, nhưng bất kỳ loại nào tuân theo IWidgethợp đồng giao diện sẽ thực hiện footheo cách phù hợp với hợp đồng đó. Trong một ngôn ngữ động, ngược lại, sẽ không có điểm tham chiếu rõ ràng cho những gì foo.woozle()nên có nghĩa.
20/2/2015

440

Không ai thực sự giải thích một cách đơn giản về cách các giao diện hữu ích, vì vậy tôi sẽ cung cấp cho nó một shot (và đánh cắp một ý tưởng từ câu trả lời của Shamim một chút).

Hãy có ý tưởng về một dịch vụ đặt hàng pizza. Bạn có thể có nhiều loại pizza và một hành động chung cho mỗi chiếc bánh pizza đang chuẩn bị thứ tự trong hệ thống. Mỗi chiếc bánh pizza phải được chuẩn bị nhưng mỗi chiếc bánh pizza được chuẩn bị khác nhau . Ví dụ, khi một chiếc bánh pizza nhồi vỏ được đặt hàng, hệ thống có thể phải xác minh một số thành phần có sẵn tại nhà hàng và đặt những thứ không cần thiết cho pizza món ăn sâu.

Khi viết mã này, về mặt kỹ thuật, bạn có thể làm

public class Pizza()
{
    public void Prepare(PizzaType tp)
    {
        switch (tp)
        {
            case PizzaType.StuffedCrust:
                // prepare stuffed crust ingredients in system
                break;

            case PizzaType.DeepDish:
                // prepare deep dish ingredients in system
                break;

            //.... etc.
        }
    }
}

Tuy nhiên, pizza món ăn sâu (theo thuật ngữ C #) có thể yêu cầu các thuộc tính khác nhau được đặt trong Prepare()phương thức so với lớp vỏ nhồi, và do đó bạn kết thúc với rất nhiều thuộc tính tùy chọn và lớp không mở rộng tốt (nếu bạn thêm mới các loại pizza).

Cách thích hợp để giải quyết điều này là sử dụng giao diện. Giao diện tuyên bố rằng tất cả các bánh pizza có thể được chuẩn bị, nhưng mỗi chiếc bánh pizza có thể được chuẩn bị khác nhau. Vì vậy, nếu bạn có các giao diện sau:

public interface IPizza
{
    void Prepare();
}

public class StuffedCrustPizza : IPizza
{
    public void Prepare()
    {
        // Set settings in system for stuffed crust preparations
    }
}

public class DeepDishPizza : IPizza
{
    public void Prepare()
    {
        // Set settings in system for deep dish preparations
    }
}

Bây giờ mã xử lý đơn đặt hàng của bạn không cần biết chính xác các loại pizza đã được đặt hàng để xử lý các thành phần. Nó chỉ có:

public PreparePizzas(IList<IPizza> pizzas)
{
    foreach (IPizza pizza in pizzas)
        pizza.Prepare();
}

Mặc dù mỗi loại pizza được chuẩn bị khác nhau, phần mã này không cần quan tâm chúng ta đang xử lý loại pizza nào, nó chỉ biết rằng nó được gọi là pizza và do đó mỗi cuộc gọi Preparesẽ tự động chuẩn bị từng bánh pizza dựa trên loại của nó, ngay cả khi bộ sưu tập có nhiều loại pizza.


11
+1, tôi thích ví dụ về việc sử dụng Danh sách để hiển thị việc sử dụng giao diện.
danielunderwood

21
Câu trả lời tốt, nhưng có lẽ sửa đổi nó để làm rõ lý do tại sao một giao diện trong trường hợp này tốt hơn là chỉ sử dụng một lớp trừu tượng (với một ví dụ đơn giản như vậy, một lớp trừu tượng có thể thực sự tốt hơn?)
Jez

3
Đây là câu trả lời tốt nhất. Có một lỗi đánh máy hoặc có thể bạn chỉ cần sao chép và dán mã. Trong giao diện C #, bạn không khai báo các sửa đổi truy cập như public. Vì vậy, bên trong giao diện nên làvoid Prepare();
Dush

26
Tôi không thấy cách này trả lời câu hỏi. Ví dụ này có thể dễ dàng được thực hiện với một lớp cơ sở và một phương thức trừu tượng, đó chính xác là những gì câu hỏi ban đầu đã chỉ ra.
Jamie Kitson

4
Các giao diện không thực sự đi vào quyền của họ cho đến khi bạn có rất nhiều trong số họ đi xung quanh. Bạn chỉ có thể kế thừa từ một lớp duy nhất, trừu tượng hoặc không, nhưng bạn có thể thực hiện bao nhiêu giao diện khác nhau trong một lớp duy nhất tùy thích. Đột nhiên bạn không cần một cánh cửa trong khung cửa; bất cứ điều gì IOpenable sẽ làm, có thể là một cánh cửa, một cửa sổ, một hộp thư, bạn đặt tên cho nó.
mtnielsen

123

Đối với tôi, khi bắt đầu, vấn đề chỉ trở nên rõ ràng khi bạn ngừng xem chúng là những thứ giúp mã của bạn dễ dàng hơn / nhanh hơn để viết - đây không phải là mục đích của chúng. Chúng có một số cách sử dụng:

(Điều này sẽ làm mất đi sự tương tự của pizza, vì không dễ để hình dung ra cách sử dụng này)

Giả sử bạn đang tạo một trò chơi đơn giản trên màn hình và Nó sẽ có những sinh vật mà bạn tương tác.

Trả lời: Chúng có thể làm cho mã của bạn dễ duy trì hơn trong tương lai bằng cách giới thiệu một khớp nối lỏng lẻo giữa mặt trước và mặt sau của bạn.

Bạn có thể viết cái này để bắt đầu, vì sẽ chỉ có những kẻ bị troll:

// This is our back-end implementation of a troll
class Troll
{
    void Walk(int distance)
    {
        //Implementation here
    }
}

Mặt trước:

function SpawnCreature()
{
    Troll aTroll = new Troll();

    aTroll.Walk(1);
}

Hai tuần sau, tiếp thị quyết định bạn cũng cần Orc, khi họ đọc về họ trên twitter, vì vậy bạn sẽ phải làm một cái gì đó như:

class Orc
{
    void Walk(int distance)
    {
        //Implementation (orcs are faster than trolls)
    }
}

Mặt trước:

void SpawnCreature(creatureType)
{
    switch(creatureType)
    {
         case Orc:

           Orc anOrc = new Orc();
           anORc.Walk();

          case Troll:

            Troll aTroll = new Troll();
             aTroll.Walk();
    }
}

Và bạn có thể thấy làm thế nào điều này bắt đầu trở nên lộn xộn. Bạn có thể sử dụng một giao diện ở đây để giao diện người dùng của bạn sẽ được viết một lần và (đây là bit quan trọng) đã được kiểm tra và sau đó bạn có thể cắm thêm các mục cuối phía sau theo yêu cầu:

interface ICreature
{
    void Walk(int distance)
}

public class Troll : ICreature
public class Orc : ICreature 

//etc

Mặt trước là:

void SpawnCreature(creatureType)
{
    ICreature creature;

    switch(creatureType)
    {
         case Orc:

           creature = new Orc();

          case Troll:

            creature = new Troll();
    }

    creature.Walk();
}

Giao diện người dùng bây giờ chỉ quan tâm đến giao diện ICreature - không bận tâm đến việc triển khai nội bộ của một con quỷ hoặc một con Orc, mà chỉ dựa trên thực tế là chúng thực hiện ICreature.

Một điểm quan trọng cần lưu ý khi nhìn vào điều này từ quan điểm này là bạn cũng có thể dễ dàng sử dụng một lớp sinh vật trừu tượng, và từ quan điểm này, điều này có tác dụng tương tự .

Và bạn có thể trích xuất sáng tạo ra một nhà máy:

public class CreatureFactory {

 public ICreature GetCreature(creatureType)
 {
    ICreature creature;

    switch(creatureType)
    {
         case Orc:

           creature = new Orc();

          case Troll:

            creature = new Troll();
    }

    return creature;
  }
}

Và kết thúc trước của chúng tôi sẽ trở thành:

CreatureFactory _factory;

void SpawnCreature(creatureType)
{
    ICreature creature = _factory.GetCreature(creatureType);

    creature.Walk();
}

Mặt trước bây giờ thậm chí không cần phải tham chiếu đến thư viện nơi Troll và Orc được triển khai (cung cấp nhà máy ở trong một thư viện riêng) - nó không cần biết gì về chúng.

B: Giả sử bạn có chức năng mà chỉ một số sinh vật sẽ có trong cấu trúc dữ liệu đồng nhất của bạn , ví dụ:

interface ICanTurnToStone
{
   void TurnToStone();
}

public class Troll: ICreature, ICanTurnToStone

Mặt trước có thể là:

void SpawnCreatureInSunlight(creatureType)
{
    ICreature creature;

    switch(creatureType)
    {
         case Orc:

           creature = new Orc();

          case Troll:

            creature = new Troll();
    }

    creature.Walk();

    if (creature is ICanTurnToStone)
    {
       (ICanTurnToStone)creature.TurnToStone();
    }
}

C: Sử dụng để tiêm phụ thuộc

Hầu hết các khung tiêm phụ thuộc dễ dàng làm việc hơn khi có sự khớp nối rất lỏng lẻo giữa mã mặt trước và việc thực hiện phía sau. Nếu chúng tôi lấy ví dụ về nhà máy ở trên và để nhà máy của chúng tôi thực hiện giao diện:

public interface ICreatureFactory {
     ICreature GetCreature(string creatureType);
}

Mặt trước của chúng tôi sau đó có thể được tiêm này (ví dụ: bộ điều khiển API MVC) thông qua hàm tạo (thông thường):

public class CreatureController : Controller {

   private readonly ICreatureFactory _factory;

   public CreatureController(ICreatureFactory factory) {
     _factory = factory;
   }

   public HttpResponseMessage TurnToStone(string creatureType) {

       ICreature creature = _factory.GetCreature(creatureType);

       creature.TurnToStone();

       return Request.CreateResponse(HttpStatusCode.OK);
   }
}

Với khung DI của chúng tôi (ví dụ Ninject hoặc Autofac), chúng tôi có thể thiết lập chúng để trong thời gian chạy, một thể hiện của CreatureFactory sẽ được tạo bất cứ khi nào cần một ICreatureFactory trong một hàm tạo - điều này làm cho mã của chúng tôi đẹp và đơn giản.

Điều đó cũng có nghĩa là khi chúng tôi viết một bài kiểm tra đơn vị cho bộ điều khiển của mình, chúng tôi có thể cung cấp một ICreatureFactory bị giả định (ví dụ: nếu việc triển khai cụ thể yêu cầu truy cập DB, chúng tôi không muốn kiểm tra đơn vị của mình phụ thuộc vào điều đó) và dễ dàng kiểm tra mã trong bộ điều khiển của chúng tôi .

D: Có những cách sử dụng khác, ví dụ: bạn có hai dự án A và B vì lý do 'di sản' không được cấu trúc tốt và A có tham chiếu đến B.

Sau đó, bạn tìm thấy chức năng trong B cần gọi một phương thức đã có trong A. Bạn không thể thực hiện bằng cách sử dụng các triển khai cụ thể khi bạn có được một tham chiếu vòng tròn.

Bạn có thể có một giao diện được khai báo trong B rằng lớp trong A sau đó thực hiện. Phương thức của bạn trong B có thể được thông qua một thể hiện của một lớp thực hiện giao diện mà không gặp vấn đề gì, mặc dù đối tượng cụ thể thuộc loại A.


6
Sẽ không khó chịu khi bạn tìm thấy một câu trả lời đang cố nói bạn là gì, nhưng nó có tốt hơn nhiều không - stackoverflow.com/a/93998/117215
Paddy

18
Tin tốt bây giờ là một trang chết và bạn thì không :). Ví dụ tốt!
Sealer_05

1
Thảo luận C của bạn mất tôi một chút. Nhưng, tôi thích các cuộc thảo luận A và B của bạn vì cả hai đều giải thích về cách các giao diện có thể được sử dụng để cung cấp các loại chức năng phổ biến trên nhiều lớp. Khu vực của các giao diện vẫn còn mờ đối với tôi là cách các giao diện được sử dụng để ghép lỏng hơn. Có lẽ đó là những gì cuộc thảo luận C của bạn đã được giải quyết? Nếu vậy, tôi đoán tôi cần một ví dụ chi tiết hơn :)
user2075599

1
Theo tôi, trong ví dụ của bạn, ICreature sẽ phù hợp hơn với việc trở thành một lớp cơ sở trừu tượng (Sinh vật?) Cho Troll và Orc. Sau đó, bất kỳ logic chung giữa các sinh vật có thể được thực hiện ở đó. Điều đó có nghĩa là bạn hoàn toàn không cần giao diện ICreature ...
Skarsnik

1
@Skarsnik - khá, đó là những gì ghi chú này nói về: "Một điểm quan trọng cần lưu ý khi nhìn vào điều này từ quan điểm này là bạn cũng có thể dễ dàng sử dụng một lớp sinh vật trừu tượng, và từ quan điểm này, điều này có cùng một hiệu ứng."
Lúa

33

Dưới đây là ví dụ của bạn được xem xét lại:

public interface IFood // not Pizza
{
    public void Prepare();

}

public class Pizza : IFood
{
    public void Prepare() // Not order for explanations sake
    {
        //Prepare Pizza
    }
}

public class Burger : IFood
{
    public void Prepare()
    {
        //Prepare Burger
    }
}

27

Các ví dụ trên không có nhiều ý nghĩa. Bạn có thể thực hiện tất cả các ví dụ trên bằng cách sử dụng các lớp (lớp trừu tượng nếu bạn muốn nó chỉ hoạt động như một hợp đồng ):

public abstract class Food {
    public abstract void Prepare();
}

public class Pizza : Food  {
    public override void Prepare() { /* Prepare pizza */ }
}

public class Burger : Food  {
    public override void Prepare() { /* Prepare Burger */ }
}

Bạn có hành vi tương tự như với giao diện. Bạn có thể tạo List<Food>và lặp đi lặp lại mà không biết lớp nào nằm trên cùng.

Ví dụ đầy đủ hơn sẽ là nhiều kế thừa:

public abstract class MenuItem {
    public string Name { get; set; }
    public abstract void BringToTable();
}

// Notice Soda only inherits from MenuItem
public class Soda : MenuItem {
    public override void BringToTable() { /* Bring soda to table */ }
}


// All food needs to be cooked (real food) so we add this
// feature to all food menu items
public interface IFood {
    void Cook();
}

public class Pizza : MenuItem, IFood {
    public override void BringToTable() { /* Bring pizza to table */ }
    public void Cook() { /* Cook Pizza */ }
}

public class Burger : MenuItem, IFood {
    public override void BringToTable() { /* Bring burger to table */ }
    public void Cook() { /* Cook Burger */ }
}

Sau đó, bạn có thể sử dụng tất cả chúng như MenuItemvà không quan tâm đến cách chúng xử lý từng cuộc gọi phương thức.

public class Waiter {
    public void TakeOrder(IEnumerable<MenuItem> order) 
    {
        // Cook first
        // (all except soda because soda is not IFood)
        foreach (var food in order.OfType<IFood>())
            food.Cook();

        // Bring them all to the table
        // (everything, including soda, pizza and burger because they're all menu items)
        foreach (var menuItem in order)
            menuItem.BringToTable();
    }
}

2
Tôi đã phải cuộn xuống đây để tìm một câu trả lời thực sự trả lời cho câu hỏi "tại sao không chỉ sử dụng một lớp trừu tượng thay vì một giao diện?". Có vẻ như thực sự chỉ xử lý việc thiếu nhiều thừa kế của c #.
Cú pháp

2
Vâng, đây là lời giải thích tốt nhất cho tôi. Người duy nhất giải thích rõ ràng tại sao giao diện có thể hữu ích hơn một lớp trừu tượng. (ví dụ: pizza là MenuItem AND cũng là Thực phẩm trong khi với lớp trừu tượng, nó chỉ có thể là một hoặc không phải cả hai)
Maxter

25

Giải thích đơn giản với sự tương tự

Vấn đề cần giải quyết: mục đích của đa hình là gì?

Tương tự: Vì vậy, tôi là một người đi trước trên một trang web xây dựng.

Thương nhân đi bộ trên các trang web xây dựng tất cả các thời gian. Tôi không biết ai sẽ đi qua những cánh cửa đó. Nhưng về cơ bản tôi nói họ phải làm gì.

  1. Nếu đó là một thợ mộc tôi nói: xây dựng giàn giáo bằng gỗ.
  2. Nếu đó là thợ sửa ống nước, tôi nói: "Thiết lập đường ống"
  3. Nếu đó là một thợ điện, tôi nói, "Rút cáp ra và thay thế chúng bằng cáp quang".

Vấn đề với cách tiếp cận trên là tôi phải: (i) biết ai đang đi trong cánh cửa đó, và tùy thuộc vào đó là ai, tôi phải nói cho họ biết phải làm gì. Điều đó có nghĩa là tôi phải biết mọi thứ về một giao dịch cụ thể. Có các chi phí / lợi ích liên quan đến phương pháp này:

Ý nghĩa của việc biết phải làm gì:

  • Điều này có nghĩa là nếu mã của thợ mộc thay đổi từ: BuildScaffolding()thành BuildScaffold()(tức là thay đổi tên một chút) thì tôi cũng sẽ phải thay đổi lớp gọi (tức là Forepersonlớp) - bạn sẽ phải thực hiện hai thay đổi cho mã thay vì (về cơ bản ) chỉ một. Với đa hình, bạn (về cơ bản) chỉ cần thực hiện một thay đổi để đạt được kết quả tương tự.

  • Thứ hai, bạn sẽ không phải liên tục hỏi: bạn là ai? ok làm điều này ... bạn là ai? ok làm điều đó ..... đa hình - nó DRY mã đó, và rất hiệu quả trong một số tình huống:

  • với tính đa hình, bạn có thể dễ dàng thêm các lớp giao dịch bổ sung mà không thay đổi bất kỳ mã hiện có nào. (tức là thứ hai của các nguyên tắc thiết kế RẮN: Nguyên tắc đóng mở).

Giải pháp

Hãy tưởng tượng một kịch bản trong đó, bất kể ai đi vào cửa, tôi có thể nói: "Work ()" và họ làm công việc tôn trọng mà họ chuyên: thợ sửa ống nước sẽ xử lý đường ống, và thợ điện sẽ xử lý dây.

Lợi ích của phương pháp này là: (i) Tôi không cần biết chính xác ai đang bước qua cánh cửa đó - tất cả những gì tôi cần biết là họ sẽ là một loại thương nhân và họ có thể làm việc, và thứ hai , (ii) tôi không cần biết gì về giao dịch cụ thể đó. Các tradie sẽ chăm sóc điều đó.

Vì vậy, thay vì điều này:

If(electrician) then  electrician.FixCablesAndElectricity() 

if(plumber) then plumber.IncreaseWaterPressureAndFixLeaks() 

Tôi có thể làm một cái gì đó như thế này:

ITradesman tradie = Tradesman.Factory(); // in reality i know it's a plumber, but in the real world you won't know who's on the other side of the tradie assignment.

tradie.Work(); // and then tradie will do the work of a plumber, or electrician etc. depending on what type of tradesman he is. The foreman doesn't need to know anything, apart from telling the anonymous tradie to get to Work()!!

Lợi ích là gì?

Lợi ích là nếu các yêu cầu công việc cụ thể của thợ mộc v.v ... thay đổi, thì người đi trước sẽ không cần thay đổi mã của mình - anh ta không cần phải biết hoặc quan tâm. Tất cả vấn đề là thợ mộc biết ý nghĩa của Work (). Thứ hai, nếu một loại công nhân xây dựng mới vào công trường, thì người quản đốc không cần biết gì về thương mại - tất cả các quản đốc quan tâm là nếu công nhân xây dựng (.eg Welder, Glazier, Tiler, v.v.) có thể nhận được một số công việc () được thực hiện.


Minh họa vấn đề và giải pháp (Có và không có giao diện):

Không có giao diện (Ví dụ 1):

Ví dụ 1: không có giao diện

Không có giao diện (Ví dụ 2):

Ví dụ 2: không có giao diện

Với giao diện:

Ví dụ 3: Lợi ích của việc sử dụng Giao diện

Tóm lược

Giao diện cho phép bạn khiến người đó thực hiện công việc họ được giao, mà bạn không có kiến ​​thức về chính xác họ là ai hoặc chi tiết cụ thể về những gì họ có thể làm. Điều này cho phép bạn dễ dàng thêm các loại mới (giao dịch) mà không cần thay đổi mã hiện tại (về mặt kỹ thuật bạn thay đổi nó một chút xíu) và đó là lợi ích thực sự của phương pháp OOP so với phương pháp lập trình chức năng hơn.

Nếu bạn không hiểu bất kỳ điều nào ở trên hoặc nếu không rõ ràng, hãy hỏi trong một bình luận và tôi sẽ cố gắng làm cho câu trả lời tốt hơn.


4
Thêm một cho anh chàng đi làm trong một chiếc áo hoodie.
Yusha

11

Trong trường hợp không gõ vịt như bạn có thể sử dụng nó trong Python, C # dựa vào các giao diện để cung cấp trừu tượng. Nếu các phụ thuộc của một lớp là tất cả các loại cụ thể, bạn không thể vượt qua trong bất kỳ loại nào khác - sử dụng các giao diện bạn có thể vượt qua trong bất kỳ loại nào thực hiện giao diện.


3
+1 Đúng vậy, nếu bạn gõ vịt, bạn không cần giao diện. Nhưng họ thực thi loại an toàn mạnh mẽ hơn.
Groo

11

Ví dụ về Pizza rất tệ vì bạn nên sử dụng một lớp trừu tượng để xử lý việc đặt hàng và các loại pizza chỉ nên ghi đè loại pizza chẳng hạn.

Bạn sử dụng giao diện khi bạn có một tài sản chung, nhưng các lớp của bạn kế thừa từ các nơi khác nhau hoặc khi bạn không có bất kỳ mã chung nào bạn có thể sử dụng. Ví dụ, đây là những thứ được sử dụng có thể được xử lý IDisposable, bạn biết nó sẽ bị loại bỏ, bạn chỉ không biết điều gì sẽ xảy ra khi nó được xử lý.

Một giao diện chỉ là một hợp đồng cho bạn biết một số điều mà một đối tượng có thể làm, những tham số nào và loại trả về nào để mong đợi.


10

Hãy xem xét trường hợp bạn không kiểm soát hoặc sở hữu các lớp cơ sở.

Lấy các điều khiển trực quan, ví dụ, trong .NET cho Winforms, tất cả chúng đều kế thừa từ Điều khiển lớp cơ sở, được xác định hoàn toàn trong khung .NET.

Giả sử bạn đang kinh doanh trong việc tạo các điều khiển tùy chỉnh. Bạn muốn xây dựng các nút mới, hộp văn bản, danh sách xem, lưới, không có gì và bạn muốn tất cả chúng có các tính năng nhất định duy nhất cho bộ điều khiển của bạn.

Ví dụ, bạn có thể muốn một cách chung để xử lý theo chủ đề hoặc một cách phổ biến để xử lý nội địa hóa.

Trong trường hợp này, bạn không thể "chỉ tạo một lớp cơ sở" bởi vì nếu bạn làm điều đó, bạn phải thực hiện lại mọi thứ liên quan đến các điều khiển.

Thay vào đó, bạn sẽ xuống từ Nút, TextBox, ListView, GridView, v.v. và thêm mã của bạn.

Nhưng điều này đặt ra một vấn đề, làm sao bây giờ bạn có thể xác định điều khiển nào là "của bạn", làm thế nào bạn có thể xây dựng một số mã có nội dung "cho tất cả các điều khiển trên biểu mẫu là của tôi, đặt chủ đề thành X".

Nhập giao diện.

Các giao diện là một cách để nhìn vào một đối tượng, để xác định rằng đối tượng tuân thủ một hợp đồng nhất định.

Bạn sẽ tạo "YourButton", xuống từ Nút và thêm hỗ trợ cho tất cả các giao diện bạn cần.

Điều này sẽ cho phép bạn viết mã như sau:

foreach (Control ctrl in Controls)
{
    if (ctrl is IMyThemableControl)
        ((IMyThemableControl)ctrl).SetTheme(newTheme);
}

Điều này sẽ không thể thực hiện được nếu không có giao diện, thay vào đó bạn sẽ phải viết mã như thế này:

foreach (Control ctrl in Controls)
{
    if (ctrl is MyThemableButton)
        ((MyThemableButton)ctrl).SetTheme(newTheme);
    else if (ctrl is MyThemableTextBox)
        ((MyThemableTextBox)ctrl).SetTheme(newTheme);
    else if (ctrl is MyThemableGridView)
        ((MyThemableGridView)ctrl).SetTheme(newTheme);
    else ....
}

Vâng, tôi biết, bạn không nên sử dụng "là" và sau đó bỏ, bỏ qua một bên.
Lasse V. Karlsen

4
Tôi biết thở dài , nhưng đó là sự cố ở đây.
Lasse V. Karlsen

7

Trong trường hợp này, bạn có thể (và có thể sẽ) chỉ định nghĩa một lớp cơ sở Pizza và kế thừa từ chúng. Tuy nhiên, có hai lý do mà Giao diện cho phép bạn thực hiện những việc không thể đạt được theo những cách khác:

  1. Một lớp có thể thực hiện nhiều giao diện. Nó chỉ định nghĩa các tính năng mà lớp phải có. Việc thực hiện một loạt các giao diện có nghĩa là một lớp có thể thực hiện nhiều chức năng ở những nơi khác nhau.

  2. Một giao diện có thể được định nghĩa trong một phạm vi rộng hơn so với lớp hoặc người gọi. Điều này có nghĩa là bạn có thể tách chức năng, tách phần phụ thuộc dự án và giữ chức năng trong một dự án hoặc lớp và thực hiện điều này ở nơi khác.

Một hàm ý của 2 là bạn có thể thay đổi lớp đang được sử dụng, chỉ cần yêu cầu nó thực hiện giao diện thích hợp.


6

Hãy xem xét bạn không thể sử dụng nhiều kế thừa trong C #, và sau đó xem lại câu hỏi của bạn.



4

Nếu tôi đang làm việc trên một API để vẽ hình dạng, tôi có thể muốn sử dụng DirectX hoặc các cuộc gọi đồ họa hoặc OpenGL. Vì vậy, tôi sẽ tạo một giao diện, nó sẽ trừu tượng hóa việc thực hiện của tôi khỏi những gì bạn gọi.

Vì vậy, bạn gọi một phương pháp nhà máy : MyInterface i = MyGraphics.getInstance(). Sau đó, bạn có một hợp đồng, vì vậy bạn biết những chức năng bạn có thể mong đợi MyInterface. Vì vậy, bạn có thể gọi i.drawRectanglehoặc i.drawCubevà biết rằng nếu bạn trao đổi một thư viện này sang thư viện khác, thì các chức năng được hỗ trợ.

Điều này trở nên quan trọng hơn nếu bạn đang sử dụng Dependency Injection, khi đó bạn có thể, trong một tệp XML, trao đổi các triển khai.

Vì vậy, bạn có thể có một thư viện tiền điện tử có thể được xuất ra để sử dụng chung và một thư viện khác chỉ bán cho các công ty Mỹ và sự khác biệt là ở chỗ bạn thay đổi tệp cấu hình và phần còn lại của chương trình không đã thay đổi

Điều này được sử dụng rất nhiều với các bộ sưu tập trong .NET, ví dụ như bạn chỉ nên sử dụng Listcác biến và đừng lo lắng liệu đó là ArrayList hay LinkedList.

Miễn là bạn mã hóa giao diện thì nhà phát triển có thể thay đổi triển khai thực tế và phần còn lại của chương trình được giữ nguyên.

Điều này cũng hữu ích khi kiểm tra đơn vị, vì bạn có thể giả lập toàn bộ giao diện, vì vậy, tôi không phải truy cập cơ sở dữ liệu, nhưng để triển khai giả lập chỉ trả về dữ liệu tĩnh, vì vậy tôi có thể kiểm tra phương thức của mình mà không cần lo lắng cơ sở dữ liệu có được bảo trì hay không.


4

Một giao diện thực sự là một hợp đồng mà các lớp triển khai phải tuân theo, thực tế nó là cơ sở cho hầu hết mọi mẫu thiết kế mà tôi biết.

Trong ví dụ của bạn, giao diện được tạo bởi vì sau đó mọi thứ mà IS A Pizza, có nghĩa là thực hiện giao diện Pizza, được đảm bảo đã thực hiện

public void Order();

Sau mã được đề cập của bạn, bạn có thể có một cái gì đó như thế này:

public void orderMyPizza(IPizza myPizza) {
//This will always work, because everyone MUST implement order
      myPizza.order();
}

Bằng cách này, bạn đang sử dụng đa hình và tất cả những gì bạn quan tâm là các đối tượng của bạn phản hồi theo thứ tự ().


4

Tôi đã tìm kiếm từ "thành phần" trên trang này và không thấy nó một lần. Câu trả lời này rất nhiều ngoài các câu trả lời đã nói ở trên.

Một trong những lý do cực kỳ quan trọng để sử dụng các giao diện trong Dự án hướng đối tượng là chúng cho phép bạn ưu tiên sáng tác hơn kế thừa. Bằng cách triển khai các giao diện, bạn có thể tách rời các triển khai của mình khỏi các thuật toán khác nhau mà bạn đang áp dụng cho chúng.

Hướng dẫn "Mẫu trang trí" tuyệt vời này của Derek Banas (trong đó - đủ vui - cũng sử dụng pizza làm ví dụ) là một minh họa đáng giá:

https://www.youtube.com/watch?v=j40kRwSm4VE


2
Tôi thực sự sốc vì đây không phải là câu trả lời tốt nhất. Các giao diện trong tất cả các tính hữu dụng của chúng, được sử dụng nhiều nhất trong thành phần.
Maciej Sitko

3

Các giao diện là để áp dụng kết nối giữa các lớp khác nhau. ví dụ, bạn có một lớp học cho xe hơi và một cái cây;

public class Car { ... }

public class Tree { ... }

bạn muốn thêm một chức năng có thể ghi cho cả hai lớp. Nhưng mỗi lớp có cách riêng để đốt. vì vậy bạn chỉ cần thực hiện;

public class Car : IBurnable
{
public void Burn() { ... }
}

public class Tree : IBurnable
{
public void Burn() { ... }
}

2
Câu hỏi ám ảnh tôi trong những ví dụ như thế này là: Tại sao điều này lại giúp ích? Bây giờ tôi có thể chuyển đối số của kiểu IBurnable sang một phương thức và tất cả các lớp có giao diện IBurnable có thể được xử lý không? Giống như ví dụ Pizza tôi tìm thấy. Thật tuyệt khi bạn có thể làm điều này, nhưng tôi không thấy lợi ích, như vậy. Bạn có thể mở rộng ví dụ (vì hiện tại tôi rất dày) hoặc đưa ra một ví dụ không hoạt động với nó (một lần nữa, vì tôi cảm thấy rất dày vào lúc này). Cảm ơn rất nhiều.
Nebelhom

1
Đồng ý. Giao diện = "Có thể làm". Lớp / Tóm tắt Calss = "Là A"
Peter.Wang

3

Bạn sẽ nhận được giao diện, khi bạn sẽ cần chúng :) Bạn có thể nghiên cứu các ví dụ, nhưng bạn cần Aha! hiệu quả để thực sự có được chúng.

Bây giờ bạn đã biết giao diện là gì, chỉ cần mã mà không có chúng. Sớm hay muộn bạn sẽ gặp phải một vấn đề, trong đó việc sử dụng các giao diện sẽ là điều tự nhiên nhất để làm.


3

Tôi ngạc nhiên khi không có nhiều bài viết chứa một lý do quan trọng nhất cho giao diện: Mẫu thiết kế . Đó là bức tranh lớn hơn trong việc sử dụng các hợp đồng, và mặc dù đó là cách trang trí cú pháp cho mã máy (thành thật mà nói, trình biên dịch có thể chỉ bỏ qua chúng), sự trừu tượng và giao diện là mấu chốt cho OOP, sự hiểu biết của con người và kiến ​​trúc hệ thống phức tạp.

Chúng ta hãy mở rộng sự tương tự của pizza để nói một bữa ăn đầy đủ 3 món. Chúng tôi vẫn sẽ có Prepare()giao diện cốt lõi cho tất cả các loại thực phẩm của chúng tôi, nhưng chúng tôi cũng có các tuyên bố trừu tượng cho các lựa chọn khóa học (món khai vị, món chính, món tráng miệng) và các đặc tính khác nhau cho các loại thực phẩm (mặn / ngọt, chay / không chay, gluten miễn phí vv).

Dựa trên các thông số kỹ thuật này, chúng tôi có thể triển khai mẫu Tóm tắt của Nhà máy để khái niệm toàn bộ quá trình, nhưng sử dụng các giao diện để đảm bảo rằng chỉ có các nền tảng là cụ thể. Mọi thứ khác có thể trở nên linh hoạt hoặc khuyến khích tính đa hình, nhưng vẫn duy trì sự đóng gói giữa các lớp khác nhau Coursethực hiện ICoursegiao diện.

Nếu tôi có nhiều thời gian hơn, tôi muốn đưa ra một ví dụ đầy đủ về điều này hoặc ai đó có thể mở rộng điều này cho tôi, nhưng tóm lại, giao diện C # sẽ là công cụ tốt nhất để thiết kế loại hệ thống này.


1
Câu trả lời này xứng đáng nhiều điểm hơn! Các giao diện tỏa sáng khi được sử dụng trong các mẫu được thiết kế, ví dụ Mẫu trạng thái. Xem plus.google.com/+ZoranHorvat- Lập trình để biết thêm thông tin
Alex Nolasco

3

Đây là một giao diện cho các đối tượng có hình chữ nhật:

interface IRectangular
{
    Int32 Width();
    Int32 Height();
}

Tất cả những gì nó đòi hỏi là bạn thực hiện các cách để truy cập chiều rộng và chiều cao của đối tượng.

Bây giờ hãy xác định một phương thức sẽ hoạt động trên bất kỳ đối tượng nào IRectangular:

static class Utils
{
    public static Int32 Area(IRectangular rect)
    {
        return rect.Width() * rect.Height();
    }
}

Điều đó sẽ trả về diện tích của bất kỳ đối tượng hình chữ nhật.

Hãy thực hiện một lớp SwimmingPoolhình chữ nhật:

class SwimmingPool : IRectangular
{
    int width;
    int height;

    public SwimmingPool(int w, int h)
    { width = w; height = h; }

    public int Width() { return width; }
    public int Height() { return height; }
}

Và một lớp khác Housecũng là hình chữ nhật:

class House : IRectangular
{
    int width;
    int height;

    public House(int w, int h)
    { width = w; height = h; }

    public int Width() { return width; }
    public int Height() { return height; }
}

Do đó, bạn có thể gọi Areaphương thức này ở nhà hoặc bể bơi:

var house = new House(2, 3);

var pool = new SwimmingPool(3, 4);

Console.WriteLine(Utils.Area(house));
Console.WriteLine(Utils.Area(pool));

Theo cách này, các lớp của bạn có thể "kế thừa" hành vi (phương thức tĩnh) từ bất kỳ số lượng giao diện nào.


3

Một giao diện xác định hợp đồng giữa nhà cung cấp một chức năng nhất định và người tiêu dùng tương ứng. Nó tách riêng việc thực hiện từ hợp đồng (giao diện). Bạn nên có một cái nhìn về kiến ​​trúc và thiết kế hướng đối tượng. Bạn có thể muốn bắt đầu với wikipedia: http://en.wikipedia.org/wiki/Interface_(computing)


3

Gì ?

Các giao diện về cơ bản là một hợp đồng mà tất cả các lớp thực hiện Giao diện nên tuân theo. Họ trông giống như một lớp học nhưng không có thực hiện.

Trong C#tên Giao diện theo quy ước được xác định bằng Tiền tố 'Tôi' vì vậy nếu bạn muốn có một giao diện được gọi là hình dạng, bạn sẽ khai báo nó làIShapes

Bây giờ tại sao ?

Improves code re-usability

Hãy nói rằng bạn muốn vẽ Circle, Triangle. Bạn có thể nhóm chúng lại với nhau và gọi chúng Shapesvà có phương pháp để vẽ CircleTriangle Nhưng thực hiện cụ thể sẽ là một ý tưởng tồi bởi vì ngày mai bạn có thể quyết định có thêm 2 Shapes Rectangle& Square. Bây giờ khi bạn thêm chúng, rất có thể bạn có thể phá vỡ các phần khác của mã.

Với Giao diện, bạn tách biệt việc thực hiện khác với Hợp đồng


Kịch bản trực tiếp Ngày 1

Bạn đã được yêu cầu tạo một Ứng dụng để Vẽ Vòng tròn và Tam giác

interface IShapes
{
   void DrawShape();
   
 }

class Circle : IShapes
{
    
    public void DrawShape()
    {
        Console.WriteLine("Implementation to Draw a Circle");
    }
}

Class Triangle: IShapes
{
     public void DrawShape()
    {
        Console.WriteLine("Implementation to draw a Triangle");
    }
}
static void Main()
{
     List <IShapes> shapes = new List<IShapes>();
        shapes.Add(new Circle());
        shapes.Add(new Triangle());

        foreach(var shape in shapes)
        {
            shape.DrawShape();
        }
}

Kịch bản trực tiếp ngày 2

Nếu bạn được yêu cầu thêm SquareRectanglevào nó, tất cả những gì bạn phải làm là tạo ra hàm ý cho nó trong class Square: IShapesMainthêm vào danh sáchshapes.Add(new Square());


Tại sao bạn thêm một câu trả lời cho câu hỏi 6 năm tuổi với hàng tá câu trả lời khác được đưa ra hàng trăm lần? Không có gì ở đây chưa được nói.
Jonathon Reinhart

@JonathonReinhart, vâng, tôi đã nghĩ vậy, nhưng sau đó tôi đã suy nghĩ ví dụ này và cách giải thích của nó sẽ giúp liên quan đến một số cơ thể tốt hơn so với những người khác.
Clint

2

Có rất nhiều câu trả lời hay ở đây nhưng tôi muốn thử từ một góc nhìn khác.

Bạn có thể quen thuộc với các nguyên tắc RẮN của thiết kế hướng đối tượng. Tóm tắt:

S - Nguyên tắc trách nhiệm duy nhất O - Nguyên tắc mở / đóng L - Nguyên tắc thay thế Liskov I - Nguyên tắc phân chia giao diện D - Nguyên tắc đảo ngược phụ thuộc

Thực hiện theo các nguyên tắc RẮN giúp tạo ra mã sạch, được bao bọc tốt, gắn kết và kết nối lỏng lẻo. Cho rằng:

"Quản lý phụ thuộc là thách thức chính trong phần mềm ở mọi quy mô" (Donald Knuth)

sau đó bất cứ điều gì giúp quản lý phụ thuộc là một chiến thắng lớn. Các giao diện và Nguyên tắc đảo ngược phụ thuộc thực sự giúp tách mã khỏi các phụ thuộc vào các lớp cụ thể, do đó mã có thể được viết và suy luận về các hành vi thay vì triển khai. Điều này giúp chia mã thành các thành phần có thể được soạn thảo trong thời gian chạy thay vì biên dịch thời gian và cũng có nghĩa là các thành phần đó có thể dễ dàng cắm vào và ra mà không phải thay đổi phần còn lại của mã.

Các giao diện trợ giúp đặc biệt với Nguyên tắc đảo ngược phụ thuộc, trong đó mã có thể được thành phần thành một tập hợp các dịch vụ, với mỗi dịch vụ được mô tả bởi một giao diện. Các dịch vụ sau đó có thể được "chèn" vào các lớp trong thời gian chạy bằng cách chuyển chúng vào dưới dạng tham số của hàm tạo. Kỹ thuật này thực sự trở nên quan trọng nếu bạn bắt đầu viết các bài kiểm tra đơn vị và sử dụng phát triển theo hướng kiểm tra. Thử nó! Bạn sẽ nhanh chóng hiểu làm thế nào các giao diện giúp phân tách mã thành các phần có thể quản lý có thể được kiểm tra riêng lẻ.


1

Mục đích chính của các giao diện là nó tạo ra một hợp đồng giữa bạn và bất kỳ lớp nào khác thực hiện giao diện đó làm cho mã của bạn tách rời và cho phép mở rộng.


1

Therese đang hỏi những ví dụ thực sự tuyệt vời.

Một trường hợp khác, trong trường hợp câu lệnh chuyển đổi, bạn không còn có nhu cầu duy trì và chuyển đổi mỗi khi bạn muốn rio thực hiện một nhiệm vụ theo một cách cụ thể.

Trong ví dụ về pizza của bạn, nếu muốn làm một chiếc bánh pizza, giao diện là tất cả những gì bạn cần, từ đó mỗi chiếc bánh pizza sẽ tự xử lý logic của nó.

Điều này giúp giảm khớp nối và độ phức tạp chu kỳ. Bạn vẫn phải thực hiện logic nhưng sẽ có ít bạn phải theo dõi trong bức tranh rộng hơn.

Đối với mỗi chiếc bánh pizza, bạn có thể theo dõi thông tin cụ thể cho chiếc bánh pizza đó. Những loại pizza khác không có vấn đề gì vì chỉ những loại pizza khác mới cần biết.


1

Cách đơn giản nhất để suy nghĩ về các giao diện là nhận ra ý nghĩa kế thừa. Nếu lớp CC kế thừa lớp C, có nghĩa là cả hai:

  1. Lớp CC có thể sử dụng bất kỳ thành viên nào được bảo vệ hoặc công khai của lớp C như thể chúng là của riêng chúng, và do đó chỉ cần thực hiện những thứ không tồn tại trong lớp cha.
  2. Một tham chiếu đến CC có thể được truyền hoặc gán cho một thường trình hoặc biến dự kiến ​​tham chiếu đến C.

Hai chức năng của thừa kế trong một số ý nghĩa độc lập; mặc dù thừa kế áp dụng đồng thời cả hai, nhưng cũng có thể áp dụng cái thứ hai mà không cần cái thứ nhất. Điều này rất hữu ích vì việc cho phép một đối tượng kế thừa các thành viên từ hai hoặc nhiều lớp không liên quan sẽ phức tạp hơn nhiều so với việc cho phép một loại điều có thể thay thế cho nhiều loại.

Một giao diện có phần giống như một lớp cơ sở trừu tượng, nhưng có một điểm khác biệt chính: một đối tượng kế thừa một lớp cơ sở không thể kế thừa bất kỳ lớp nào khác. Ngược lại, một đối tượng có thể thực hiện một giao diện mà không ảnh hưởng đến khả năng kế thừa bất kỳ lớp mong muốn nào hoặc thực hiện bất kỳ giao diện nào khác.

Một tính năng hay của điều này (không được sử dụng đúng mức trong khung .net, IMHO) là chúng có thể chỉ ra khai báo những điều mà một đối tượng có thể làm. Ví dụ, một số đối tượng sẽ muốn đối tượng nguồn dữ liệu mà từ đó họ có thể truy xuất mọi thứ theo chỉ mục (có thể với Danh sách), nhưng họ sẽ không cần lưu trữ bất cứ thứ gì ở đó. Các thói quen khác sẽ cần một đối tượng lưu trữ dữ liệu nơi họ có thể lưu trữ những thứ không theo chỉ mục (như với Collection.Add), nhưng họ sẽ không cần phải đọc lại bất cứ điều gì. Một số loại dữ liệu sẽ cho phép truy cập theo chỉ mục, nhưng sẽ không cho phép viết; những người khác sẽ cho phép viết, nhưng sẽ không cho phép truy cập theo chỉ mục. Một số, tất nhiên, sẽ cho phép cả hai.

Nếu ReadableBy Index và Appendable là các lớp cơ sở không liên quan, thì không thể định nghĩa một loại có thể được truyền cho cả những thứ mong đợi ReadableBy Index và những thứ mong đợi có thể bổ sung. Người ta có thể cố gắng giảm thiểu điều này bằng cách có nguồn gốc ReadableBy Index hoặc Appendable từ cái khác; lớp dẫn xuất sẽ phải cung cấp các thành viên công cộng có sẵn cho cả hai mục đích, nhưng cảnh báo rằng một số thành viên công cộng có thể không thực sự hoạt động. Một số lớp và giao diện của Microsoft làm điều đó, nhưng điều đó khá khó hiểu. Một cách tiếp cận sạch hơn là có các giao diện cho các mục đích khác nhau, và sau đó có các đối tượng triển khai giao diện cho những việc họ thực sự có thể làm. Nếu một giao diện có IReadableByIndex và một giao diện khác IAppendable,


1

Các giao diện cũng có thể được xâu chuỗi để tạo giao diện khác. Khả năng triển khai nhiều Giao diện này mang lại cho nhà phát triển lợi thế của việc thêm chức năng vào các lớp của họ mà không phải thay đổi chức năng lớp hiện tại (Nguyên tắc RẮN)

O = "Các lớp nên được mở rộng nhưng đóng để sửa đổi"


1

Đối với tôi một lợi thế / lợi ích của một giao diện là nó linh hoạt hơn một lớp trừu tượng. Vì bạn chỉ có thể kế thừa 1 lớp trừu tượng nhưng bạn có thể thực hiện nhiều giao diện, các thay đổi đối với một hệ thống kế thừa một lớp trừu tượng ở nhiều nơi trở nên có vấn đề. Nếu nó được kế thừa ở 100 vị trí, một thay đổi yêu cầu thay đổi cho tất cả 100. Nhưng, với giao diện, bạn có thể đặt thay đổi mới trong giao diện mới và chỉ cần sử dụng giao diện đó khi cần (Giao diện Seq. Từ RẮN). Ngoài ra, việc sử dụng bộ nhớ có vẻ như sẽ ít hơn với giao diện vì một đối tượng trong ví dụ giao diện chỉ được sử dụng một lần trong bộ nhớ mặc dù có bao nhiêu nơi thực hiện giao diện.


1

Các giao diện được sử dụng để điều khiển tính nhất quán, theo cách được ghép lỏng lẻo, điều này làm cho nó khác với lớp trừu tượng được liên kết chặt chẽ. Đó là lý do tại sao nó cũng thường được định nghĩa là một hợp đồng. được xác định bởi giao diện và không có yếu tố cụ thể trong đó.

Tôi sẽ chỉ đưa ra một ví dụ được hỗ trợ bởi đồ họa bên dưới.

Hãy tưởng tượng trong một nhà máy có 3 loại máy. Máy hình chữ nhật, máy hình tam giác và máy đa giác. Thời gian cạnh tranh và bạn muốn hợp lý hóa việc đào tạo người vận hành. Bạn chỉ muốn đào tạo chúng theo một phương pháp khởi động và dừng máy để bạn Có nút khởi động màu xanh lá cây và nút dừng màu đỏ. Bây giờ trên 3 máy khác nhau, bạn có cách khởi động và dừng 3 loại máy khác nhau. Hãy tưởng tượng những máy này là các lớp và các lớp cần có phương thức khởi động và dừng, bạn thế nào sẽ dẫn đến sự nhất quán trong các lớp này có thể rất khác nhau? Giao diện là câu trả lời.

nhập mô tả hình ảnh ở đây

Một ví dụ đơn giản để giúp bạn hình dung, người ta có thể hỏi tại sao không sử dụng lớp trừu tượng? Với giao diện, các đối tượng không phải liên quan trực tiếp hoặc kế thừa và bạn vẫn có thể điều khiển tính nhất quán giữa các lớp khác nhau.

public interface IMachine
{
    bool Start();
    bool Stop();
}

public class Car : IMachine
{
    public bool Start()
    {
        Console.WriteLine("Car started");
        return true;
    }

    public bool Stop()
    {
        Console.WriteLine("Car stopped");
        return false;
    }
}

public class Tank : IMachine
{
    public bool Start()
    {
        Console.WriteLine("Tank started");
        return true;
    }

    public bool Stop()
    {
        Console.WriteLine("Tank stopped");
        return false;
    }
}

class Program
{
    static void Main(string[] args)
    {
        var car = new Car();
        car.Start();
        car.Stop();

        var tank = new Tank();
        tank.Start();
        tank.Stop();

    }
}

1
class Program {
    static void Main(string[] args) {
        IMachine machine = new Machine();
        machine.Run();
        Console.ReadKey();
    }

}

class Machine : IMachine {
    private void Run() {
        Console.WriteLine("Running...");
    }
    void IMachine.Run() => Run();
}

interface IMachine
{
    void Run();
}

Hãy để tôi mô tả điều này bằng một quan điểm khác. Hãy tạo một câu chuyện theo ví dụ mà tôi đã trình bày ở trên;

Chương trình, Máy và IMachine là những diễn viên trong câu chuyện của chúng tôi. Chương trình muốn chạy nhưng nó không có khả năng đó và Máy biết cách chạy. Máy và IMachine là những người bạn tốt nhất nhưng Chương trình không nói về các điều khoản với Máy. Vì vậy, Chương trình và IMachine đã thỏa thuận và quyết định rằng IMachine sẽ nói với Chương trình cách chạy bằng cách tìm Máy (giống như một gương phản xạ).

Và Chương trình học cách chạy nhờ sự giúp đỡ của IMachine.

Giao diện cung cấp thông tin liên lạc và phát triển các dự án kết nối lỏng lẻo.

PS: Tôi có phương pháp của lớp cụ thể là riêng tư. Mục đích của tôi ở đây là để đạt được sự kết hợp lỏng lẻo bằng cách ngăn chặn truy cập các thuộc tính và phương thức lớp cụ thể, và chỉ cho phép cách tiếp cận chúng thông qua các giao diện. (Vì vậy, tôi xác định rõ ràng các phương thức của giao diện).


1

Tôi biết tôi đến rất muộn .. (gần chín năm), nhưng nếu ai đó muốn giải thích nhỏ thì bạn có thể làm điều này:

Nói một cách đơn giản, bạn sử dụng Giao diện khi bạn biết đối tượng có thể làm gì hoặc chức năng nào chúng ta sẽ thực hiện trên một đối tượng .. Ví dụ Chèn, Cập nhật và Xóa.

interface ICRUD{
      void InsertData(); // will insert data
      void UpdateData(); // will update data
      void DeleteData(); // will delete data
}

Lưu ý quan trọng: giao diện luôn LUÔN công khai.

Hi vọng điêu nay co ich.

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.