Khi nào tôi nên mở rộng một lớp Java Swing?


35

Sự hiểu biết hiện tại của tôi về việc thực hiện Kế thừa là người ta chỉ nên mở rộng một lớp nếu có mối quan hệ IS-A . Nếu lớp cha có thể có thêm các kiểu con cụ thể hơn với chức năng khác nhau nhưng sẽ chia sẻ các phần tử phổ biến được trừu tượng hóa trong lớp cha.

Tôi đang nghi ngờ sự hiểu biết đó vì những gì giáo sư Java của tôi khuyên chúng ta nên làm. Ông đã khuyến nghị rằng đối với một JSwingứng dụng chúng tôi đang xây dựng trong lớp

Người ta phải mở rộng tất cả JSwingcác lớp học ( JFrame, JButton, JTextBox, vv) vào các lớp tùy chỉnh riêng biệt và chỉ định GUI tùy biến có liên quan đến họ (như kích thước thành phần, thành phần nhãn, vv)

Cho đến nay vẫn tốt, nhưng ông tiếp tục khuyên rằng mỗi JButton nên có lớp mở rộng tùy chỉnh riêng của mình mặc dù yếu tố phân biệt duy nhất là nhãn của họ.

Ví dụ: Nếu GUI có hai nút OkayHủy . Ông khuyến nghị họ nên được gia hạn như sau:

class OkayButton extends JButton{
    MainUI mui;
    public OkayButton(MainUI mui) {
        setSize(80,60);
        setText("Okay");
        this.mui = mui;
        mui.add(this);        
    }
}

class CancelButton extends JButton{
    MainUI mui;
    public CancelButton(MainUI mui) {
        setSize(80,60);
        setText("Cancel");
        this.mui = mui;
        mui.add(this);        
    }
}

Như bạn có thể thấy sự khác biệt duy nhất là trong setTextchức năng.

Vậy đây có phải là tiêu chuẩn thực hành?

Btw, khóa học mà điều này đã được thảo luận được gọi là Thực tiễn lập trình tốt nhất trong Java

[Trả lời từ Giáo sư]

Vì vậy, tôi đã thảo luận vấn đề với giáo sư và nêu ra tất cả các điểm được đề cập trong các câu trả lời.

Lý do của ông là phân lớp cung cấp mã có thể tái sử dụng trong khi tuân theo các tiêu chuẩn thiết kế GUI. Chẳng hạn, nếu nhà phát triển đã sử dụng các nút tùy chỉnh Okayvà các Cancelnút trong một Cửa sổ, việc đặt các nút tương tự trong các Windows khác cũng sẽ dễ dàng hơn.

Tôi có lý do tôi cho rằng, nhưng vẫn chỉ là khai thác tính kế thừa và làm cho mã dễ vỡ.

Sau đó, bất kỳ nhà phát triển vô tình có thể gọi setTextvào một Okaynút và thay đổi nó. Lớp con chỉ trở nên phiền toái trong trường hợp đó.


3
Tại sao mở rộng JButtonvà gọi các phương thức công khai trong hàm tạo của nó, khi bạn có thể chỉ cần tạo một JButtonvà gọi các phương thức công khai đó bên ngoài lớp?

17
tránh xa giáo sư đó nếu bạn có thể Điều này là khá xa từ thực tiễn tốt nhất . Mã trùng lặp như bạn minh họa là một mùi rất xấu.
njzk2

7
Chỉ cần hỏi anh ấy tại sao, đừng ngần ngại truyền đạt động lực của anh ấy ở đây.
Alex

9
Tôi muốn downvote vì đó là mã khủng khiếp, nhưng tôi cũng muốn upvote vì thật tuyệt khi bạn đặt câu hỏi thay vì chấp nhận một cách mù quáng những gì một giáo sư tồi nói.
Nic Hartley

1
@Alex Tôi đã cập nhật câu hỏi với lời biện minh của prof
Paras

