Cách thực thi giao diện để hành xử theo một cách nhất định


8

Giả sử bạn có giao diện sau

public interface IUserRepository
{
    User GetByID(int userID);
}

Làm thế nào bạn sẽ thực thi những người triển khai giao diện này để ném ngoại lệ nếu không tìm thấy người dùng?

Tôi nghi ngờ không thể làm một mình với mã, vậy làm thế nào để bạn thực thi những người triển khai thực hiện hành vi dự định? Có thể thông qua mã, tài liệu, vv?

Trong ví dụ này, việc triển khai cụ thể dự kiến ​​sẽ ném UserNotFoundException

public class SomeClass
{
    private readonly IUserRepository _userRepository;

    public SomeClass(IUserRepository userRepository)
    {
        _userRepository = userRepository;
    }

    public void DisplayUser()
    {
        try 
        {
            var user = _userRepository.GetByID(5);
            MessageBox.Show("Hello " + user.Name);
        }
        catch (UserNotFoundException)
        {
            MessageBox.Show("User not found");
        }
    }
}

3
Đây thực sự là một câu hỏi rất thú vị. Tôi có cảm giác câu trả lời đúng có thể liên quan đến Thuộc tính hoặc hack IL. Nói chung, tôi cho rằng việc thử nó là một lựa chọn sai lầm, bởi vì mỗi người triển khai sẽ có thể đưa ra các loại ngoại lệ của riêng họ, nhưng nó vẫn hữu dụng. Có lẽ bạn chỉ cần sử dụng một lớp trừu tượng với một phương thức mẫu?
Magus

15
Công nghệ không giải quyết vấn đề con người. Nếu mọi người vi phạm giao diện, hãy đánh họ bằng gậy cho đến khi họ dừng lại.
Telastyn

1
@Telastyn Có lẽ tôi nên xây dựng một chương trình đánh bại mọi người bằng gậy thay thế.
Matthew

2
@Doval: Vậy làm thế nào để Giao diện biết liệu người dùng hợp lệ có tồn tại hay không mà không cần triển khai sao lưu?
Robert Harvey

1
.... .
Telastyn

Câu trả lời:


9

Đây là một tính năng ngôn ngữ đã bị bỏ qua có chủ ý từ C # . Nói một cách đơn giản, hoàn toàn có thể xảy IUserRepository.GetByIDra lỗi vì một số lý do khác hoàn toàn ngoài việc không tìm thấy người dùng, vì vậy bạn không muốn yêu cầu một lỗi cụ thể khi điều đó không thể xảy ra. Bạn có hai lựa chọn nếu vì bất kỳ lý do gì bạn muốn thực thi hành vi này:

  1. Xác định Userlớp để nó tự ném ngoại lệ nếu nó được kích hoạt không đúng cách.
  2. Viết các bài kiểm tra đơn vị cho bài kiểm tra IUserRepositoryrõ ràng cho hành vi này.

Lưu ý rằng cả hai tùy chọn này đều không "bao gồm nó trong tài liệu." Tốt nhất, bạn nên làm điều đó bằng mọi cách, đặc biệt là tài liệu là nơi bạn có thể giải thích lý do tại sao bạn muốn thực thi một loại lỗi cụ thể.


2
Ngay cả với các ngoại lệ được kiểm tra, bạn không thể buộc người thực hiện ném ngoại lệ đúng vào đúng thời điểm.
Svick

7

Không có cách nào để yêu cầu triển khai ném ngoại lệ qua giao diện, ngay cả trong các ngôn ngữ như Java nơi bạn có thể tuyên bố rằng một phương thức có thể ném ngoại lệ.

Có thể có một cách để đảm bảo (ở một mức độ nào đó nhưng không hoàn toàn) rằng một ngoại lệ được ném ra. Bạn có thể tạo một triển khai trừu tượng của giao diện của bạn. Sau đó, bạn có thể triển khai GetUserphương thức là cuối cùng trong lớp trừu tượng và sử dụng mẫu chiến lược để gọi một thành viên khác, được bảo vệ của lớp con và ném một ngoại lệ nếu nó trả về bất cứ thứ gì ngoài người dùng hợp lệ (như null). Điều này vẫn có thể rơi xuống nếu, giả sử, nhà phát triển khác trả về một loại đối tượng null User, nhưng họ thực sự sẽ phải làm việc để lật đổ ý định ở đây. Họ cũng có thể chỉ thực hiện lại giao diện của bạn, cũng rất tệ, vì vậy bạn có thể xem xét thay thế hoàn toàn giao diện bằng lớp trừu tượng.

(Kết quả tương tự có thể đạt được bằng cách sử dụng ủy quyền thay vì phân lớp với thứ gì đó giống như trang trí gói.)

