Tôi có cần sử dụng một giao diện khi chỉ có một lớp sẽ thực hiện nó không?


243

Không phải toàn bộ điểm của một giao diện mà nhiều lớp tuân thủ một tập hợp các quy tắc và triển khai sao?



16
Hoặc để làm cho nó dễ dàng nhất

11
Việc cho phép nhiều lớp thực hiện Giao diện và có mã của bạn phụ thuộc vào các giao diện là ESSENTIAL để cách ly cho thử nghiệm đơn vị. Nếu bạn đang thực hiện kiểm thử đơn vị, bạn sẽ có một lớp khác thực hiện giao diện đó.
Người sử dụng Stuper

2
Reddit thảo luận về câu hỏi này.
yannis

6
Các trường và phương thức công khai là một "giao diện" theo cách riêng của chúng. Nếu sự thiếu đa hình được lên kế hoạch có chủ ý thì không có lý do gì để sử dụng một giao diện. Các thử nghiệm đơn vị khác được đề cập là một kế hoạch sử dụng đa hình.
mike30

Câu trả lời:


205

Nói đúng ra, không có bạn, YAGNI áp dụng. Điều đó nói rằng, thời gian bạn sẽ dành để tạo giao diện là tối thiểu, đặc biệt là nếu bạn có một công cụ tạo mã tiện dụng làm hầu hết công việc cho bạn. Nếu bạn không chắc chắn liệu bạn có cần giao diện hay không, tôi sẽ nói rằng tốt hơn hết là bạn nên ủng hộ định nghĩa của giao diện.

Hơn nữa, sử dụng một giao diện ngay cả đối với một lớp duy nhất sẽ cung cấp cho bạn một triển khai giả khác cho các thử nghiệm đơn vị, một giao diện không được sản xuất. Câu trả lời của Avner Shahar-Kashtan mở rộng về điểm này.


84
Thử nghiệm +1 có nghĩa là bạn hầu như luôn có hai triển khai
jk.

24
@YannisRizos Không đồng ý với quan điểm sau của bạn vì Yagni. Thực tế, việc tạo một giao diện từ các phương thức công khai của lớp là không đáng kể, như việc thay thế CFoo bằng IFoo trong việc tiêu thụ các lớp. Không có điểm nào trong việc viết nó trước nhu cầu.
Dan Neely

5
Tôi vẫn không chắc chắn tôi đang theo dõi lý luận của bạn. Vì các công cụ tạo mã làm cho việc thêm nó sau khi thực tế thậm chí rẻ hơn, tôi thậm chí còn thấy ít lý do hơn để tạo giao diện trước khi bạn có nhu cầu rõ ràng về nó.
Dan Neely

6
Tôi nghĩ rằng Giao diện bị thiếu không phải là một ví dụ tốt cho YAGNI mà là "Cửa sổ bị hỏng" và Tài liệu bị thiếu. Người dùng của lớp thực tế bị buộc phải viết mã chống lại việc thực hiện, thay vì sự trừu tượng như họ cần.
Fabio Fracassi

10
Tại sao bạn lại làm ô nhiễm codebase của mình với hành trình vô nghĩa chỉ để thỏa mãn một số khung thử nghiệm? Ý tôi là nghiêm túc, tôi chỉ là một anh chàng khách hàng JavaScript tự học đang cố gắng loại WTF là sai với các triển khai C # và nhà phát triển Java mà tôi gặp phải khi theo đuổi việc trở thành một nhà tổng quát toàn diện nhưng tại sao lại không ' Họ sẽ gạt IDE ra khỏi tay bạn và không để bạn lấy lại cho đến khi bạn học cách viết mã rõ ràng, dễ đọc khi họ bắt bạn làm điều đó ở trường đại học? Điều đó thật tục tĩu.
Erik Reppen

144

Tôi sẽ trả lời rằng bạn có cần giao diện hay không không phụ thuộc vào số lượng lớp sẽ thực hiện. Các giao diện là một công cụ để xác định hợp đồng giữa nhiều hệ thống con của ứng dụng của bạn; Vì vậy, điều thực sự quan trọng là làm thế nào ứng dụng của bạn được chia thành các hệ thống con. Cần có các giao diện như mặt trước cho các hệ thống con được đóng gói, bất kể có bao nhiêu lớp thực hiện chúng.

