Có phải thành phần của người thừa kế thừa kế


36

Ví dụ, hãy xem xét tôi có một lớp để các lớp khác mở rộng:

public class LoginPage {
    public String userId;
    public String session;
    public boolean checkSessionValid() {
    }
}

và một số lớp con:

public class HomePage extends LoginPage {

}

public class EditInfoPage extends LoginPage {

}

Trên thực tế, lớp con không có bất kỳ phương thức nào để ghi đè, tôi cũng sẽ không truy cập HomePage theo cách chung, tức là: Tôi sẽ không làm điều gì đó như:

for (int i = 0; i < loginPages.length; i++) {
    loginPages[i].doSomething();
}

Tôi chỉ muốn sử dụng lại trang đăng nhập. Nhưng theo https://stackoverflow.com/a/53354 , tôi nên thích sáng tác ở đây vì tôi không cần giao diện LoginPage, vì vậy tôi không sử dụng tính kế thừa ở đây:

public class HomePage {
    public LoginPage loginPage;
}

public class EditInfoPage {
    public LoginPage loginPage;
}

nhưng vấn đề ở đây, ở phiên bản mới, mã:

public LoginPage loginPage;

trùng lặp khi một lớp mới được thêm vào. Và nếu LoginPage cần setter và getter, nhiều mã cần được sao chép:

public LoginPage loginPage;

private LoginPage getLoginPage() {
    return this.loginPage;
}
private void setLoginPage(LoginPage loginPage) {
    this.loginPage = loginPage;
}

Vì vậy, câu hỏi của tôi là, "thành phần trên thừa kế" vi phạm "nguyên tắc khô"?


13
Đôi khi bạn không cần kế thừa cũng không phải thành phần, nghĩa là đôi khi một lớp với một vài đối tượng thực hiện công việc.
Erik Eidt

23
Tại sao bạn muốn tất cả các trang của bạn là trang đăng nhập? Có lẽ chỉ cần có một trang đăng nhập.
Ông Cochese

29
Nhưng, với sự kế thừa, bạn đã nhân đôi extends LoginPagekhắp nơi. KIỂM TRA MATE!
el.pescado

2
Nếu bạn gặp vấn đề ở đoạn cuối rất nhiều, bạn có thể đang lạm dụng các getters và setters. Họ có xu hướng vi phạm đóng gói.
Brian McCutchon

4
Vì vậy, làm cho trang của bạn có thể được trang trí và làm cho LoginPagemột trang trí của bạn . Không còn trùng lặp, chỉ đơn giản page = new LoginPage(new EditInfoPage())và bạn đã hoàn thành. Hoặc bạn sử dụng nguyên tắc đóng mở để tạo mô-đun xác thực có thể được thêm vào bất kỳ trang nào một cách linh hoạt. Có rất nhiều cách để đối phó với sao chép mã, chủ yếu liên quan đến việc tìm kiếm một sự trừu tượng mới. LoginPagecó thể là một tên xấu, điều bạn muốn chắc chắn là người dùng được xác thực trong khi duyệt trang đó, trong khi được chuyển hướng đến LoginPagehoặc hiển thị một thông báo lỗi thích hợp nếu anh ta không.
Polygnome

Câu trả lời:


46

Er chờ bạn lo lắng rằng lặp đi lặp lại

public LoginPage loginPage;

ở hai nơi vi phạm DRY? Theo logic đó

int x;

bây giờ chỉ có thể tồn tại trong một đối tượng trong toàn bộ cơ sở mã. Thôi nào.

DRY là một điều tốt để ghi nhớ nhưng thôi nào. ngoài ra

... extends LoginPage

đang được nhân đôi trong giải pháp thay thế của bạn, do đó, thậm chí là hậu môn về DRY sẽ không có ý nghĩa về điều này.

Mối quan tâm DRY hợp lệ có xu hướng tập trung vào hành vi giống hệt nhau cần thiết ở nhiều nơi được xác định ở nhiều nơi sao cho nhu cầu thay đổi hành vi này kết thúc đòi hỏi phải thay đổi ở nhiều nơi. Đưa ra quyết định ở một nơi và bạn sẽ chỉ cần thay đổi chúng ở một nơi. Điều đó không có nghĩa là chỉ một đối tượng có thể giữ một tham chiếu đến LoginPage của bạn.

DRY không nên bị theo dõi một cách mù quáng. Nếu bạn đang sao chép vì sao chép và dán dễ dàng hơn là nghĩ ra một phương thức hay tên lớp tốt thì có lẽ bạn đã nhầm.

Nhưng nếu bạn muốn đặt cùng một mã ở một nơi khác vì nơi khác đó chịu trách nhiệm khác nhau và có thể cần phải thay đổi độc lập thì có lẽ nên nới lỏng việc thực thi DRY của bạn và để hành vi giống hệt này có một bản sắc khác . Đó là cùng một kiểu suy nghĩ đi vào việc cấm các con số ma thuật.

