Sử dụng lớp trừu tượng trong C # như định nghĩa


21

Là một nhà phát triển C ++, tôi khá quen với các tệp tiêu đề C ++ và thấy có lợi khi có một số "tài liệu" bắt buộc bên trong mã. Tôi thường có một khoảng thời gian tồi tệ khi tôi phải đọc một số mã C # vì điều đó: Tôi không có loại bản đồ tinh thần của lớp tôi đang làm việc.

Giả sử rằng là một kỹ sư phần mềm, tôi đang thiết kế khung chương trình. Sẽ là quá điên rồ khi định nghĩa mỗi lớp là một lớp chưa thực hiện trừu tượng, tương tự như những gì chúng ta sẽ làm với các tiêu đề C ++ và để các nhà phát triển triển khai nó?

Tôi đoán có thể có một số lý do tại sao một người nào đó có thể thấy đây là một giải pháp khủng khiếp nhưng tôi không chắc tại sao. Điều gì người ta sẽ phải xem xét cho một giải pháp như thế này?


13
C # là một ngôn ngữ lập trình hoàn toàn khác so với C ++. Một số cú pháp trông tương tự, nhưng bạn cần hiểu C # để thực hiện tốt công việc đó. Và bạn nên làm gì đó với thói quen xấu mà bạn có khi dựa vào các tệp tiêu đề. Tôi đã làm việc với C ++ trong nhiều thập kỷ, tôi chưa bao giờ đọc các tệp tiêu đề, chỉ viết chúng.
Bent

17
Một trong những điều tốt nhất của C # và Java là sự tự do khỏi các tệp tiêu đề! Chỉ cần sử dụng một IDE tốt.
Erik Eidt

9
Các khai báo trong các tệp tiêu đề C ++ cuối cùng không phải là một phần của tệp nhị phân được tạo. Họ ở đó cho trình biên dịch và liên kết. Các lớp trừu tượng C # này sẽ là một phần của mã được tạo, không có lợi ích gì.
Mark Benningfield

16
Cấu trúc tương đương trong C # là một giao diện, không phải là một lớp trừu tượng.
Robert Harvey

6
@DaniBarcaCasafont "Tôi thấy các tiêu đề là một cách nhanh chóng và đáng tin cậy để xem các phương thức và thuộc tính của lớp là gì, loại tham số nào nó mong đợi và những gì nó trả về". Khi sử dụng Visual Studio, lựa chọn thay thế của tôi là phím tắt Ctrl-MO.
wha7ever - Phục hồi Monica

Câu trả lời:


44

Lý do điều này đã được thực hiện trong C ++ phải làm với việc tạo các trình biên dịch nhanh hơn và dễ thực hiện hơn. Đây không phải là một thiết kế để làm cho lập trình dễ dàng hơn.

Mục đích của các tệp tiêu đề là cho phép trình biên dịch thực hiện lần đầu tiên siêu nhanh để biết tất cả các tên hàm dự kiến ​​và phân bổ các vị trí bộ nhớ cho chúng để chúng có thể được tham chiếu khi được gọi trong các tệp cp, ngay cả khi lớp xác định chúng có chưa được phân tích cú pháp

Cố gắng tái tạo hệ quả của những hạn chế phần cứng cũ trong môi trường phát triển hiện đại là không nên!

Xác định một giao diện hoặc lớp trừu tượng cho mỗi lớp sẽ làm giảm năng suất của bạn; bạn có thể làm gì khác với thời gian đó ? Ngoài ra, các nhà phát triển khác sẽ không tuân theo quy ước này.

Trong thực tế, các nhà phát triển khác có thể xóa các lớp trừu tượng của bạn. Nếu tôi tìm thấy một giao diện trong mã đáp ứng cả hai tiêu chí này, tôi sẽ xóa nó và tái cấu trúc nó ra khỏi mã:1. Does not conform to the interface segregation principle 2. Only has one class that inherits from it

Một điều nữa là, có những công cụ đi kèm với Visual Studio thực hiện những gì bạn hướng tới để thực hiện tự động:

  1. Các Class View
  2. Các Object Browser
  3. Trong Solution Explorerbạn có thể nhấp vào hình tam giác để chi các lớp để xem các chức năng, tham số và kiểu trả về của chúng.
  4. Có ba menu thả xuống bên dưới các tab tệp, một menu bên phải nhất liệt kê tất cả các thành viên của lớp hiện tại.

Hãy thử một lần ở trên trước khi dành thời gian để sao chép các tệp tiêu đề C ++ trong C #.


Hơn nữa, có những lý do kỹ thuật để không làm điều này ... nó sẽ làm cho nhị phân cuối cùng của bạn lớn hơn mức cần thiết. Tôi sẽ lặp lại nhận xét này của Mark Benningfield:

Các khai báo trong các tệp tiêu đề C ++ cuối cùng không phải là một phần của tệp nhị phân được tạo. Họ ở đó cho trình biên dịch và liên kết. Các lớp trừu tượng C # này sẽ là một phần của mã được tạo, không có lợi ích gì.