Đây là một quy tắc rất hữu ích:

  • Nếu bạn Footham gia lớp học trực tiếp đến lớp BarImpl, bạn mạnh mẽ cam kết sẽ thay đổi Foomỗi khi bạn thay đổi BarImpl. Về cơ bản, bạn coi chúng là một đơn vị mã được chia thành hai lớp.
  • Nếu bạn Footham khảo giao diện Bar , thay vào đó bạn sẽ tự cam kết để tránh thay đổi Fookhi bạn thay đổi BarImpl.

Nếu bạn xác định các giao diện tại các điểm chính trong ứng dụng của mình, bạn sẽ suy nghĩ cẩn thận về các phương pháp mà chúng nên hỗ trợ và chúng không nên, và bạn nhận xét các giao diện rõ ràng để mô tả cách thực hiện được cho là hành xử (và cách không), ứng dụng của bạn sẽ dễ dàng hơn rất nhiều để hiểu vì các giao diện nhận xét sẽ cung cấp một loại đặc điểm kỹ thuật của việc áp dụng một mô tả về cách nó có ý định để hành xử. Điều này giúp đọc mã dễ dàng hơn nhiều (thay vì hỏi "mã này phải làm cái quái gì", bạn có thể hỏi "làm thế nào để mã này làm những gì nó phải làm").

Ngoài tất cả những điều này (hoặc thực sự là vì nó), các giao diện thúc đẩy quá trình biên dịch riêng biệt. Vì các giao diện không quan trọng để biên dịch và có ít phụ thuộc hơn so với triển khai của chúng, điều đó có nghĩa là nếu bạn viết lớp Foođể sử dụng giao diện Bar, bạn thường có thể biên dịch lại BarImplmà không cần biên dịch lại Foo. Trong các ứng dụng lớn, điều này có thể tiết kiệm rất nhiều thời gian.


14
Tôi sẽ nâng cao điều này nhiều hơn một lần, nếu tôi có thể. IMO câu trả lời tốt nhất cho câu hỏi này.
Fabio Fracassi

4
Nếu lớp chỉ thực hiện một vai trò (nghĩa là chỉ có một giao diện được xác định cho nó), thì tại sao không thực hiện điều này thông qua các phương thức công khai / riêng tư?
Matthew Finlay

4
1. Tổ chức mã; có giao diện trong tệp riêng chỉ có chữ ký và nhận xét tài liệu giúp giữ sạch mã. 2. Các khung buộc bạn phải hiển thị các phương thức mà bạn muốn giữ riêng tư (ví dụ: các thùng chứa phụ thuộc thông qua các setters công khai). 3. Biên soạn riêng biệt, như đã đề cập; nếu lớp Foophụ thuộc vào giao diện Barthì BarImplcó thể được sửa đổi mà không cần phải biên dịch lại Foo. 4. Kiểm soát truy cập chi tiết hơn so với các đề nghị công khai / riêng tư (hiển thị cùng một lớp cho hai khách hàng thông qua các giao diện khác nhau).
sacundim

3
Và cuối cùng: 5. Khách hàng (lý tưởng) không quan tâm mô-đun của tôi có bao nhiêu lớp hoặc bao nhiêu, hoặc thậm chí có thể nói. Tất cả những gì họ nên thấy là một tập hợp các loại , và một số nhà máy hoặc mặt tiền để có được quả bóng lăn. Tức là, tôi thường thấy giá trị trong việc gói gọn ngay cả những lớp mà thư viện của bạn bao gồm.
sacundim

4
@sacundim If you make class Foo refer directly to class BarImpl, you're strongly committing yourself to change Foo every time you change BarImplKhi thay đổi BarImpl, những thay đổi có thể tránh được trong Foo bằng cách sử dụng là interface Bargì? Vì miễn là chữ ký và chức năng của một phương thức không thay đổi trong BarImpl, Foo sẽ không yêu cầu thay đổi ngay cả khi không có giao diện (toàn bộ mục đích của giao diện). Tôi đang nói về kịch bản chỉ có một lớp tức là BarImplthực hiện Bar. Đối với kịch bản nhiều lớp, tôi hiểu Nguyên tắc đảo ngược phụ thuộc và cách giao diện của nó hữu ích.
Shishir Gupta

101