Câu trả lời:


21

Điều này vi phạm Nguyên tắc thay thế LiskovOkayButtonkhông thể thay thế ở bất kỳ nơi nào a Buttondự kiến. Ví dụ: bạn có thể thay đổi nhãn của bất kỳ nút nào bạn muốn. Nhưng làm điều đó với một OkayButtonvi phạm bất biến nội bộ của nó.

Đây là một lạm dụng cổ điển của thừa kế để tái sử dụng mã. Sử dụng một phương pháp trợ giúp thay thế.

Một lý do khác để không làm điều này là đây chỉ là một cách dễ dàng để đạt được điều tương tự như mã tuyến tính.


Điều này không vi phạm LSP trừ khi OkayButtonbạn nghĩ đến điều bất biến. Có OkayButtonthể tuyên bố không có bất kỳ bất biến bổ sung nào, nó chỉ có thể coi văn bản là mặc định và có ý định hỗ trợ đầy đủ các sửa đổi cho văn bản. Vẫn không phải là một ý tưởng tốt, nhưng không phải vì lý do đó.
hvd

Nó không vi phạm vì bạn có thể. Tức là, nếu một phương thức activateButton(Button btn)mong đợi một nút, bạn có thể dễ dàng cung cấp cho nó một ví dụ OkayButton. Nguyên tắc không có nghĩa là nó phải có chức năng chính xác như nhau, vì điều này sẽ khiến việc thừa kế trở nên vô dụng.
16h55

1
@Sebb nhưng bạn không thể sử dụng nó với một hàm setText(button, "x")vì điều đó vi phạm bất biến (giả định). Đúng là OkayButton đặc biệt này có thể không có bất kỳ sự bất biến thú vị nào nên tôi đoán tôi đã chọn một ví dụ tồi. Nhưng nó có thể. Ví dụ, nếu trình xử lý nhấp chuột nói if (myText == "OK") ProcessOK(); else ProcessCancel();gì? Sau đó, văn bản là một phần của bất biến.
usr

@usr Tôi không nghĩ về văn bản là một phần của bất biến. Bạn nói đúng, nó đã vi phạm rồi.
16:00

50

Nó hoàn toàn khủng khiếp theo mọi cách có thể. Nhiều nhất , sử dụng chức năng của nhà máy để tạo JButtons. Bạn chỉ nên thừa kế từ họ nếu bạn có một số nhu cầu mở rộng nghiêm trọng.


20
+1. Để tham khảo thêm, hãy xem phân cấp lớp Swing của AbstractButton . Sau đó xem hệ thống phân cấp của JToggleButton . Kế thừa được sử dụng để khái niệm các loại nút khác nhau . Nhưng nó không được sử dụng để phân biệt các khái niệm kinh doanh ( Có, Không, Tiếp tục, Hủy ... ).
Laiv

2
@Laiv: thậm chí có loại riêng biệt cho, ví dụ như JButton, JCheckBox, JRadioButton, chỉ là một thỏa hiệp với các nhà phát triển quen thuộc với các bộ công cụ khác, như AWT đồng bằng, nơi các loại như là tiêu chuẩn. Về nguyên tắc, tất cả chúng có thể được xử lý bởi một lớp nút duy nhất. Đó là sự kết hợp giữa mô hình và đại biểu UI tạo nên sự khác biệt thực sự.
Holger

Có, họ có thể được xử lý bằng một nút duy nhất. Tuy nhiên, hệ thống phân cấp thực tế có thể được bộc lộ dưới dạng các thông lệ tốt. Để tạo một Nút mới hoặc chỉ để ủy quyền các điều khiển của sự kiện và hành vi cho các thành phần khác là vấn đề ưu tiên. Nếu là tôi, tôi sẽ mở rộng các thành phần Swing để mô hình hóa nút nợ của mình và gói gọn các hành vi, hành động của nó, ... Chỉ để dễ dàng thực hiện nó trong dự án và giúp cho đàn em dễ dàng. Trong web envs tôi thích các thành phần web hơn xử lý sự kiện quá nhiều. Dù sao đi nữa. Bạn nói đúng, chúng tôi có thể tách rời UI khỏi Behaivors.
Laiv

