Nguyên tắc ít ngạc nhiên nhất (Pola) và giao diện


17

Một phần tư thế kỷ trước khi tôi học C ++, tôi được dạy rằng các giao diện nên được tha thứ và càng nhiều càng không quan tâm đến thứ tự mà các phương thức được gọi vì người tiêu dùng có thể không có quyền truy cập vào nguồn hoặc tài liệu thay cho điều này.

Tuy nhiên, bất cứ khi nào tôi cố vấn các lập trình viên cơ sở và các nhà phát triển cấp cao đã tình cờ nghe thấy tôi, họ đã phản ứng với sự ngạc nhiên khiến tôi tự hỏi liệu đây có thực sự là một điều hay nếu nó không còn thịnh hành.

Rõ ràng như bùn?

Xem xét một giao diện với các phương thức này (để tạo tệp dữ liệu):

OpenFile
SetHeaderString
WriteDataLine
SetTrailerString
CloseFile

Bây giờ bạn tất nhiên có thể chuyển qua các thứ tự này, nhưng nói rằng bạn không quan tâm đến tên tệp (nghĩ a.out) hoặc chuỗi tiêu đề và đoạn giới thiệu được bao gồm, bạn chỉ có thể gọi AddDataLine.

Một ví dụ ít cực đoan hơn có thể là bỏ qua các tiêu đề và đoạn giới thiệu.

Tuy nhiên, một cái khác có thể là thiết lập chuỗi tiêu đề và đoạn giới thiệu trước khi tệp được mở.

Đây có phải là một nguyên tắc thiết kế giao diện được công nhận hay chỉ là cách Pola trước khi nó được đặt tên?

NB không bị sa lầy vào các chi tiết vụn vặt của giao diện này, nó chỉ là một ví dụ cho câu hỏi này.


10
Nguyên tắc "ít ngạc nhiên nhất" phổ biến hơn nhiều trong thiết kế giao diện Người dùng so với thiết kế "Giao diện lập trình viên ứng dụng". Lý do là người dùng của một trang web hoặc chương trình không thể đọc bất kỳ hướng dẫn nào trước khi sử dụng nó, trong khi về nguyên tắc, một lập trình viên dự kiến ​​sẽ đọc các tài liệu API trước khi lập trình với chúng.
Kilian Foth


7
@KilianFoth: Tôi khá chắc chắn Wikipedia đã sai về điều này - Pola không chỉ về thiết kế giao diện người dùng, thuật ngữ "nguyên tắc ít bất ngờ nhất" (cũng khá giống nhau) cũng được Bob Martin sử dụng cho chức năng và thiết kế lớp trong Cuốn sách "Mã sạch".
Doc Brown

2
Thông thường, một giao diện bất biến là tốt hơn dù sao. Bạn có thể chỉ định tất cả dữ liệu mà bạn muốn đặt tại thời điểm xây dựng. Không còn mơ hồ và lớp học trở nên đơn giản hơn để viết. (Tất nhiên đôi khi kế hoạch này là không thể.)
usr

4
Không đồng ý hoàn toàn về Pola không áp dụng cho API. Nó áp dụng cho bất cứ điều gì một con người tạo ra cho những người khác. Khi mọi thứ hoạt động như mong đợi, chúng sẽ dễ dàng khái niệm hóa hơn và do đó tạo ra một tải nhận thức thấp hơn, cho phép mọi người làm nhiều việc hơn với ít nỗ lực hơn.
Gort Robot

Câu trả lời:


25

Một cách mà bạn có thể tuân thủ nguyên tắc ít gây ngạc nhiên nhất là xem xét các nguyên tắc khác như ISPSRP , hoặc thậm chí DRY .

Trong ví dụ cụ thể bạn đã đưa ra, đề xuất dường như là có một sự phụ thuộc nhất định của việc đặt hàng để thao tác với tệp; nhưng API của bạn kiểm soát cả quyền truy cập tệp và định dạng dữ liệu, có mùi hơi giống như vi phạm SRP.

Chỉnh sửa / Cập nhật: nó cũng gợi ý rằng chính API đang yêu cầu người dùng vi phạm DRY, bởi vì họ sẽ cần lặp lại các bước tương tự mỗi lần họ sử dụng API .