Ngoài ra, được đề cập bởi Robert Harvey, về mặt kỹ thuật, tương đương gần nhất của một tiêu đề trong C # sẽ là một giao diện, không phải là một lớp trừu tượng.


2
Bạn cũng sẽ kết thúc với rất nhiều cuộc gọi gửi ảo không cần thiết (không tốt nếu mã của bạn nhạy cảm với hiệu suất).
Lucas Trzesniewski

5
Đây là Nguyên tắc phân chia giao diện được đề cập.
NH.

2
Người ta cũng có thể đơn giản thu gọn toàn bộ lớp (nhấp chuột phải -> Phác thảo -> Thu gọn thành Định nghĩa) để có cái nhìn toàn cảnh về nó.
jpmc26

"bạn có thể làm gì khác với thời gian đó" -> Uhm, sao chép và dán và bài thực sự rất nhỏ? Mất khoảng 3 giây, nếu có. Tất nhiên, thay vào đó, tôi có thể sử dụng đầu lọc để chuẩn bị cho việc hút thuốc lá. Không thực sự là một điểm có liên quan. [Tuyên bố miễn trừ trách nhiệm: Tôi yêu cả hai, C # thành ngữ, cũng như C ++ thành ngữ và một số ngôn ngữ khác]
phresnel

1
@phresnel: Tôi muốn thấy bạn thử và lặp lại các định nghĩa của một lớp và kế thừa lớp gốc từ lớp trừu tượng trong 3 giây, được xác nhận không có lỗi, đối với bất kỳ lớp nào đủ lớn để không dễ nhìn qua mắt chim nó (vì đó là trường hợp sử dụng được đề xuất của OP: được cho là không phức tạp với các lớp phức tạp khác). Hầu như vốn có, tính chất phức tạp của lớp gốc có nghĩa là bạn không thể viết định nghĩa lớp trừu tượng bằng trái tim (bởi vì điều đó có nghĩa là bạn đã biết nó bằng trái tim) Và sau đó xem xét thời gian cần thiết để làm điều này cho toàn bộ cơ sở mã.
Flater

14

Trước tiên, hãy hiểu rằng một lớp trừu tượng thuần túy thực sự chỉ là một giao diện không thể thực hiện nhiều kế thừa.

Viết lớp, trích xuất giao diện, là một hoạt động chết não. Nhiều đến mức chúng tôi có một cấu trúc lại cho nó. Đó là một điều đáng tiếc. Theo mô hình "mỗi lớp có một giao diện" này không chỉ tạo ra sự lộn xộn mà nó hoàn toàn bỏ lỡ điểm.

Một giao diện không nên được coi là chỉ đơn giản là một sự phục hồi chính thức của bất cứ điều gì mà lớp có thể làm. Một giao diện nên được coi là một hợp đồng được áp đặt bởi việc sử dụng mã khách hàng chi tiết hóa nhu cầu của nó.

Tôi không gặp khó khăn gì khi viết một giao diện hiện chỉ có một lớp thực hiện nó. Tôi thực sự không quan tâm nếu không có lớp nào thực hiện nó. Bởi vì tôi đang nghĩ về những gì tôi sử dụng mã cần. Giao diện thể hiện những gì sử dụng mã yêu cầu. Bất cứ điều gì đến sau này đều có thể làm những gì nó thích miễn là nó thỏa mãn những mong đợi này.

Bây giờ tôi không làm điều này mỗi khi một đối tượng sử dụng một đối tượng khác. Tôi làm điều này khi vượt qua một ranh giới. Tôi làm điều đó khi tôi không muốn một đối tượng biết chính xác đối tượng khác mà nó đang nói chuyện. Đó là cách duy nhất đa hình sẽ hoạt động. Tôi làm điều đó khi tôi mong đợi đối tượng mà mã khách hàng của tôi đang nói có khả năng thay đổi. Tôi chắc chắn không làm điều này khi thứ tôi đang sử dụng là lớp String. Lớp String rất hay và ổn định và tôi cảm thấy không cần phải đề phòng nó thay đổi.

Khi bạn quyết định tương tác trực tiếp với một triển khai cụ thể thay vì thông qua một sự trừu tượng, bạn dự đoán rằng việc triển khai đó đủ ổn định để tin tưởng không thay đổi.

Đúng vậy, đó là cách tôi tiết chế Nguyên tắc đảo ngược phụ thuộc . Bạn không nên mù quáng áp dụng điều này vào mọi thứ. Khi bạn thêm một sự trừu tượng, bạn thực sự nói rằng bạn không tin tưởng sự lựa chọn của lớp triển khai sẽ ổn định trong suốt vòng đời của dự án.

Tất cả điều này giả định rằng bạn đang cố gắng tuân theo Nguyên tắc Đóng mở . Nguyên tắc này chỉ quan trọng khi chi phí liên quan đến việc thay đổi trực tiếp mã được thiết lập là đáng kể. Một trong những lý do chính khiến mọi người không đồng ý về việc các đối tượng tách rời quan trọng như thế nào là bởi vì không phải ai cũng trải qua cùng một chi phí khi thực hiện các thay đổi trực tiếp. Nếu việc kiểm tra lại, biên dịch lại và phân phối lại toàn bộ cơ sở mã của bạn là chuyện nhỏ với bạn thì việc giải quyết nhu cầu thay đổi bằng sửa đổi trực tiếp có thể là sự đơn giản hóa rất hấp dẫn của vấn đề này.

Đơn giản là không có câu trả lời chết não cho câu hỏi này. Một giao diện hoặc lớp trừu tượng không phải là thứ bạn nên thêm vào mỗi lớp và bạn không thể chỉ đếm số lượng lớp thực hiện và quyết định nó không cần thiết. Đó là về việc đối phó với sự thay đổi. Điều đó có nghĩa là bạn đang dự đoán tương lai. Đừng ngạc nhiên nếu bạn hiểu sai. Giữ nó đơn giản như bạn có thể mà không cần lùi mình vào một góc.

Vì vậy, xin vui lòng, đừng viết trừu tượng chỉ để giúp chúng tôi đọc mã. Chúng tôi có các công cụ cho việc đó. Sử dụng trừu tượng để tách rời những gì cần tách rời.


5

Vâng, điều đó sẽ rất tệ bởi vì (1) Nó giới thiệu mã không cần thiết (2) Nó sẽ gây nhầm lẫn cho người đọc.

Nếu bạn muốn lập trình trong C #, bạn chỉ nên làm quen với việc đọc C #. Mã được viết bởi những người khác sẽ không theo mô hình này.


1

Bài kiểm tra đơn vị

Tôi thực sự khuyên bạn nên viết các bài kiểm tra Đơn vị thay vì làm lộn xộn mã với các giao diện hoặc các lớp trừu tượng (trừ khi được bảo hành vì các lý do khác).

Một bài kiểm tra đơn vị được viết tốt không chỉ mô tả giao diện của lớp của bạn (như tệp tiêu đề, lớp trừu tượng hoặc giao diện sẽ làm) mà còn mô tả, ví dụ, chức năng mong muốn.

Ví dụ: Trường hợp bạn có thể đã viết một tệp tiêu đề mygroup.h như thế này:

class MyClass
{
public:
  void foo();
};

Thay vào đó, trong c # hãy viết một bài kiểm tra như thế này:

[TestClass]
public class MyClassTests
{
    [TestMethod]
    public void MyClass_should_have_method_Foo()
    {
        //Arrange
        var myClass = new MyClass();
        //Act
        myClass.Foo();
        //Verify
        Assert.Inconclusive("TODO: Write a more detailed test");
    }
}

Thử nghiệm rất đơn giản này truyền tải thông tin giống như tệp tiêu đề. (Chúng ta nên có một lớp có tên "MyClass" với hàm không tham số "Foo") Trong khi một tệp tiêu đề nhỏ gọn hơn, thử nghiệm chứa nhiều thông tin hơn.

Một cảnh báo: quá trình thử nghiệm cung cấp kỹ sư phần mềm cao cấp (không thành công) cho các nhà phát triển khác để giải quyết xung đột dữ dội với các phương pháp như TDD, nhưng trong trường hợp của bạn, đó sẽ là một cải tiến rất lớn.


Làm thế nào để bạn thực hiện Kiểm thử đơn vị (kiểm tra độc lập) = Mocks cần thiết, không có giao diện? Khung nào hỗ trợ giả lập từ một triển khai thực tế, hầu hết tôi đã thấy sử dụng một giao diện để trao đổi thực hiện với triển khai giả.
Bulan

Tôi không nghĩ rằng giả cần thiết là cần thiết cho các mục đích của OP. Hãy nhớ rằng, đây không phải là kiểm tra đơn vị làm công cụ cho TDD, mà là kiểm tra đơn vị thay thế cho các tệp tiêu đề. (Tất nhiên họ có thể tiến hóa thành các bài kiểm tra đơn vị "thông thường" với giả, v.v.)
Guran

Ok, nếu tôi chỉ xem xét khía cạnh OP của nó, tôi hiểu rõ hơn. Đọc câu trả lời của bạn như là một câu trả lời áp dụng chung hơn. Thx đã làm rõ!
Bulan

@Bulan nhu cầu chế nhạo rộng rãi thường là biểu hiện của một thiết kế tồi
TheCatWhisperer

@TheCatWhisperer Vâng, tôi nhận thức rõ về điều đó, vì vậy không có tranh luận ở đó, tôi cũng không thể thấy rằng tôi đã đưa ra tuyên bố đó ở bất cứ đâu: DI nói chung về thử nghiệm, rằng tất cả Mock-framework tôi đã sử dụng Giao diện để hoán đổi triển khai thực tế và nếu có một số kỹ thuật khác về cách bạn đã chế giễu nếu bạn không có giao diện.
Bulan
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.