Các giao diện được chỉ định để xác định một hành vi, tức là một tập hợp các nguyên mẫu của các hàm / phương thức. Các loại thực hiện giao diện sẽ thực hiện hành vi đó, vì vậy khi bạn xử lý một loại như vậy bạn biết (một phần) hành vi đó có.

Không cần xác định giao diện nếu bạn biết rằng hành vi được xác định bởi giao diện đó sẽ chỉ được sử dụng một lần. KISS (giữ cho nó đơn giản, ngu ngốc)


Không phải lúc nào, bạn có thể sử dụng giao diện trong một lớp nếu lớp đó là lớp chung.
John Isaiah Carmona

Hơn nữa, bạn có thể giới thiệu một giao diện khi cuối cùng cần một cách dễ dàng trong IDE hiện đại với cấu trúc lại "giao diện giải nén".
Hans-Peter Störr

Hãy xem xét một lớp tiện ích trong đó chỉ có các phương thức tĩnh công khai và một hàm tạo riêng - hoàn toàn được chấp nhận và được quảng bá bởi các tác giả như Joshua Bloch et al.
Darrell Teague

63

Mặc dù về lý thuyết, bạn không nên có một giao diện chỉ vì mục đích của giao diện, câu trả lời của Yannis Rizos gợi ý về các biến chứng tiếp theo:

Khi bạn đang viết bài kiểm tra đơn vị và sử dụng các khung giả như Moq hoặc FakeIt EAS (để đặt tên cho hai cái gần đây nhất tôi đã sử dụng), bạn đang ngầm tạo một lớp khác thực hiện giao diện. Tìm kiếm mã hoặc phân tích tĩnh có thể cho rằng chỉ có một triển khai, nhưng trên thực tế có triển khai giả nội bộ. Bất cứ khi nào bạn bắt đầu viết giả, bạn sẽ thấy rằng việc trích xuất các giao diện có ý nghĩa.

Nhưng xin chờ chút nữa. Có nhiều kịch bản hơn trong đó có các triển khai giao diện ngầm. Ví dụ, sử dụng ngăn xếp giao tiếp WCF của .NET, tạo proxy cho dịch vụ từ xa, một lần nữa, thực hiện giao diện.

Trong một môi trường mã sạch, tôi đồng ý với các câu trả lời còn lại ở đây. Tuy nhiên, hãy chú ý đến bất kỳ khuôn khổ, mẫu hoặc phụ thuộc nào bạn có thể sử dụng giao diện.


2
+1: Tôi không chắc YAGNI áp dụng ở đây, vì có giao diện và sử dụng các khung (ví dụ JMock, v.v.) sử dụng giao diện thực sự có thể giúp bạn tiết kiệm thời gian.
Deco

4
@Deco: các khung mô phỏng tốt (JMock trong số chúng) thậm chí không cần giao diện.
Michael Borgwardt

12
Tạo một giao diện chỉ vì một giới hạn của khung mô phỏng của bạn có vẻ như là một lý do khủng khiếp đối với tôi. Việc sử dụng, ví dụ, EasyMock chế nhạo một lớp cũng dễ như giao diện.
Alb

8
Tôi sẽ nói đó là cách khác. Sử dụng các đối tượng giả trong thử nghiệm có nghĩa là tạo ra các triển khai thay thế cho các giao diện của bạn, theo định nghĩa. Điều này đúng cho dù bạn tạo các lớp FakeImcellenceation của riêng mình hay để các khung mô phỏng làm công việc nặng nhọc cho bạn. Có thể có một số khung công tác, như EasyMock, sử dụng nhiều cách hack và thủ thuật cấp thấp khác nhau để chế nhạo các lớp cụ thể - và tăng thêm sức mạnh cho chúng! - nhưng về mặt khái niệm, các đối tượng giả là các triển khai thay thế cho hợp đồng.
Avner Shahar-Kashtan

1
Bạn không bắn ra các thử nghiệm trong sản xuất. Tại sao một giả cho một lớp không cần giao diện, cần giao diện?
Erik Reppen

32

Không, bạn không cần chúng và tôi coi đó là một mô hình chống để tự động tạo giao diện cho mọi tham chiếu lớp.