Xem xét một API thay thế trong đó các hoạt động IO tách biệt với các hoạt động dữ liệu. và nơi API tự 'sở hữu' đơn đặt hàng:

Nội dung

SetHeader( ... )
AddLine( ... )
SetTrailer ( ... )

FileWriter

Open(filename) 
Write(content) throws InvalidContentException
Close()

Với sự phân tách ở trên, ContentBuilderkhông cần thực sự "làm" bất cứ điều gì ngoài việc lưu trữ các dòng / tiêu đề / đoạn giới thiệu (Có thể cũng là một ContentBuilder.Serialize()phương pháp biết thứ tự). Bằng cách tuân theo các nguyên tắc RẮN khác, việc bạn đặt tiêu đề hoặc đoạn giới thiệu trước hoặc sau khi thêm dòng không còn quan trọng nữa, bởi vì không có gì trong ContentBuilderthực tế được ghi vào tệp cho đến khi nó được chuyển đến FileWriter.Write.

Nó cũng có thêm lợi ích là linh hoạt hơn một chút; ví dụ, có thể hữu ích khi viết nội dung ra bộ ghi chẩn đoán hoặc có thể truyền nội dung đó qua mạng thay vì ghi trực tiếp vào tệp.

Trong khi thiết kế API, bạn cũng nên xem xét báo cáo lỗi, cho dù đó là trạng thái, giá trị trả về, ngoại lệ, gọi lại hay thứ gì khác. Người dùng API có thể sẽ có khả năng phát hiện lập trình mọi vi phạm hợp đồng của mình hoặc thậm chí các lỗi khác mà nó không thể kiểm soát được như lỗi I / O của tệp.


Chính xác những gì tôi đang tìm kiếm - cảm ơn bạn! Từ bài viết của ISP: "(ISP) nói rằng không có khách hàng nào bị buộc phải phụ thuộc vào các phương thức mà nó không sử dụng"
Robbie Dee

5
Đây không phải là một câu trả lời tồi, tuy nhiên, người xây dựng nội dung vẫn có thể được triển khai theo cách thức theo thứ tự các cuộc gọi SetHeaderhoặc AddLinevấn đề. Để loại bỏ sự phụ thuộc thứ tự này không phải là ISP hay SRP, nó chỉ đơn giản là Pola.
Doc Brown

Khi đơn hàng có vấn đề, bạn vẫn có thể đáp ứng Pola bằng cách xác định các hoạt động sao cho việc thực hiện các bước sau yêu cầu giá trị được trả về từ các bước trước đó, do đó thực thi lệnh với hệ thống loại. FileWritersau đó có thể yêu cầu giá trị từ ContentBuilderbước cuối cùng trong Writephương thức để đảm bảo tất cả nội dung đầu vào được hoàn thành, InvalidContentExceptionkhông cần thiết.
Dan Lyons

@DanLyons Tôi cảm thấy khá gần với tình huống mà người hỏi đang cố tránh; nơi người dùng API cần biết hoặc quan tâm đến đơn hàng. Lý tưởng nhất là chính API nên thực thi lệnh, nếu không, nó có khả năng yêu cầu người dùng vi phạm DRY. Đó là lý do để tách ra ContentBuildervà cho phép FileWriter.Writegói gọn chút kiến ​​thức đó. Ngoại lệ sẽ là cần thiết trong trường hợp bất kỳ điều gì xảy ra không phù hợp với nội dung, (ví dụ như tiêu đề bị thiếu). Trả về cũng có thể hoạt động, nhưng tôi không phải là người thích biến ngoại lệ thành mã trả lại.
Ben Cottrell

Nhưng chắc chắn giá trị thêm nhiều ghi chú về DRY và đặt hàng cho câu trả lời.
Ben Cottrell

12

Đây không chỉ là về Pola, mà còn về việc ngăn chặn trạng thái không hợp lệ như là một nguồn có thể có lỗi.

Hãy xem cách chúng tôi có thể cung cấp một số ràng buộc cho ví dụ của bạn mà không cung cấp triển khai cụ thể:

Bước đầu tiên: Không cho phép bất cứ điều gì được gọi, trước khi một tệp được mở.