DRY không chỉ là về những gì mã trông như thế nào. Đó là về việc không truyền bá các chi tiết của một ý tưởng xung quanh với sự lặp đi lặp lại vô thức buộc các nhà bảo trì phải sửa chữa mọi thứ bằng cách sử dụng sự lặp đi lặp lại không suy nghĩ. Đó là khi bạn cố gắng nói với bản thân rằng sự lặp đi lặp lại không suy nghĩ chỉ là quy ước của bạn rằng mọi thứ đang đi theo một cách tồi tệ thực sự.

Những gì tôi nghĩ rằng bạn đang thực sự cố gắng để phàn nàn về được gọi là mã soạn sẵn. Có, sử dụng thành phần chứ không phải thừa kế yêu cầu mã soạn sẵn. Không có gì được tiếp xúc miễn phí, bạn phải viết mã để lộ nó. Với tính năng nồi hơi đi kèm với tính linh hoạt của trạng thái, khả năng thu hẹp giao diện được hiển thị, để đặt cho mọi thứ các tên khác nhau phù hợp với mức độ trừu tượng của chúng, cảm ứng ol tốt và bạn đang sử dụng những gì bạn cấu thành từ bên ngoài, chứ không phải Bên trong, vì vậy bạn đang đối mặt với giao diện bình thường.

Nhưng vâng, đó là rất nhiều bàn phím gõ thêm. Miễn là tôi có thể ngăn chặn vấn đề yo-yo nảy lên và xuống một chồng thừa kế khi tôi đọc mã đó là giá trị nó.

Bây giờ không phải là tôi từ chối sử dụng thừa kế. Một trong những cách sử dụng yêu thích của tôi là đưa ra ngoại lệ cho các tên mới:

public class MyLoginPageWasNull extends NullPointerException{}

int x;có thể tồn tại không quá 0 lần trong một cơ sở mã
K. Alan Bates

@ K.AlanBates xin lỗi ?
candied_orange

... tôi biết bạn sẽ vặn lại với lớp Point lol. Không ai quan tâm đến lớp Point. Nếu tôi bước vào codebase của bạn và thấy int a; int b; int x;tôi sẽ cố gắng loại bỏ 'bạn'.
K. Alan Bates

@ K.AlanBates Wow. thật buồn khi bạn nghĩ rằng loại hành vi đó sẽ khiến bạn trở thành một lập trình viên giỏi. Lập trình viên giỏi nhất không phải là người có thể tìm thấy hầu hết mọi thứ về những người khác để chê bai. Đó là một trong những làm cho phần còn lại tốt hơn.
candied_orange

Sử dụng tên vô nghĩa là một người không bắt đầu và có khả năng làm cho nhà phát triển mã đó trở thành một trách nhiệm hơn là một tài sản.
K. Alan Bates

127

Một sự hiểu lầm phổ biến với nguyên tắc DRY là nó liên quan đến việc không lặp lại các dòng mã. Nguyên tắc DRY là "Mỗi phần kiến ​​thức phải có một đại diện duy nhất, rõ ràng, có thẩm quyền trong một hệ thống" . Đó là về kiến ​​thức, không phải mã.

LoginPagebiết về cách vẽ trang để đăng nhập. Nếu EditInfoPagebiết cách làm điều này thì đó sẽ là một vi phạm. Bao gồm LoginPagethông qua thành phần không phải là vi phạm nguyên tắc DRY.

Nguyên tắc DRY có lẽ là nguyên tắc bị lạm dụng nhiều nhất trong công nghệ phần mềm và nó luôn luôn phải được coi là không phải là nguyên tắc không sao chép mã, mà là nguyên tắc không sao chép kiến ​​thức miền trừu tượng. Trên thực tế, trong nhiều trường hợp nếu bạn áp dụng DRY chính xác thì bạn sẽ sao chép mã và đây không hẳn là một điều xấu.


27
"Nguyên tắc trách nhiệm duy nhất" bị lạm dụng nhiều hơn nữa. "Đừng lặp lại chính mình" có thể dễ dàng đứng ở vị trí thứ hai :-)
gnasher729

28
"DRY" là từ viết tắt tiện dụng cho "Đừng lặp lại chính mình", đó là một cụm từ dễ hiểu, nhưng không phải là tên của nguyên tắc, tên thực tế của nguyên tắc là "Một lần và Chỉ một lần", đến lượt nó là một cái tên hấp dẫn cho một nguyên tắc cần một vài đoạn để mô tả. Điều quan trọng là hiểu được một vài đoạn, không ghi nhớ ba chữ cái D, R và Y.
Jörg W Mittag

