Thiết kế giao diện nơi các chức năng cần được gọi theo một trình tự cụ thể


24

Nhiệm vụ là cấu hình một phần cứng trong thiết bị, theo một số đặc điểm kỹ thuật đầu vào. Điều này cần đạt được như sau:

1) Thu thập thông tin cấu hình. Điều này có thể xảy ra ở những thời điểm và địa điểm khác nhau. Ví dụ, mô-đun A và mô-đun B có thể yêu cầu (tại các thời điểm khác nhau) một số tài nguyên từ mô-đun của tôi. Những "tài nguyên" đó thực sự là cấu hình.

2) Sau khi rõ ràng rằng sẽ không có thêm yêu cầu nào được thực hiện, một lệnh khởi động, đưa ra một bản tóm tắt các tài nguyên được yêu cầu, cần phải được gửi đến phần cứng.

3) Chỉ sau đó, có thể (và phải) cấu hình chi tiết của các tài nguyên nói trên được thực hiện.

4) Ngoài ra, chỉ sau 2), có thể (và phải) định tuyến các tài nguyên đã chọn đến người gọi được khai báo.


Một nguyên nhân phổ biến cho các lỗi, ngay cả đối với tôi, người đã viết điều này, đang nhầm lẫn thứ tự này. Những quy ước, thiết kế hoặc cơ chế đặt tên nào tôi có thể sử dụng để làm cho giao diện có thể sử dụng được bởi người lần đầu tiên nhìn thấy mã?


Giai đoạn 1 được gọi là tốt hơn discoveryhay handshake?
rwong

1
Khớp nối tạm thời là một mô hình chống và nên tránh.

1
Tiêu đề của câu hỏi khiến tôi nghĩ rằng bạn có thể quan tâm đến mẫu xây dựng bước .
Joshua Taylor

Câu trả lời:


45

Đây là một thiết kế lại nhưng bạn có thể ngăn chặn việc sử dụng sai nhiều API nhưng không có sẵn bất kỳ phương thức nào không nên gọi.

Ví dụ, thay vì first you init, then you start, then you stop

Hàm tạo của bạn initlà một đối tượng có thể được bắt đầu và starttạo một phiên có thể dừng lại.

Tất nhiên, nếu bạn có một hạn chế đối với một phiên tại một thời điểm, bạn cần xử lý trường hợp ai đó cố gắng tạo một phiên với một phiên đã hoạt động.

Bây giờ áp dụng kỹ thuật đó cho trường hợp của riêng bạn.


zlibjpegliblà hai ví dụ theo mô hình này để khởi tạo. Tuy nhiên, rất nhiều tài liệu là cần thiết để dạy khái niệm cho các nhà phát triển.
rwong

5
Đây chính xác là câu trả lời đúng: nếu thứ tự quan trọng, mỗi hàm trả về một kết quả mà sau đó có thể được gọi để thực hiện bước tiếp theo. Trình biên dịch có thể thực thi các ràng buộc thiết kế.

2
Điều này tương tự như mô hình bước xây dựng ; chỉ trình bày giao diện có ý nghĩa ở một giai đoạn nhất định.
Joshua Taylor

@JoshuaTaylor câu trả lời của tôi là triển khai mẫu xây dựng bước :)
Silviu Burcea

@SilviuBurcea Câu trả lời của bạn không phải là một công cụ xây dựng bước, nhưng tôi sẽ bình luận về nó hơn là ở đây.
Joshua Taylor

19

Bạn có thể có phương thức khởi động trả về một đối tượng là tham số bắt buộc cho cấu hình:

Tài nguyên * MyModule :: GetResource ();
MySession * MyModule :: Startup ();
void Resource :: Configure (phiên MySession *);

Ngay cả khi bạn MySessionchỉ là một cấu trúc trống, điều này sẽ thực thi thông qua loại an toàn mà không có Configure()phương thức nào có thể được gọi trước khi khởi động.


Điều gì ngăn ai đó làm module->GetResource()->Configure(nullptr)?
Svick

