Tại sao không phải là các trường trừu tượng?


100

Tại sao các lớp Java không thể có các trường trừu tượng giống như chúng có thể có các phương thức trừu tượng?

Ví dụ: Tôi có hai lớp mở rộng cùng một lớp cơ sở trừu tượng. Hai lớp này đều có một phương thức giống hệt nhau ngoại trừ một hằng số Chuỗi, điều này xảy ra là một thông báo lỗi, bên trong chúng. Nếu các trường có thể là trừu tượng, tôi có thể làm cho hằng số này trừu tượng và kéo phương thức lên lớp cơ sở. Thay vào đó, tôi phải tạo một phương thức trừu tượng, được gọi getErrMsg()trong trường hợp này, trả về Chuỗi, ghi đè phương thức này trong hai lớp dẫn xuất và sau đó tôi có thể kéo lên phương thức (bây giờ gọi phương thức trừu tượng).

Tại sao tôi không thể chỉ làm cho trường trừu tượng để bắt đầu? Có thể Java đã được thiết kế để cho phép điều này?


3
Có vẻ như bạn có thể đã bỏ qua toàn bộ vấn đề này bằng cách làm cho trường không phải là hằng số và chỉ cần cung cấp giá trị thông qua hàm tạo, kết thúc bằng 2 trường hợp của một lớp thay vì 2 lớp.
Nate

5
Bằng cách làm cho các trường trở nên trừu tượng trong một lớp siêu, bạn phải cụ thể rằng mọi lớp con phải có trường này, vì vậy điều này không khác gì một trường không trừu tượng.
Peter Lawrey

@peter, tôi không chắc mình đang theo quan điểm của bạn. nếu một hằng số không trừu tượng được chỉ định trong lớp trừu tượng, thì giá trị của nó cũng không đổi qua tất cả các lớp con. nếu nó là trừu tượng, thì giá trị của nó sẽ phải được thực hiện / cung cấp bởi mỗi lớp con. vì vậy, nó sẽ không giống nhau ở tất cả.
liltitus27

1
@ liltitus27 Tôi nghĩ rằng quan điểm của tôi cách đây 3,5 năm là việc có các trường trừu tượng sẽ không thay đổi nhiều ngoại trừ việc phá vỡ toàn bộ ý tưởng tách người dùng của một giao diện khỏi việc triển khai.
Peter Lawrey

Điều này sẽ hữu ích vì nó có thể cho phép chú thích trường tùy chỉnh trong lớp con
geg

Câu trả lời:


103

Bạn có thể làm những gì bạn đã mô tả bằng cách có một trường cuối cùng trong lớp trừu tượng của bạn được khởi tạo trong phương thức khởi tạo của nó (mã chưa được kiểm tra):

abstract class Base {

    final String errMsg;

    Base(String msg) {
        errMsg = msg;
    }

    abstract String doSomething();
}

class Sub extends Base {

    Sub() {
        super("Sub message");
    }

    String doSomething() {

        return errMsg + " from something";
    }
}

Nếu lớp con của bạn "quên" khởi tạo cuối cùng thông qua phương thức siêu khởi tạo, trình biên dịch sẽ đưa ra cảnh báo lỗi, giống như khi một phương thức trừu tượng không được triển khai.


Không có một trình chỉnh sửa nào để thử nó ở đây, nhưng đây là những gì tôi cũng sẽ đăng ..
Tim

6
"trình biên dịch sẽ đưa ra cảnh báo". Trên thực tế, hàm tạo Con sẽ cố gắng sử dụng một hàm tạo noargs không tồn tại và đó là lỗi biên dịch (không phải là cảnh báo).
Stephen C

Và thêm vào bình luận của @Stephen C, "như khi một phương thức trừu tượng không được thực hiện" cũng là một lỗi, không phải là một cảnh báo.
Laurence Gonsalves

4
Câu trả lời tốt - điều này đã làm việc cho tôi. Tôi biết nó đã được một thời gian kể từ khi nó được đăng nhưng nghĩ rằng Basecần phải được khai báo abstract.
Steve Chambers