Có một chi phí thực sự để làm Foo / FooImpl cho mọi thứ. IDE có thể tạo giao diện / triển khai miễn phí, nhưng khi bạn điều hướng mã, bạn có thêm tải nhận thức từ F3 / F12 khi foo.doSomething()đưa bạn đến chữ ký giao diện chứ không phải triển khai thực tế bạn muốn. Thêm vào đó bạn có sự lộn xộn của hai tập tin thay vì một cho tất cả mọi thứ.

Vì vậy, bạn chỉ nên làm điều đó khi bạn thực sự cần nó cho một cái gì đó.

Bây giờ giải quyết các phản biện:

Tôi cần giao diện cho khung tiêm Dependency

Các giao diện để hỗ trợ khung là di sản. Trong Java, các giao diện từng là một yêu cầu đối với các proxy động, tiền CGLIB. Ngày nay, bạn thường không cần nó. Nó được coi là tiến bộ và là một lợi ích cho năng suất của nhà phát triển mà bạn không cần đến chúng nữa trong EJB3, Spring, v.v.

Tôi cần thử nghiệm để thử nghiệm đơn vị

Nếu bạn viết giả của riêng bạn và có hai triển khai thực tế, thì một giao diện là phù hợp. Chúng tôi có lẽ sẽ không có cuộc thảo luận này ở nơi đầu tiên nếu cơ sở mã của bạn có cả FooImpl và TestFoo.

Nhưng nếu bạn đang sử dụng một khung mô phỏng như Moq, EasyMock hoặc Mockito, bạn có thể giả định các lớp và bạn không cần giao diện. Nó tương tự như thiết lập foo.method = mockImplementationtrong một ngôn ngữ động nơi các phương thức có thể được chỉ định.

Chúng ta cần các giao diện để tuân theo Nguyên tắc đảo ngược phụ thuộc (DIP)

DIP nói rằng bạn xây dựng để phụ thuộc vào hợp đồng (giao diện) chứ không phải triển khai. Nhưng một lớp đã là một hợp đồng và một sự trừu tượng. Đó là những gì các từ khóa công khai / riêng tư dành cho. Ở trường đại học, ví dụ kinh điển giống như lớp Ma trận hoặc Đa thức - người tiêu dùng có API công khai để tạo ma trận, thêm chúng, v.v. nhưng không được phép quan tâm nếu ma trận được triển khai ở dạng thưa thớt hoặc dày đặc. Không có IMatrix hoặc MatrixImpl cần thiết để chứng minh điểm đó.

Ngoài ra, DIP thường được áp dụng quá mức ở mọi cấp độ gọi lớp / phương thức, không chỉ ở các ranh giới mô-đun chính. Một dấu hiệu cho thấy bạn đang áp dụng quá mức DIP là giao diện và cách triển khai của bạn thay đổi theo bước khóa sao cho bạn phải chạm vào hai tệp để thực hiện thay đổi thay vì một. Nếu DIP được áp dụng phù hợp, điều đó có nghĩa là giao diện của bạn không cần phải thay đổi thường xuyên. Ngoài ra, một dấu hiệu khác là giao diện của bạn chỉ có một người tiêu dùng thực sự (ứng dụng riêng của nó). Câu chuyện khác nhau nếu bạn đang xây dựng một thư viện lớp để tiêu thụ trong nhiều ứng dụng khác nhau.

Đây là một hệ quả tất yếu đối với quan điểm của chú Bob Martin về việc chế giễu - bạn chỉ cần chế giễu ở các ranh giới kiến ​​trúc chính. Trong một ứng dụng web, truy cập HTTP và DB là các ranh giới chính. Tất cả các cuộc gọi lớp / phương thức ở giữa là không. Tương tự với DIP.

Xem thêm:


4
Các lớp mô phỏng thay vì các giao diện (ít nhất là trong Java và C #) nên được coi là không dùng nữa, vì không có cách nào để ngăn chặn trình xây dựng siêu lớp chạy, điều này có thể khiến đối tượng giả của bạn tương tác với môi trường theo những cách không lường trước. Việc giả lập một giao diện sẽ an toàn và dễ dàng hơn vì bạn không phải suy nghĩ về mã xây dựng.
Jules

4
Tôi đã không gặp vấn đề với các lớp chế nhạo, nhưng tôi đã thất vọng vì điều hướng IDE không hoạt động như mong đợi. Giải quyết một vấn đề thực sự đánh bại một giả thuyết.
wrschneider

1
@Jules Bạn có thể giả định một lớp cụ thể bao gồm cả hàm tạo của nó trong Java.
assylias

1
@assylias làm thế nào để bạn ngăn chặn constructor chạy?
Jules

2
@Jules Nó phụ thuộc vào khung mô phỏng của bạn - ví dụ với jmockit, bạn chỉ có thể viết new Mockup<YourClass>() {}và cả lớp, bao gồm các hàm tạo của nó, bị chế giễu, cho dù đó là giao diện, lớp trừu tượng hay lớp cụ thể. Bạn cũng có thể "ghi đè" hành vi của hàm tạo nếu bạn muốn làm như vậy. Tôi cho rằng có những cách tương đương trong Mockito hoặc Powermock.
assylias

22

Có vẻ như câu trả lời ở cả hai phía của hàng rào có thể được tóm tắt trong này:

Thiết kế tốt, và đặt giao diện nơi cần giao diện.

Như tôi đã lưu ý khi trả lời câu trả lời của Yanni , tôi không nghĩ bạn có thể có một quy tắc cứng và nhanh về giao diện. Các quy tắc cần phải, theo định nghĩa, linh hoạt. Quy tắc của tôi về giao diện là giao diện nên được sử dụng ở bất cứ đâu bạn đang tạo API. Và một API nên được tạo ở bất cứ đâu bạn vượt qua ranh giới từ một miền trách nhiệm sang một miền khác.

Đối với (một ví dụ khủng khiếp), giả sử bạn đang xây dựng một Carlớp. Trong lớp của bạn, chắc chắn bạn sẽ cần một lớp UI. Trong ví dụ cụ thể này, nó có dạng của một IginitionSwitch, SteeringWheel, GearShift, GasPedal, và BrakePedal. Vì chiếc xe này chứa một AutomaticTransmission, bạn không cầnClutchPedal . (Và vì đây là một chiếc xe khủng khiếp, không có A / C, radio hay chỗ ngồi. Thực tế, ván sàn cũng bị thiếu - bạn chỉ cần bám vào vô lăng đó và hy vọng điều tốt nhất!)

Vì vậy, những lớp nào cần một giao diện? Câu trả lời có thể là tất cả trong số họ, hoặc không ai trong số họ - tùy thuộc vào thiết kế của bạn.

Bạn có thể có một giao diện trông như thế này:

Interface ICabin
    Event IgnitionSwitchTurnedOn()
    Event IgnitionSwitchTurnedOff()
    Event BrakePedalPositionChanged(int percent)
    Event GasPedalPositionChanged(int percent)
    Event GearShiftGearChanged(int gearNum)
    Event SteeringWheelTurned(float degree)
End Interface

Tại thời điểm đó, hành vi của các lớp đó trở thành một phần của Giao diện / API ICabin. Trong ví dụ này, các lớp (nếu có một số) có thể đơn giản, với một vài thuộc tính và một hoặc hai hàm. Và điều bạn đang ngầm nói rõ với thiết kế của mình là các lớp này tồn tại chỉ để hỗ trợ cho bất kỳ triển khai cụ thể nào của ICabin mà bạn có và chúng không thể tự tồn tại, hoặc chúng vô nghĩa bên ngoài bối cảnh ICabin.

Đó là cùng một lý do mà bạn không kiểm tra các thành viên tư nhân đơn vị - họ chỉ tồn tại để hỗ trợ API công khai, và do đó hành vi của họ nên được kiểm tra bằng cách kiểm tra API.

Vì vậy, nếu lớp của bạn tồn tại chỉ để hỗ trợ một lớp khác và về mặt khái niệm bạn xem nó là không thực sự có tên miền riêng thì bỏ qua giao diện. Nhưng nếu lớp của bạn đủ quan trọng để bạn coi nó đủ lớn để có tên miền riêng, thì hãy tiếp tục và cung cấp cho nó một giao diện.


BIÊN TẬP:

Thường xuyên (bao gồm trong câu trả lời này) bạn sẽ đọc những thứ như 'tên miền', 'sự phụ thuộc' (thường được kết hợp với 'tiêm') không có ý nghĩa gì với bạn khi bạn bắt đầu lập trình (họ chắc chắn không có ý nghĩa với tôi) Đối với tên miền, nó có nghĩa chính xác những gì nó nghe như:

Lãnh thổ mà quyền thống trị hoặc quyền lực được phát huy; tài sản của một chủ quyền hoặc thịnh vượng chung, hoặc tương tự. Cũng được sử dụng theo nghĩa bóng. [WordNet nghĩa 2] [1913 Webster]

Trong các điều khoản của ví dụ của tôi - hãy xem xét IgnitionSwitch. Trong một chiếc xe không gian, công tắc đánh lửa chịu trách nhiệm:

  1. Xác thực (không xác định) người dùng (họ cần khóa chính xác)
  2. Cung cấp dòng điện cho bộ khởi động để nó thực sự có thể khởi động xe
  3. Cung cấp dòng điện cho hệ thống đánh lửa để nó có thể tiếp tục hoạt động
  4. Tắt dòng điện để xe dừng lại.
  5. Tùy thuộc vào cách bạn xem nó, trong hầu hết (tất cả?) Những chiếc xe mới hơn có một công tắc ngăn chặn chìa khóa khỏi bị đánh lửa trong khi truyền ra khỏi Park, vì vậy đây có thể là một phần của miền của nó. (Trên thực tế, điều đó có nghĩa là tôi cần suy nghĩ lại và thiết kế lại hệ thống của mình ...)

Những thuộc tính này tạo nên miền của IgnitionSwitch, hay nói cách khác, những gì nó biết và chịu trách nhiệm.

Các IgnitionSwitchkhông chịu trách nhiệm cho GasPedal. Công tắc đánh lửa hoàn toàn không biết gì về bàn đạp ga theo mọi cách. Cả hai đều hoạt động hoàn toàn độc lập với nhau (mặc dù một chiếc xe sẽ khá vô dụng nếu không có cả hai!).

Như tôi đã nói ban đầu, nó phụ thuộc vào thiết kế của bạn. Bạn có thể thiết kế một IgnitionSwitchgiá trị có hai giá trị: On ( True) và Off ( False). Hoặc bạn có thể thiết kế nó để xác thực khóa được cung cấp cho nó và một loạt các hành động khác. Đó là phần khó khăn của việc trở thành một nhà phát triển là quyết định nơi vẽ các đường thẳng trên cát - và thực sự hầu hết thời gian nó hoàn toàn tương đối. Mặc dù vậy, những dòng trên cát rất quan trọng - đó là nơi API của bạn và do đó giao diện của bạn phải ở đâu.


Bạn có thể vui lòng giải thích thêm về ý của bạn bằng cách "có tên miền riêng" không?
Lamin Sanneh

1
@LaminSanneh, xây dựng. cái đó có giúp ích không?
Wayne Werner

8

Không (YAGNI) , trừ khi bạn dự định viết bài kiểm tra cho các lớp khác bằng giao diện này và những bài kiểm tra đó sẽ có lợi từ việc chế nhạo giao diện.


8

Từ MSDN :

Các giao diện phù hợp hơn với các tình huống trong đó các ứng dụng của bạn yêu cầu nhiều loại đối tượng có thể không liên quan để cung cấp chức năng nhất định.

Các giao diện linh hoạt hơn các lớp cơ sở vì bạn có thể định nghĩa một triển khai duy nhất có thể thực hiện nhiều giao diện.

Các giao diện tốt hơn trong các tình huống mà bạn không cần kế thừa triển khai từ lớp cơ sở.

Các giao diện rất hữu ích trong trường hợp bạn không thể sử dụng kế thừa lớp. Ví dụ, các cấu trúc không thể kế thừa từ các lớp, nhưng chúng có thể thực hiện các giao diện.

Nói chung trong trường hợp của một lớp duy nhất, sẽ không cần thiết phải thực hiện giao diện, nhưng xem xét tương lai của dự án của bạn, có thể hữu ích để xác định chính thức hành vi cần thiết của các lớp.


5

Để trả lời câu hỏi: Có nhiều điều hơn thế.

Một khía cạnh quan trọng của một giao diện là ý định.

Giao diện là "một loại trừu tượng không chứa dữ liệu, nhưng phơi bày các hành vi" - Giao diện (tính toán) Vì vậy, nếu đây là một hành vi, hoặc một tập hợp các hành vi, mà lớp hỗ trợ, hơn là một giao diện có thể là mẫu chính xác. Tuy nhiên, nếu (các) hành vi là (nội tại) đối với khái niệm được thể hiện bởi lớp, thì bạn có thể không muốn một giao diện nào cả.

Câu hỏi đầu tiên cần đặt ra là bản chất của sự vật hoặc quá trình mà bạn đang cố gắng thể hiện là gì. Sau đó tiếp tục với những lý do thực tế để thực hiện bản chất đó theo một cách nhất định.


5

Vì bạn đã hỏi câu hỏi này, tôi cho rằng bạn đã thấy những lợi ích của việc có một giao diện ẩn nhiều triển khai. Điều này có thể được biểu hiện bằng nguyên tắc đảo ngược phụ thuộc.

Tuy nhiên, sự cần thiết phải có giao diện hay không không phụ thuộc vào số lượng triển khai của nó. Vai trò thực sự của giao diện là nó xác định hợp đồng nêu rõ dịch vụ nào sẽ được cung cấp thay vì cách triển khai.

Khi hợp đồng được xác định, hai hoặc nhiều nhóm có thể làm việc độc lập. Giả sử bạn đang làm việc trên một mô-đun A và nó phụ thuộc vào mô-đun B, thực tế để tạo giao diện trên B cho phép tiếp tục công việc của bạn mà không phải lo lắng về việc triển khai của B vì tất cả các chi tiết đều bị ẩn bởi giao diện. Do đó, lập trình phân tán trở nên có thể.

Mặc dù mô-đun B chỉ có một triển khai giao diện của nó, giao diện vẫn cần thiết.

Tóm lại, một giao diện ẩn các chi tiết triển khai từ người dùng của nó. Lập trình để giao diện giúp viết nhiều tài liệu hơn vì phải xác định hợp đồng, viết nhiều phần mềm mô-đun hơn, để thúc đẩy các bài kiểm tra đơn vị và tăng tốc độ phát triển.


2
Mỗi lớp có thể có giao diện chung (phương thức chung) và giao diện riêng (chi tiết triển khai). Tôi có thể tranh luận rằng giao diện chung của lớp là hợp đồng. Bạn không cần một yếu tố phụ để làm việc với hợp đồng đó.
Fuhrmanator

4

Tất cả câu trả lời ở đây là rất tốt. Thật vậy, hầu hết thời gian bạn không cần phải thực hiện một giao diện khác. Nhưng có những trường hợp bạn có thể muốn làm điều đó anyway. Đây là một số trường hợp tôi làm điều đó:

Lớp triển khai một giao diện khác mà tôi không muốn tiếp xúc với
Happen thường xuyên với lớp bộ điều hợp kết nối mã bên thứ ba.

interface NameChangeListener { // Implemented by a lot of people
    void nameChanged(String name); 
} 

interface NameChangeCount { // Only implemented by my class
    int getCount();
}

class NameChangeCounter implements NameChangeListener, NameChangeCount {
    ...
}

class SomeUserInterface {
    private NameChangeCount currentCount; // Will never know that you can change the counter
}

Lớp sử dụng một công nghệ cụ thể không nên rò rỉ máng
Hầu hết khi tương tác với các thư viện bên ngoài. Ngay cả khi chỉ có một triển khai, tôi vẫn sử dụng giao diện để đảm bảo rằng tôi không giới thiệu khớp nối không cần thiết với thư viện bên ngoài.

interface SomeRepository { // Guarantee that the external library details won't leak trough
    ...
}

class OracleSomeRepository implements SomeRepository { 
    ... // Oracle prefix allow us to quickly know what is going on in this class
}

Giao tiếp giữa các lớp
Ngay cả khi chỉ có một lớp UI từng thực hiện một trong các lớp miền, nó cho phép phân tách tốt hơn giữa các lớp đó và quan trọng nhất là nó tránh được sự phụ thuộc theo chu kỳ.

package project.domain;

interface UserRequestSource {
    public UserRequest getLastRequest();
}

class UserBehaviorAnalyser {
    private UserRequestSource requestSource;
}

package project.ui;

class OrderCompleteDialog extends SomeUIClass implements project.domain.UserRequestSource {
    // UI concern, no need for my domain object to know about this method.
    public void displayLabelInErrorMode(); 

    // They most certainly need to know about *that* though
    public UserRequest getLastRequest();
}

Chỉ có một tập hợp con của phương thức nên có sẵn cho hầu hết các đối tượng
Hầu hết xảy ra khi tôi có một số phương thức cấu hình trên lớp cụ thể

interface Sender {
    void sendMessage(Message message)
}

class PacketSender implements Sender {
    void sendMessage(Message message);
    void setPacketSize(int sizeInByte);
}

class Throttler { // This class need to have full access to the object
    private PacketSender sender;

    public useLowNetworkUsageMode() {
        sender.setPacketSize(LOW_PACKET_SIZE);
        sender.sendMessage(new NotifyLowNetworkUsageMessage());

        ... // Other details
    }
}

class MailOrder { // Not this one though
    private Sender sender;
}

Vì vậy, cuối cùng tôi sử dụng giao diện cho cùng một lý do là tôi sử dụng trường riêng: đối tượng khác không nên có quyền truy cập vào nội dung mà họ không nên truy cập. Nếu tôi có một trường hợp như vậy, tôi giới thiệu một giao diện ngay cả khi chỉ có một lớp thực hiện nó.


2

Các giao diện rất quan trọng nhưng hãy cố gắng kiểm soát số lượng chúng mà bạn có.

Đã đi vào con đường tạo giao diện cho mọi thứ, thật dễ dàng để kết thúc với mã 'spaghetti xắt nhỏ'. Tôi nói đến sự khôn ngoan lớn hơn của Ayende Rahien, người đã đăng một số từ rất khôn ngoan về chủ đề này:

http://ayende.com/blog/153889/limit-your-abstrilities-analyzing-a-ddd-application

Đây là bài viết đầu tiên của anh ấy trong cả bộ, vì vậy hãy tiếp tục đọc!


mã 'mì xắt nhỏ-up' còn được gọi là mã ravioli c2.com/cgi/wiki?RavioliCode
CurtainDog

Âm thanh giống như mã lasagne hoặc mã baklava đối với tôi - mã có quá nhiều lớp. ;-)
dodgy_coder