@svick: Không có gì, nhưng bạn phải làm điều này một cách rõ ràng. Cách tiếp cận này cho bạn biết những gì nó mong đợi và bỏ qua việc triển khai đó là một quyết định có ý thức. Như với hầu hết các ngôn ngữ lập trình, không ai ngăn bạn tự bắn vào chân mình. Nhưng API luôn luôn tốt để chỉ rõ rằng bạn đang làm như vậy;)
Michael Klement

+1 trông tuyệt vời và đơn giản. Tuy nhiên, tôi có thể thấy một vấn đề. Nếu tôi có các đối tượng a, b, c, d, thì tôi có thể bắt đầu avà sử dụng nó MySessionđể cố gắng sử dụng bnhư một đối tượng đã bắt đầu, trong khi thực tế thì không phải vậy.
Vorac

8

Dựa trên câu trả lời của Cashcow - tại sao bạn phải trình bày một Đối tượng mới cho người gọi, khi bạn có thể trình bày một Giao diện mới? Mô hình Rebrand:

class IStartable     { public: virtual IRunnable      start()     = 0; };
class IRunnable      { public: virtual ITerminateable run()       = 0; };
class ITerminateable { public: virtual void           terminate() = 0; };

Bạn cũng có thể để ITerminatizable triển khai IRunnable, nếu một phiên có thể được chạy nhiều lần.

Đối tượng của bạn:

class Service : IStartable, IRunnable, ITerminateable
{
  public:
    IRunnable      start()     { ...; return this; }
    ITerminateable run()       { ...; return this; }
    void           terminate() { ...; }
}

// And use it like this:
IStartable myService = Service();

// Now you can only call start() via the interface
IRunnable configuredService = myService.start();

// Now you can also call run(), because it is wrapped in the new interface...

Theo cách này, bạn chỉ có thể gọi đúng phương thức, vì ban đầu bạn chỉ có Giao diện IStartable-Giao diện và sẽ chỉ có thể truy cập Phương thức run () khi bạn đã gọi start (); Nhìn từ bên ngoài, nó trông giống như một mẫu có nhiều lớp và Đối tượng, nhưng lớp bên dưới vẫn là một lớp, luôn được tham chiếu.


1
Lợi thế của việc chỉ có một lớp cơ bản thay vì một vài lớp là gì? Vì đây là sự khác biệt duy nhất với giải pháp tôi đề xuất, tôi sẽ quan tâm đến điểm đặc biệt này.
Michael Le Barbier Grünewald

1
@ MichaelGrünewald Không cần thiết phải thực hiện tất cả các giao diện với một lớp, nhưng đối với một đối tượng kiểu cấu hình, đó có thể là kỹ thuật triển khai đơn giản nhất để chia sẻ dữ liệu giữa các phiên bản của giao diện (nghĩa là vì nó được chia sẻ bởi vì giống nhau vật).
Joshua Taylor

1
Đây thực chất là mô hình xây dựng bước .
Joshua Taylor

@JoshuaTaylor Chia sẻ dữ liệu giữa các phiên bản của giao diện có hai mặt: mặc dù có thể dễ thực hiện hơn, chúng tôi phải cẩn thận không truy cập vào trạng thái không xác định của Wap (như truy cập địa chỉ máy khách của máy chủ không được kết nối). Khi OP nhấn mạnh vào khả năng sử dụng giao diện, chúng ta có thể đánh giá hai cách tiếp cận như nhau. Thnak bạn đã trích dẫn mô hình xây dựng bước trên thang điểm BT BT.
Michael Le Barbier Grünewald

1
@ MichaelGrünewald Nếu bạn chỉ tương tác với đối tượng thông qua giao diện cụ thể được chỉ định tại một điểm nhất định, không nên có bất kỳ cách nào (không truyền, v.v.) để truy cập trạng thái đó.
Joshua Taylor

2