12

Đây là một cách viết mã không chuẩn. Thông thường, bạn chỉ hiếm khi tạo các lớp con của các thành phần UI UI, phổ biến nhất là JFrame (để thiết lập các cửa sổ con và trình xử lý sự kiện), nhưng ngay cả việc phân lớp đó là không cần thiết và được nhiều người nản lòng. Việc cung cấp tùy chỉnh văn bản cho các nút và tương tự thường được thực hiện bằng cách mở rộng lớp AbstractAction (hoặc giao diện Hành động mà nó cung cấp). Điều này có thể cung cấp văn bản, biểu tượng và tùy chỉnh trực quan cần thiết khác và liên kết chúng với mã thực tế mà chúng đại diện. Đây là một cách viết mã UI tốt hơn nhiều so với các ví dụ bạn hiển thị.

(nhân tiện, học giả google chưa bao giờ nghe về bài báo mà bạn trích dẫn - bạn có tài liệu tham khảo chính xác hơn không?)


Chính xác thì "cách tiêu chuẩn" là gì? Tôi đồng ý rằng viết một lớp con cho mọi thành phần có thể là một ý tưởng tồi, nhưng các hướng dẫn chính thức của Swing vẫn ủng hộ thực hành này. Tôi nghĩ giáo sư chỉ theo phong cách thể hiện trong tài liệu chính thức của khung.
ĐẾN TỪ

@COMEFROM Tôi thừa nhận đã không đọc toàn bộ hướng dẫn Xoay, vì vậy có thể tôi đã bỏ lỡ phong cách này được sử dụng, nhưng các trang tôi đã xem có xu hướng sử dụng các lớp cơ sở và không lái các lớp con, ví dụ: docs.oracle .com / javase / guide / uiswing / thành phần / button.html
Jules

@Jules xin lỗi về sự nhầm lẫn, ý tôi là khóa học / bài báo mà giáo sư dạy được gọi là Thực tiễn tốt nhất trong Java
Paras

1
Nói chung là đúng, nhưng có một ngoại lệ lớn khác - JPanel. Nó thực sự hữu ích hơn cho lớp con hơn thế JFrame, vì một bảng điều khiển chỉ là một thùng chứa trừu tượng.
Thông thường

10

IMHO, Thực tiễn lập trình tốt nhất trong Java được định nghĩa bởi cuốn sách "Java hiệu quả" của Joshua Bloch. Thật tuyệt khi giáo viên của bạn đang đưa cho bạn các bài tập OOP và điều quan trọng là học cách đọc và viết các kiểu lập trình của người khác. Nhưng bên ngoài cuốn sách của Josh Bloch, các ý kiến ​​khác nhau khá nhiều về các thực tiễn tốt nhất.

Nếu bạn sẽ mở rộng lớp này, bạn cũng có thể tận dụng sự kế thừa. Tạo một lớp MyButton để quản lý mã chung và lớp con cho các phần biến:

class MyButton extends JButton{
    protected final MainUI mui;
    public MyButton(MainUI mui, String text) {
        setSize(80,60);
        setText(text);
        this.mui = mui;
        mui.add(this);        
    }
}

class OkayButton extends MyButton{
    public OkayButton(MainUI mui) {
        super(mui, "Okay");
    }
}

class CancelButton extends MyButton{
    public CancelButton(MainUI mui) {
        super(mui, "Cancel");
    }
}

Khi nào thì đây là một ý tưởng tốt? Khi bạn sử dụng các loại bạn đã tạo! Chẳng hạn, nếu bạn có chức năng tạo cửa sổ bật lên và chữ ký là:

public void showPopUp(String text, JButton ok, JButton cancel)

Những loại bạn vừa tạo không làm gì tốt cả. Nhưng:

public void showPopUp(String text, OkButton ok, CancelButton cancel)

Bây giờ bạn đã tạo ra một cái gì đó hữu ích.

  1. Trình biên dịch xác minh rằng showPopUp có một OkButton và CancButton. Ai đó đọc mã biết chức năng này được sử dụng như thế nào vì loại tài liệu này sẽ gây ra lỗi thời gian biên dịch nếu nó lỗi thời. Đây là một lợi ích CHÍNH. Các nghiên cứu thực nghiệm 1 hoặc 2 về lợi ích của an toàn loại cho thấy hiểu biết về mã người là lợi ích duy nhất có thể định lượng.

  2. Điều này cũng ngăn ngừa lỗi khi bạn đảo ngược thứ tự các nút bạn chuyển đến chức năng. Những lỗi này rất khó phát hiện, nhưng chúng cũng khá hiếm, vì vậy đây là một lợi ích nhỏ. Điều này đã được sử dụng để bán loại an toàn, nhưng chưa được chứng minh bằng thực nghiệm là đặc biệt hữu ích.

  3. Dạng đầu tiên của chức năng linh hoạt hơn vì nó sẽ hiển thị bất kỳ hai nút nào. Đôi khi điều đó tốt hơn là hạn chế loại nút sẽ sử dụng. Chỉ cần nhớ rằng bạn phải kiểm tra chức năng của bất kỳ nút nào trong nhiều trường hợp - nếu nó chỉ hoạt động khi bạn vượt qua chính xác loại nút bạn không làm bất kỳ ai ủng hộ bằng cách giả vờ nó sẽ lấy bất kỳ loại nút nào.

Vấn đề với lập trình hướng đối tượng là nó đặt giỏ hàng trước ngựa. Trước tiên, bạn cần viết hàm để biết chữ ký là gì để biết liệu có đáng để tạo kiểu cho nó không. Nhưng trong Java, bạn cần tạo các kiểu của mình trước để có thể viết chữ ký hàm.

Vì lý do này, bạn có thể muốn viết một số mã Clojure để xem mức độ tuyệt vời của việc viết các chức năng của bạn trước tiên. Bạn có thể mã hóa nhanh chóng, suy nghĩ về độ phức tạp tiệm cận càng nhiều càng tốt và giả định về tính bất biến của Clojure ngăn chặn các lỗi nhiều như các kiểu trong Java.

Tôi vẫn là một fan hâm mộ của các loại tĩnh cho các dự án lớn và hiện sử dụng một số tiện ích chức năng cho phép tôi viết các hàm trước và đặt tên cho các loại của mình sau trong Java . Chỉ là một ý nghĩ.

PS Tôi đã thực hiện muicon trỏ cuối cùng - nó không cần phải thay đổi.


6
Trong 3 điểm của bạn, 1 và 3 cũng có thể được xử lý bằng JButton chung và sơ đồ đặt tên tham số đầu vào thích hợp. điểm 2 thực sự là một nhược điểm ở chỗ nó làm cho mã của bạn được liên kết chặt chẽ hơn: nếu bạn KHÔNG muốn đảo ngược thứ tự nút thì sao? Điều gì sẽ xảy ra nếu, thay vì các nút OK / Hủy, bạn muốn sử dụng các nút Có / Không? Bạn sẽ tạo một phương thức showPopup hoàn toàn mới có YesButton và Nobutton chứ? Nếu bạn chỉ sử dụng JButton mặc định, bạn không cần tạo kiểu trước vì chúng đã tồn tại.
Nzall

Mở rộng những gì @NateKerkhofs đã nói, một IDE hiện đại sẽ hiển thị cho bạn các tên đối số chính thức trong khi bạn nhập các biểu thức thực tế.
Solomon chậm