4
@ gnasher729 Bạn biết đấy, tôi thực sự đồng ý với bạn rằng SRP có thể bị lạm dụng nhiều hơn nữa. Mặc dù để công bằng, trong nhiều trường hợp tôi nghĩ rằng chúng thường bị lạm dụng cùng nhau. Lý thuyết của tôi là các lập trình viên nói chung không nên tin tưởng để sử dụng các từ viết tắt với "tên dễ dàng".
wasatz

17
IMHO đây là một câu trả lời tốt. Tuy nhiên, công bằng mà nói: theo kinh nghiệm của tôi, hình thức vi phạm DRY thường xuyên nhất là do lập trình sao chép-dán và chỉ sao chép mã. Ai đó đã nói với tôi một lần "bất cứ khi nào bạn định sao chép-dán một số mã, hãy suy nghĩ kỹ nếu phần trùng lặp có thể được trích xuất thành một chức năng chung" - đó là lời khuyên rất tốt của IMHO.
Doc Brown

9
Lạm dụng sao chép-dán chắc chắn là một động lực để vi phạm DRY nhưng chỉ đơn giản là cấm sao chép-dán là hướng dẫn kém cho việc tuân theo DRY. Tôi sẽ không cảm thấy hối hận vì đã có hai phương thức với mã giống hệt nhau khi các phương thức đó đại diện cho các phần kiến ​​thức khác nhau. Họ phục vụ hai trách nhiệm khác nhau. Họ chỉ tình cờ có mã giống hệt ngày hôm nay. Họ nên được tự do thay đổi độc lập. Vâng, kiến ​​thức, không phải mã. Vâng đặt. Tôi đã viết một trong những câu trả lời khác nhưng tôi sẽ cúi đầu trước câu trả lời này. +1.
candied_orange

12

Câu trả lời ngắn: có, nó làm - với một số mức độ nhỏ, chấp nhận được.

Thoạt nhìn, tính kế thừa đôi khi có thể giúp bạn tiết kiệm một số dòng mã, vì nó có tác dụng nói "lớp tái sử dụng của tôi sẽ chứa tất cả các phương thức và thuộc tính công khai theo cách 1: 1". Vì vậy, nếu có một danh sách 10 phương thức trong một thành phần, người ta không phải lặp lại chúng trong mã trong lớp kế thừa. Khi trong kịch bản thành phần 9 trong số 10 phương thức đó phải được hiển thị công khai thông qua một thành phần sử dụng lại, thì người ta phải viết ra 9 cuộc gọi ủy nhiệm và để cho các cuộc gọi còn lại, không có cách nào khác.

Tại sao điều này được chấp nhận? Nhìn vào các phương thức được sao chép trong một kịch bản thành phần - các phương thức này chỉ ủy thác các cuộc gọi đến giao diện của thành phần, do đó không chứa logic thực sự.

Trọng tâm của nguyên tắc DRY là tránh có hai vị trí trong mã nơi các quy tắc logic giống nhau được mã hóa - bởi vì khi các quy tắc logic này thay đổi, trong mã không DRY, bạn có thể dễ dàng điều chỉnh một trong những vị trí đó và quên đi vị trí kia, trong đó giới thiệu một lỗi.

Nhưng vì các cuộc gọi ủy nhiệm không chứa logic, nên chúng thường không phải là đối tượng của sự thay đổi như vậy, do đó không gây ra vấn đề thực sự khi "thích sáng tác hơn kế thừa". Và ngay cả khi giao diện của thành phần thay đổi, có thể tạo ra thay đổi chính thức ở tất cả các lớp sử dụng thành phần, trong ngôn ngữ được biên dịch, trình biên dịch sẽ cho chúng ta biết khi chúng ta quên thay đổi một trong những người gọi.

Một lưu ý cho ví dụ của bạn: Tôi không biết bạn HomePagevà bạn EditInfoPagetrông như thế nào, nhưng nếu họ có chức năng đăng nhập và HomePage(hoặc một EditInfoPage) là một LoginPage , thì thừa kế có thể là công cụ chính xác ở đây. Một ví dụ ít gây tranh cãi, trong đó bố cục sẽ là công cụ tốt hơn theo cách rõ ràng hơn, có lẽ sẽ làm cho mọi thứ rõ ràng hơn.

Giả sử không HomePagephải EditInfoPagelà một LoginPage, và người ta muốn sử dụng lại cái sau, như bạn đã viết, rất có thể người ta chỉ yêu cầu một số phần của LoginPage, không phải tất cả mọi thứ. Nếu đó là trường hợp, một cách tiếp cận tốt hơn so với sử dụng sáng tác theo cách hiển thị có thể là

  • trích phần có thể tái sử dụng LoginPagethành một thành phần của chính nó

  • tái sử dụng mà thành phần trong HomePageEditInfoPagetheo cùng một cách nó được sử dụng hiện đang nằm trongLoginPage

Theo cách đó, nó thường sẽ trở nên rõ ràng hơn nhiều tại sao và khi thành phần trên thừa kế là cách tiếp cận đúng.

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.