Có rất nhiều cách tiếp cận hợp lệ để giải quyết vấn đề của bạn. Basile Starynkevitch đã đề xuất một cách tiếp cận không quan liêu, không để lại cho bạn một giao diện đơn giản và dựa vào lập trình viên bằng cách sử dụng giao diện một cách thích hợp. Trong khi tôi thích cách tiếp cận này, tôi sẽ trình bày một cách khác có nhiều tính năng hơn nhưng cho phép trình biên dịch bắt được một số lỗi.

  1. Xác định các trạng thái khác nhau thiết bị của bạn có thể ở, như Uninitialised, Started, Configuredvà vân vân. Danh sách này phải là hữu hạn.¹

  2. Đối với mỗi trạng thái, xác định structviệc giữ thông tin bổ sung cần thiết có liên quan đến trạng thái đó, ví dụ DeviceUninitialised, DeviceStartedv.v.

  3. Đóng gói tất cả các nghiệm thức trong một đối tượng DeviceStrategytrong đó các phương thức sử dụng các cấu trúc được xác định trong 2. làm đầu vào và đầu ra. Do đó, bạn có thể có một DeviceStarted DeviceStrategy::start (DeviceUninitalised dev)phương thức (hoặc bất cứ điều gì tương đương có thể theo các quy ước dự án của bạn).

Với phương pháp này, một chương trình hợp lệ phải gọi một số phương thức trong chuỗi được thi hành bởi các nguyên mẫu phương thức.

Các trạng thái khác nhau là các đối tượng không liên quan, điều này là do nguyên tắc thay thế. Nếu nó hữu ích cho bạn khi các cấu trúc này có chung một tổ tiên chung, hãy nhớ rằng mẫu khách truy cập có thể được sử dụng để khôi phục loại cụ thể của thể hiện của một lớp trừu tượng.

Trong khi tôi mô tả trong 3. một DeviceStrategylớp duy nhất , có những tình huống bạn có thể muốn phân chia chức năng mà nó cung cấp trên một số lớp.

Để tóm tắt chúng, những điểm chính của thiết kế tôi đã mô tả là:

  1. Do nguyên tắc thay thế, các đối tượng đại diện cho trạng thái thiết bị phải khác biệt và không có quan hệ thừa kế đặc biệt.

  2. Đóng gói các phương pháp điều trị thiết bị trong các đối tượng bắt đầu thay vì trong các đối tượng đại diện cho chính các thiết bị, sao cho mỗi trạng thái thiết bị hoặc thiết bị chỉ nhìn thấy chính nó và chiến lược nhìn thấy tất cả chúng và thể hiện sự chuyển tiếp có thể giữa chúng.

Tôi sẽ thề rằng tôi đã thấy một lần mô tả về việc triển khai máy khách telnet theo những dòng này, nhưng tôi không thể tìm lại được. Nó sẽ là một tài liệu tham khảo rất hữu ích!

: Đối với điều này, hãy làm theo trực giác của bạn hoặc tìm các lớp phương thức tương đương trong triển khai thực tế của bạn cho phương thức mối quan hệ “₁ phương thức “iff. sử dụng chúng trên cùng một đối tượng là hợp lệ - giả sử bạn có một đối tượng lớn gói gọn tất cả các phương pháp điều trị trên thiết bị của bạn. Cả hai phương pháp liệt kê các quốc gia đều cho kết quả tuyệt vời.


1
Thay vì xác định các cấu trúc riêng biệt, có thể đủ để xác định các giao diện cần thiết mà một đối tượng ở mỗi giai đoạn sẽ xuất hiện. Sau đó, đó là mô hình xây dựng bước .
Joshua Taylor

2

Sử dụng một mô hình xây dựng.

Có một đối tượng có các phương thức cho tất cả các hoạt động bạn đã đề cập ở trên. Tuy nhiên, nó không thực hiện các hoạt động này ngay lập tức. Nó chỉ nhớ mỗi hoạt động cho sau này. Bởi vì các hoạt động không được thực hiện ngay lập tức, thứ tự mà bạn chuyển chúng cho nhà xây dựng không thành vấn đề.