8

Tôi sẵn sàng đặt cược rằng giáo viên của bạn không thực sự tin rằng bạn nên luôn mở rộng thành phần Xoay để sử dụng nó. Tôi cá là họ chỉ sử dụng điều này như một ví dụ để buộc bạn thực hành mở rộng các lớp học. Tôi sẽ không lo lắng quá nhiều về các thực hành tốt nhất trong thế giới thực.

Điều đó đang được nói, trong thế giới thực, chúng tôi ủng hộ sáng tác hơn thừa kế .

Quy tắc "người ta chỉ nên mở rộng một lớp nếu có mối quan hệ IS-A" không đầy đủ. Nó sẽ kết thúc bằng "... và chúng ta cần thay đổi hành vi mặc định của lớp" bằng các chữ in đậm lớn.

Ví dụ của bạn không phù hợp với tiêu chí đó. Bạn không cần phải extendcác JButtonlớp chỉ để thiết lập văn bản của nó. Bạn không cần phải extendcác JFramelớp chỉ để thêm các thành phần với nó. Bạn có thể làm những điều này tốt bằng cách sử dụng các triển khai mặc định của chúng, vì vậy việc thêm thừa kế chỉ là thêm các biến chứng không cần thiết.

Nếu tôi thấy một lớp mà extendsmột lớp khác, tôi sẽ tự hỏi lớp đó đang thay đổi . Nếu bạn không thay đổi bất cứ điều gì, đừng bắt tôi phải xem qua lớp học.

Quay lại câu hỏi của bạn: khi nào bạn nên mở rộng một lớp Java? Khi bạn có một lý do thực sự, thực sự, thực sự tốt để mở rộng một lớp học.

Đây là một ví dụ cụ thể: một cách để thực hiện vẽ tùy chỉnh (cho trò chơi hoặc hoạt hình hoặc chỉ cho một thành phần tùy chỉnh) là bằng cách mở rộng JPanellớp. (Xem thêm thông tin về điều đó ở đây .) Bạn mở rộng JPanellớp vì bạn cần phải ghi đè lên các paintComponent()chức năng. Bạn đang thực sự thay đổi hành vi của lớp bằng cách làm điều này. Bạn không thể thực hiện vẽ tùy chỉnh với việc JPanelthực hiện mặc định .

Nhưng như tôi đã nói, giáo viên của bạn có lẽ chỉ sử dụng những ví dụ này như một cái cớ để buộc bạn thực hành mở rộng các lớp học.


1
Oh chờ đợi, bạn có thể thiết lập một Bordersơn trang trí bổ sung. Hoặc tạo JLabelvà truyền một Icontriển khai tùy chỉnh cho nó. Không cần thiết phải phân lớp JPanelđể vẽ (và tại sao JPanelthay vì JComponent).
Holger

1
Tôi nghĩ rằng bạn có ý tưởng đúng ở đây, nhưng có lẽ có thể chọn các ví dụ tốt hơn.

1
Nhưng tôi muốn nhắc lại câu trả lời của bạn là chính xác và bạn đã ghi điểm tốt . Tôi chỉ nghĩ rằng ví dụ cụ thể và hướng dẫn hỗ trợ nó yếu.

1
@KevinWorkman Tôi không biết về phát triển trò chơi Swing, nhưng tôi đã thực hiện một số UI tùy chỉnh trong Swing và "không mở rộng các lớp Swing để dễ dàng kết nối các thành phần và trình kết xuất thông qua cấu hình" khá chuẩn ở đó.

1
"Tôi cá là họ chỉ sử dụng điều này như một ví dụ để buộc bạn ..." Một trong những người bạn lớn của tôi! Giáo viên hướng dẫn cách làm X bằng cách sử dụng các ví dụ loạn trí khủng khiếp về lý do nên làm X.
Solomon Slow
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.