Một tùy chọn khác có thể là tạo ra một bộ kiểm tra sự phù hợp mà tất cả các mã thực hiện phải vượt qua để được đưa vào. Hiệu quả của việc này phụ thuộc vào mức độ kiểm soát của bạn đối với liên kết của mã khác với mã của bạn.

Tôi cũng đồng ý với những người khác rằng tài liệu rõ ràng và thông tin liên lạc được đưa ra khi một yêu cầu như thế này được mong đợi nhưng không thể được thực thi hoàn toàn trong mã.


Mã ví dụ:

Phương pháp phân lớp:

public abstract class ExceptionalUserRepository : IUserRepository
{
    public sealed User GetUser(int user_id)
    {
        User u = FindUserByID(user_id);
        if(u == null)
        {
            throw new UserNotFoundException();
        }
        return u;
    }

    // subclasses implement this method instead
    protected abstract User FindUserByID(int user_id);
    // More code here
}

Phương pháp trang trí:

public sealed class DecoratedUserRepository : IUserRepository
{
    private readonly IUserRepository _userRepository;

    public DecoratedUserRepository(IUserRepository userRepository)
    {
        _userRepository = userRepository;
    }

    public User GetUser(int user_id)
    {
        User u = _userRepository.GetUser(user_id);
        if(u == null)
        {
            throw new UserNotFoundException();
        }
        return u;
    }

    // More code here
}

public class SomeClass
{
    private readonly IUserRepository _userRepository;

    // They now *have* to pass in exactly what you want
    public SomeClass(DecoratedUserRepository userRepository)
    {
        _userRepository = userRepository;
    }
    // More code
}

Một điểm nhanh cuối cùng tôi muốn làm cho tôi quên trước đó là bằng cách thực hiện bất kỳ điều nào trong số này, bạn đang buộc mình phải thực hiện cụ thể hơn, có nghĩa là các nhà phát triển triển khai có được sự tự do ít hơn nhiều.


3

Nếu bạn làm việc cùng với các nhà phát triển mà bạn muốn thực hiện hành vi ném ngoại lệ, thì bạn có thể viết những điều không mong muốn (cho họ) để kiểm tra xem có ngoại lệ nào được ném hay không nếu bạn cố gắng để người dùng không tồn tại.

Do đó, bạn cần phải biết phương pháp nào để kiểm tra. Tôi không quen thuộc với C # nhưng bạn có thể sử dụng sự phản chiếu để tìm kiếm tất cả các lớp triển khai IUserRep repository và tự động kiểm tra chúng để ngay cả khi nhà phát triển thêm một lớp khác, nó sẽ được kiểm tra khi chạy thử.

Nhưng đó chỉ là những gì bạn có thể làm trong thực tế nếu bạn thực sự phải chịu đựng các nhà phát triển thực hiện sai việc triển khai mà bạn phải làm việc. Trong các giao diện lý thuyết không phải để xác định cách thức logic kinh doanh phải được thực hiện.

Tôi đang mong chờ câu trả lời khác vì đây thực sự là một câu hỏi thú vị!


3

Bạn không thể. Đó là điều về giao diện - chúng cho phép bất cứ ai thực hiện chúng bất cứ lúc nào. Có vô số triển khai tiềm năng cho giao diện của bạn và bạn không thể ép buộc bất kỳ trong số chúng là chính xác. Bằng cách chọn một giao diện, bạn đã mất quyền thực thi rằng việc triển khai cụ thể được sử dụng trên toàn hệ thống. Heck, những gì bạn có thậm chí không phải là một giao diện, đó là một chức năng. Bạn đang viết mã vượt qua các chức năng xung quanh.

Nếu bạn cần đi theo lộ trình này thì bạn sẽ chỉ cần ghi lại rõ ràng thông số kỹ thuật mà việc triển khai phải tuân thủ và tin tưởng rằng không ai cố tình phá vỡ thông số kỹ thuật đó.

Cách khác là có một kiểu dữ liệu trừu tượng, dịch thành một lớp bằng các ngôn ngữ giống như Java / C #. Vấn đề là các ngôn ngữ chính có sự hỗ trợ yếu cho các ADT và bạn không thể chuyển đổi việc thực hiện lớp mà không có hệ thống tệp tomfoolery. Nhưng nếu bạn sẵn sàng sống với điều đó, bạn có thể đảm bảo rằng chỉ có một triển khai trong hệ thống. Đó là một bước tiến từ giao diện, nơi mã của bạn có thể bị vỡ bất cứ lúc nào, và khi nó bạn sẽ phải tìm ra thực hiện của giao diện đã phá vỡ mã của bạn và nó đến từ đâu.