Sau khi bạn xác định tất cả các hoạt động trên trình xây dựng, bạn gọi một execute-method. Khi phương thức này được gọi, nó thực hiện tất cả các bước bạn đã liệt kê ở trên theo đúng thứ tự với các thao tác bạn đã lưu trữ ở trên. Phương pháp này cũng là một nơi tốt để thực hiện một số kiểm tra vệ sinh kéo dài hoạt động (như cố gắng định cấu hình tài nguyên chưa được thiết lập) trước khi ghi chúng vào phần cứng. Điều này có thể giúp bạn tránh làm hỏng phần cứng với cấu hình vô nghĩa (trong trường hợp phần cứng của bạn dễ bị ảnh hưởng).


1

Bạn chỉ cần ghi lại chính xác cách sử dụng giao diện và đưa ra một ví dụ hướng dẫn.

Bạn cũng có thể có một biến thể thư viện gỡ lỗi mà một số kiểm tra thời gian chạy.

Có lẽ việc xác định và lập hồ sơ một cách chính xác một số quy ước đặt tên (ví dụ preconfigure*, startup*, postconfigure*, run*....)

BTW, rất nhiều giao diện hiện có theo một mẫu tương tự (ví dụ: bộ công cụ X11).


Một sơ đồ chuyển trạng thái, tương tự như vòng đời hoạt động của ứng dụng Android , có thể cần thiết để truyền đạt thông tin.
rwong

1

Đây thực sự là một loại lỗi phổ biến và xảo quyệt, bởi vì trình biên dịch chỉ có thể thực thi các điều kiện cú pháp, trong khi bạn cần các chương trình máy khách của mình phải "đúng ngữ pháp".

Thật không may, các quy ước đặt tên gần như hoàn toàn không hiệu quả đối với loại lỗi này. Nếu bạn thực sự muốn khuyến khích mọi người không làm những điều phi ngôn ngữ, bạn nên đưa ra một đối tượng lệnh thuộc loại nào đó phải được khởi tạo với các giá trị cho các điều kiện tiên quyết, để họ không thể thực hiện các bước không theo thứ tự.


Bạn có ý nghĩa gì đó như thế này ?
Vorac

1
public class Executor {

private Executor() {} // helper class

  public void execute(MyStepsRunnable r) {
    r.step1();
    r.step2();
    r.step3();
  }
}

interface MyStepsRunnable {

  void step1();
  void step2();
  void step3();
}

Sử dụng mẫu này, bạn chắc chắn rằng bất kỳ người triển khai nào cũng sẽ thực hiện theo thứ tự chính xác này. Bạn có thể tiến thêm một bước và tạo ExecutorFactory để xây dựng Executor với các đường dẫn thực thi tùy chỉnh.


Trong một bình luận khác, bạn gọi đây là một bước thực hiện của trình xây dựng, nhưng thực tế không phải vậy. Nếu bạn có một phiên bản MyStepsRunnable, thì bạn có thể gọi bước 3 trước bước 1. Việc triển khai trình xây dựng bước sẽ phù hợp hơn với các dòng của ideone.com/UDECgY . Ý tưởng chỉ nhận được một cái gì đó với một bước 2 bằng cách chạy bước 1. Do đó, bạn buộc phải gọi các phương thức theo đúng thứ tự. Ví dụ: xem stackoverflow.com/q/17256627/1281433 .
Joshua Taylor

Bạn có thể chuyển đổi nó thành một lớp trừu tượng với các phương thức được bảo vệ (hoặc thậm chí mặc định) để hạn chế cách sử dụng nó. Bạn sẽ bị buộc phải sử dụng người thực thi, nhưng tôi có rằng có thể có một hoặc hai lỗ hổng với việc thực hiện hiện tại.
Silviu Burcea

Điều đó vẫn không làm cho nó trở thành một người xây dựng bước. Trong mã của bạn, người dùng không thể làm gì để chạy mã giữa các bước khác nhau. Ý tưởng không chỉ là mã trình tự (bất kể là công khai hay riêng tư, hay được gói gọn). Như mã của bạn cho thấy, điều đó đủ dễ thực hiện với đơn giản step1(); step2(); step3();. Quan điểm của trình xây dựng bước là đưa ra một API hiển thị một số bước và để thực thi trình tự mà chúng được gọi. Nó không nên ngăn lập trình viên làm những việc khác giữa các bước.
Joshua Taylor
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.