CreateDataFileInterface
  + OpenFile(filename : string) : DataFileInterface

DataFileInterface
  + SetHeaderString(header : string) : void
  + WriteDataLine(data : string) : void
  + SetTrailerString(trailer : string) : void
  + Close() : void

Bây giờ rõ ràng là CreateDataFileInterface.OpenFilephải được gọi để lấy một DataFileInterfacethể hiện, trong đó dữ liệu thực tế có thể được ghi.

Bước thứ hai: Đảm bảo, các tiêu đề và đoạn giới thiệu luôn được đặt.

CreateDataFileInterface
  + OpenFile(filename : string, header: string, trailer : string) : DataFileInterface

DataFileInterface
  + WriteDataLine(data : string) : void
  + Close() : void

Bây giờ bạn phải cung cấp tất cả các tham số cần thiết trả trước để có được DataFileInterface: tên tệp, tiêu đề và đoạn giới thiệu. Nếu chuỗi trailer không khả dụng cho đến khi tất cả các dòng được ghi, bạn cũng có thể di chuyển tham số này sang Close()(có thể đổi tên phương thức thành WriteTrailerAndClose()) để ít nhất tệp không thể kết thúc mà không có chuỗi trailer.


Để trả lời bình luận:

Tôi thích tách giao diện. Nhưng tôi có xu hướng nghĩ rằng đề xuất của bạn về việc thực thi (ví dụ WriteTrailerAndClose ()) đang phản đối việc vi phạm SRP. (Đây là điều mà tôi đã đấu tranh trong một số dịp, nhưng đề nghị của bạn dường như là một ví dụ có thể.) Bạn sẽ trả lời như thế nào?

Thật. Tôi không muốn tập trung vào ví dụ nhiều hơn cần thiết để đưa ra quan điểm của mình, nhưng đó là một câu hỏi hay. Trong trường hợp này tôi nghĩ rằng tôi sẽ gọi nó Finalize(trailer)và lập luận rằng nó không làm quá nhiều. Viết đoạn giới thiệu và kết thúc chỉ là chi tiết thực hiện. Nhưng nếu bạn không đồng ý hoặc có một tình huống tương tự khác, thì đây là một giải pháp khả thi:

CreateDataFileInterface
  + OpenFile(filename : string, header : string) : IncompleteDataFileInterface

IncompleteDataFileInterface
  + WriteDataLine(data : string) : void
  + FinalizeWithTrailer(trailer : string) : CompleteDataFileInterface

CompleteDataFileInterface
  + Close()

Tôi thực sự sẽ không làm điều đó cho ví dụ này nhưng nó cho thấy cách thực hiện kỹ thuật này.

Nhân tiện, tôi giả định rằng các phương thức thực sự phải được gọi theo thứ tự này, ví dụ để viết tuần tự nhiều dòng. Nếu điều này là không bắt buộc, tôi sẽ luôn thích một người xây dựng, như đề xuất của Ben Cottrel .


1
Bạn có than rơi vào bẫy tôi rõ ràng cảnh báo bạn để tránh ngay từ đầu. Tên tệp không bắt buộc - không phải là tiêu đề và đoạn giới thiệu. Nhưng chủ đề chung của việc chia nhỏ giao diện là một thứ tốt nên +1 :-)
Robbie Dee

Ồ, sau đó tôi đã hiểu lầm bạn, tôi nghĩ rằng đây là mô tả ý định của người dùng, không phải việc thực hiện.
Fabian Schmengler

Tôi thích tách giao diện. Nhưng tôi có khuynh hướng nghĩ rằng đề nghị của bạn về việc thực thi (ví dụ WriteTrailerAndClose()) đang phản đối việc vi phạm SRP. (Đây là điều mà tôi đã đấu tranh trong một số dịp, nhưng đề nghị của bạn dường như là một ví dụ có thể.) Bạn sẽ trả lời như thế nào?
kmote

1
@kmote câu trả lời quá dài cho một nhận xét, hãy xem cập nhật của tôi
Fabian Schmengler

1
Nếu tên tệp là tùy chọn, bạn có thể cung cấp OpenFilequá tải không yêu cầu.
5gon12eder
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.