2

Một lý do bạn vẫn có thể muốn giới thiệu một giao diện trong trường hợp này là tuân theo Nguyên tắc đảo ngược phụ thuộc . Nghĩa là, mô-đun sử dụng lớp sẽ phụ thuộc vào sự trừu tượng hóa của nó (tức là giao diện) thay vì phụ thuộc vào việc triển khai cụ thể. Nó tách các thành phần cấp cao khỏi các thành phần cấp thấp.


2

Không có lý do thực sự để làm bất cứ điều gì. Các giao diện là để giúp bạn chứ không phải chương trình đầu ra. Vì vậy, ngay cả khi Giao diện được thực hiện bởi một triệu lớp, không có quy tắc nào nói rằng bạn phải tạo một lớp. Bạn tạo một cái để khi bạn hoặc bất kỳ ai khác sử dụng mã của bạn muốn thay đổi thứ gì đó mà nó thấm vào tất cả các cài đặt. Tạo một giao diện sẽ hỗ trợ bạn trong tất cả các trường hợp trong tương lai nơi bạn có thể muốn tạo một lớp khác thực hiện nó.


1

Không phải lúc nào cũng cần xác định giao diện cho một lớp.

Các đối tượng đơn giản như các đối tượng giá trị không có nhiều triển khai. Họ cũng không cần phải bị chế giễu. Việc thực hiện có thể được kiểm tra một mình và khi các lớp khác được kiểm tra phụ thuộc vào chúng, đối tượng giá trị thực có thể được sử dụng.

Hãy nhớ rằng việc tạo một giao diện có chi phí. Nó cần được cập nhật cùng với việc triển khai, nó cần một tệp bổ sung và một số IDE sẽ gặp khó khăn khi phóng to triển khai, chứ không phải giao diện.

Vì vậy, tôi sẽ định nghĩa các giao diện chỉ dành cho các lớp cấp cao hơn, nơi bạn muốn một sự trừu tượng hóa từ việc thực hiện.

Lưu ý rằng với một lớp bạn có được một giao diện miễn phí. Bên cạnh việc thực hiện, một lớp định nghĩa một giao diện từ tập các phương thức công khai. Giao diện đó được thực hiện bởi tất cả các lớp dẫn xuất. Nó không hoàn toàn nói một giao diện, nhưng nó có thể được sử dụng chính xác theo cùng một cách. Vì vậy, tôi không nghĩ cần phải tạo lại một giao diện đã tồn tại dưới tên của lớp.

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.