2
Tôi vẫn không thích cách tiếp cận này (mặc dù đó là cách duy nhất để đi) vì nó thêm một đối số bổ sung vào một hàm tạo. Trong lập trình mạng, tôi thích tạo các kiểu thông báo, trong đó mỗi thông báo mới thực hiện một kiểu trừu tượng nhưng phải có một ký tự được chỉ định duy nhất như 'A' cho AcceptMessage và 'L' cho LoginMessage. Những điều này đảm bảo rằng một giao thức tin nhắn tùy chỉnh sẽ có thể đặt ký tự phân biệt này trên dây. Những thứ này là ngầm định đối với lớp, vì vậy chúng tốt nhất nên được phục vụ dưới dạng các trường, không phải thứ được truyền vào hàm tạo.
Adam Hughes

7

Tôi thấy không có ích gì trong đó. Bạn có thể di chuyển hàm đến lớp trừu tượng và chỉ cần ghi đè một số trường được bảo vệ. Tôi không biết liệu điều này có hoạt động với hằng số hay không nhưng hiệu quả là giống nhau:

public abstract class Abstract {
    protected String errorMsg = "";

    public String getErrMsg() {
        return this.errorMsg;
    }
}

public class Foo extends Abstract {
    public Foo() {
       this.errorMsg = "Foo";
    }

}

public class Bar extends Abstract {
    public Bar() {
       this.errorMsg = "Bar";
    }
}

Vì vậy, quan điểm của bạn là bạn muốn thực thi việc thực thi / ghi đè / bất cứ điều gì errorMsgtrong các lớp con? Tôi nghĩ rằng bạn chỉ muốn có phương thức trong lớp cơ sở và không biết làm thế nào để xử lý trường sau đó.


4
Tất nhiên, tôi có thể làm điều đó. Và tôi đã nghĩ về nó. Nhưng tại sao tôi phải đặt một giá trị trong lớp cơ sở khi tôi biết thực tế là tôi sẽ ghi đè giá trị đó trong mọi lớp dẫn xuất? Lập luận của bạn cũng có thể được sử dụng để lập luận rằng việc cho phép các phương thức trừu tượng cũng không có giá trị.
Paul Reiners

1
Bằng cách này, không phải mọi lớp con đều phải ghi đè giá trị. Tôi cũng có thể chỉ định nghĩa protected String errorMsg;cái nào thực thi bằng cách nào đó mà tôi đặt giá trị trong các lớp con. Nó tương tự như những lớp trừu tượng thực sự triển khai tất cả các phương thức dưới dạng trình giữ chỗ để các nhà phát triển không phải triển khai mọi phương thức mặc dù họ không cần nó.
Felix Kling

7
Nói protected String errorMsg;không không thực thi mà bạn thiết lập giá trị trong các lớp con.
Laurence Gonsalves

1
Nếu có giá trị là null nếu bạn không đặt nó được tính là "thực thi", thì bạn sẽ gọi không thực thi là gì?
Laurence Gonsalves

9
@felix, có sự khác biệt giữa thực thihợp đồng . toàn bộ bài đăng này tập trung xung quanh các lớp trừu tượng, đến lượt nó, đại diện cho một hợp đồng phải được thực hiện bởi bất kỳ lớp con nào. vì vậy, khi chúng ta nói về việc có một trường trừu tượng, chúng ta đang nói rằng bất kỳ lớp con nào cũng phải triển khai giá trị của trường đó, trên mỗi hợp đồng. cách tiếp cận của bạn không đảm bảo bất kỳ giá trị nào và không làm rõ những gì được mong đợi, giống như hợp đồng. rất, rất khác.
liltitus27

3

Rõ ràng là nó có thể được thiết kế để cho phép điều này, nhưng dưới vỏ bọc, nó vẫn phải thực hiện điều phối động, và do đó là một cuộc gọi phương thức. Thiết kế của Java (ít nhất là trong những ngày đầu tiên), ở một mức độ nào đó, là một nỗ lực để tối giản hóa. Đó là, các nhà thiết kế đã cố gắng tránh thêm các tính năng mới nếu chúng có thể dễ dàng được mô phỏng bởi các tính năng khác đã có trong ngôn ngữ.