EDIT : Lưu ý rằng ngay cả các bản hack IL sẽ không cho phép bạn đảm bảo tính chính xác của các khía cạnh nhất định của việc triển khai. Ví dụ, nó mặc nhiên giả định rằng GetByIDphải trả về giá trị hoặc ném ngoại lệ. Ai đó có thể viết một triển khai chỉ đơn giản là đi vào một vòng lặp vô tận và không. Bạn không thể chứng minh trong thời gian chạy rằng mã lặp vòng mãi mãi - nếu bạn quản lý để làm điều này, bạn đã giải quyết được vấn đề Dừng. Bạn có thể phát hiện các trường hợp tầm thường (ví dụ: nhận ra a while (true) {}) nhưng không phải là một đoạn mã tùy ý.


Chà, một giao diện sẽ ngăn một số triển khai sai: Những giao diện không được gõ (không khớp với các chữ ký phương thức trong giao diện). Nó không thể biểu thị ràng buộc mà OP muốn, nhưng một vài hệ thống có thể làm được điều đó.

@delnan Nếu một đoạn mã có một loại khác, bạn khó có thể gọi nó là một triển khai! Nhưng chúng tôi đang tranh luận về ngữ nghĩa. Tôi hiểu ý bạn.
Doval

Một điều nữa: "Kiểu dữ liệu trừu tượng" là một khái niệm không chính thức giống như tài liệu; để làm cho nó có thể kiểm tra bằng máy, bạn cần xây dựng một hệ thống loại và sử dụng các giao diện tương đương của hệ thống loại đó.

@delnan Đó là một quan niệm sai lầm. Xem phần Hiểu về trừu tượng dữ liệu, xem xét lại . Một ADT trừu tượng hóa dữ liệu như một giao diện, vâng. Nhưng một ADT làm như vậy bằng cách che khuất danh tính của một loại hiện có. Điều này tương đương với một lớp có các trường riêng trong OOP chính thống. Loại bị che khuất là bản ghi có các lĩnh vực bạn đã đặt ở chế độ riêng tư. Mặt khác, một giao diện là một nhóm các chức năng và kiểu triển khai không xuất hiện trong chữ ký. Đây là những gì cho phép thực hiện nhiều và thay thế.
Doval

Đây dường như là một cách sử dụng "kiểu dữ liệu trừu tượng" khá khác so với tôi đã từng sử dụng; những gì tôi và nhiều người khác gọi là kiểu dữ liệu trừu tượng chỉ là một công cụ để nói về các kiểu dữ liệu theo cách thức không biết ngôn ngữ. Những gì bạn mô tả dường như là (các) tính năng hệ thống loại (lớp) diễn giải và thực hiện khái niệm đã nói ở trên về các loại dữ liệu trừu tượng.

2

Không có cách nào buộc 100% hành vi bạn muốn, nhưng sử dụng hợp đồng mã bạn có thể nói rằng việc triển khai không được trả về null và có lẽ đối tượng Người dùng đã chuyển id (vì vậy một đối tượng có userid 0 sẽ thất bại nếu 1 được thông qua ). Trên hết, việc ghi lại những gì bạn mong đợi về việc triển khai cũng có thể giúp ích.


0

Điều này có lẽ là rất muộn để trả lời câu hỏi này, nhưng muốn chia sẻ suy nghĩ của tôi về điều này. Như tất cả các câu trả lời cho thấy, việc ép buộc với Giao diện là không thể. Ngay cả với hack cũng sẽ khó khăn.

Một cách để đạt được điều này, có lẽ cụ thể đối với .NET là thiết kế giao diện như bên dưới -

public interface IUserRepository
{
    Task<User> GetByID(int userId);
}

Nhiệm vụ sẽ không buộc phải ném một loại Ngoại lệ nhất định, nhưng nó có điều khoản để truyền đạt ý định của tất cả những người thực hiện giao diện này. Các tác vụ trong .NET có mô hình sử dụng nhất định, như Task.Result để truy cập kết quả, Task.Exception nếu có ngoại lệ. Thêm vào đó, điều này cũng sẽ người dùng sử dụng nó không đồng bộ.


0

Bạn không thể làm điều đó với một giao diện, như nhiều người khác đã trả lời.
Nhưng bạn có thể làm điều đó với một lớp cơ sở trừu tượng:

public abstract class UserRepositoryBase
{
  public User GetByID (int userID)  // not virtual because you don't want to allow overriding
  {
    User user = null;
    try
    {
      user = GetByID_EXEC (userID);
    }
    catch (UserNotFoundException)
    {
      throw;  // rethrow exception because it's the correct type.
    }
    catch (Exception)
    {
      // do whatever you want
    }
    if (user != null)
      return user;
    throw new UserNotFoundException ();
  }

  protected abstract User GetByID_EXEC (int userID);
}
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.