@Laurence - đối với tôi không rõ ràng rằng các trường trừu tượng có thể đã được đưa vào. Một cái gì đó như vậy có thể sẽ có tác động sâu sắc và cơ bản đến hệ thống kiểu và khả năng sử dụng ngôn ngữ. (Trong cùng một cách mà đa kế thừa mang lại những vấn đề sâu ... có thể cắn C ++ không thận trọng lập trình viên.)
Stephen C

0

Đọc tiêu đề của bạn, tôi nghĩ bạn đang đề cập đến các thành viên phiên bản trừu tượng; và tôi không thấy có nhiều tác dụng đối với chúng. Nhưng các thành viên tĩnh trừu tượng hoàn toàn là một vấn đề khác.

Tôi thường ước rằng tôi có thể khai báo một phương thức như sau trong Java:

public abstract class MyClass {

    public static abstract MyClass createInstance();

    // more stuff...

}

Về cơ bản, tôi muốn nhấn mạnh rằng các triển khai cụ thể của lớp cha của tôi cung cấp một phương thức nhà máy tĩnh với một chữ ký cụ thể. Điều này sẽ cho phép tôi nhận được một tham chiếu đến một lớp cụ thể với Class.forName()và chắc chắn rằng tôi có thể xây dựng một lớp theo quy ước mà tôi chọn.


1
Vâng ... điều này có thể có nghĩa là bạn đang sử dụng phản xạ rất nhiều !!
Stephen C

Nghe có vẻ như một thói quen lập trình kỳ lạ đối với tôi. Class.forName (..) có mùi như một lỗ hổng thiết kế.
whiskyierra

Ngày nay, về cơ bản chúng ta luôn sử dụng Spring DI để thực hiện loại việc này; nhưng trước khi khuôn khổ đó được biết đến và phổ biến, chúng tôi sẽ chỉ định các lớp triển khai trong các thuộc tính hoặc tệp XML, sau đó sử dụng Class.forName () để tải chúng.
Drew Wills

Tôi có thể cung cấp cho bạn một trường hợp sử dụng cho chúng. Sự vắng mặt của "lĩnh vực trừu tượng" gần như không thể khai báo một lĩnh vực một kiểu generic trong một lớp học chung trừu tượng: @autowired T fieldName. Nếu Tđược định nghĩa là một cái gì đó giống như T extends AbstractController, tẩy xóa kiểu khiến trường được tạo dưới dạng kiểu AbstractControllervà không thể tự động tải vì nó không cụ thể và không duy nhất. Tôi thường đồng ý rằng chúng không được sử dụng nhiều, và do đó chúng không thuộc về ngôn ngữ. Điều tôi không đồng ý là KHÔNG có tác dụng gì với chúng.
DaBlick

0

Một tùy chọn khác là xác định trường dưới dạng công khai (cuối cùng, nếu bạn muốn) trong lớp cơ sở, rồi khởi tạo trường đó trong phương thức khởi tạo của lớp cơ sở, tùy thuộc vào lớp con nào hiện đang được sử dụng. Nó có một chút mờ ám, trong đó nó giới thiệu một sự phụ thuộc vòng tròn. Nhưng, ít nhất nó không phải là một phụ thuộc có thể thay đổi - tức là, lớp con sẽ tồn tại hoặc không tồn tại, nhưng các phương thức hoặc trường của lớp con không thể ảnh hưởng đến giá trị của field.

public abstract class Base {
  public final int field;
  public Base() {
    if (this instanceof SubClassOne) {
      field = 1;
    } else if (this instanceof SubClassTwo) {
      field = 2;
    } else {
      // assertion, thrown exception, set to -1, whatever you want to do 
      // to trigger an error
      field = -1;
    }
  }
}
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.