Mở rộng enum thông qua kế thừa


85

Tôi biết điều này đi ngược lại với ý tưởng về enums, nhưng liệu có thể mở rộng enums trong C # / Java không? Ý tôi là "mở rộng" theo cả nghĩa là thêm các giá trị mới vào một enum, nhưng cũng theo nghĩa OO là kế thừa từ một enum hiện có.

Tôi cho rằng điều đó là không thể trong Java, vì nó chỉ có chúng khá gần đây (Java 5?). Tuy nhiên, C # dường như dễ tha thứ hơn cho những người muốn làm những điều điên rồ, vì vậy tôi nghĩ nó có thể có khả năng theo một cách nào đó. Có lẽ nó có thể bị tấn công thông qua phản chiếu (không phải bạn thực sự sử dụng phương pháp đó)?

Tôi không nhất thiết phải quan tâm đến việc thực hiện bất kỳ phương pháp nhất định nào, nó chỉ khơi gợi sự tò mò của tôi khi nó xảy ra với tôi :-)


Điều này có trả lời câu hỏi của bạn không? Enum "Inheritance"
T.Todua 30/10/19

Câu trả lời:


103

Lý do bạn không thể mở rộng Enums là vì nó sẽ dẫn đến các vấn đề với tính đa hình.

Giả sử bạn có một enum MyEnum với các giá trị A, B và C và mở rộng nó với giá trị D là MyExtEnum.

Giả sử một phương thức mong đợi một giá trị myEnum ở đâu đó, chẳng hạn như một tham số. Việc cung cấp giá trị MyExtEnum là hợp pháp, vì đó là một kiểu con, nhưng bây giờ bạn sẽ làm gì khi giá trị đó là D?

Để loại bỏ vấn đề này, mở rộng enums là bất hợp pháp


4
Thực ra, lý do đơn giản là nó không có ý nghĩa gì cả. Vấn đề bạn đề cập, tức là mã máy khách nhận được một hằng số mà nó không mong đợi, vẫn tồn tại với việc triển khai hiện tại - trên thực tế, trình biên dịch không cho phép bạn switchsử dụng các giá trị enum mà không cung cấp defaulttrường hợp hoặc đưa ra một ngoại lệ. Và, ngay cả khi nó đã làm, enum trong thời gian chạy có thể đến từ một bộ sưu tập riêng biệt
Raffaele

@Raffaele Tôi vừa thử cái này, và ít nhất trong Java 7, bạn có thể bật enum mà không cần hộp đựng defaulthoặc ném.
Dathan

@Dathan bạn chắc chắn đúng! Khi nó được viết, điều đó chỉ đơn giản là sai - có lẽ tôi nên xóa nó? Tôi đã nghĩ đến trường hợp bạn sử dụng một switchđể cung cấp một giá trị bắt buộc, chẳng hạn như chữ cái đầu của một địa phương hoặc đếnreturn
Raffaele

3
Đây không phải là chuyện cá nhân, Rik. Nhưng đây là lý do tồi tệ nhất từ ​​trước đến nay! Polymorphism và enum ...DayOfWeek a = (DayOfWeek) 1; DayOfWeek b = (DayOfWeek) 4711; Console.WriteLine(a + ", " + b);
Bitterblue

41

Khi enums tích hợp sẵn không đủ, bạn có thể làm theo cách cũ và tự chế tạo. Ví dụ: nếu bạn muốn thêm một thuộc tính bổ sung, ví dụ: trường mô tả, bạn có thể thực hiện như sau:

public class Action {
    public string Name {get; private set;}
    public string Description {get; private set;}

    private Action(string name, string description) {
        Name = name;
        Description = description;
    }

    public static Action DoIt = new Action("Do it", "This does things");
    public static Action StopIt = new Action("Stop It", "This stops things");
}

Sau đó, bạn có thể coi nó như một enum như vậy:

public void ProcessAction(Action a) {
    Console.WriteLine("Performing action: " + a.Name)
    if (a == Action.DoIt) {
       // ... and so on
    }
}

Bí quyết là đảm bảo rằng hàm tạo là riêng tư (hoặc được bảo vệ nếu bạn muốn kế thừa) và các thể hiện của bạn là tĩnh.


Tôi không phải dân C #, nhưng bạn không muốn một chút cuối cùng (hoặc niêm phong / const / bất cứ điều gì) trong đó?
Tom Hawtin - tackline

1
Tôi không tin như vậy. Hoặc, ít nhất, không phải đối với trường hợp bạn muốn kế thừa từ lớp này để thêm các giá trị "enum" mới. Cuối cùng và niêm phong ngăn chặn kế thừa IIRC.
alastairs

4
@alastairs Tôi nghĩ ý của anh ấy là thêm finalvào public static Action DoIt = new Action("Do it", "This does things");dòng chứ không phải lớp.
nghiền nát

1
Như bằng cách thêm readonly, ala:public static readonly Action DoIt = new Action("Do it", "This does things");
ErikE

40

Bạn đang đi sai đường: một lớp con của một enum sẽ có ít mục nhập hơn .

Trong mã giả, hãy nghĩ:

enum Animal { Mosquito, Dog, Cat };
enum Mammal : Animal { Dog, Cat };  // (not valid C#)

Bất kỳ phương pháp nào có thể chấp nhận Động vật sẽ có thể chấp nhận Động vật có vú, nhưng không phải ngược lại. Phân lớp là để làm cho một cái gì đó cụ thể hơn, không chung chung hơn. Đó là lý do tại sao "đối tượng" là gốc của hệ thống phân cấp lớp. Tương tự như vậy, nếu enum có thể kế thừa, thì một gốc giả định của hệ thống phân cấp enum sẽ có mọi ký hiệu có thể.

Nhưng không, C # / Java không cho phép các sub-enums, AFAICT, mặc dù đôi khi nó thực sự hữu ích. Có thể là do họ đã chọn triển khai Enums dưới dạng int (như C) thay vì các ký hiệu thực tập (như Lisp). (Ở trên, (Động vật) 1 đại diện cho cái gì và (Động vật có vú) 1 đại diện cho cái gì, và chúng có cùng giá trị không?)

Tuy nhiên, bạn có thể viết lớp giống enum của riêng mình (với một tên khác) cung cấp điều này. Với các thuộc tính C #, nó thậm chí có thể trông khá đẹp.


3
phân tích thú vị. không bao giờ nghĩ về nó theo cách này!
Nerrve

Giao nhau, nhưng tôi nghĩ nó là sai. Hàm ý rằng nên có ít loại hơn trong một lớp con có ý nghĩa về cây phân loại động vật, nhưng không hợp lý về mặt mã. Khi bạn phân lớp trong mã, không bao giờ có ít phương thức hơn. Không bao giờ có ít biến thành viên. Lập luận của bạn dường như không áp dụng cho phần mềm cũng như đối với phân loại động vật.
Kieveli

4
@Kieveli, phân tích của bạn sai rồi. Việc thêm một thành viên không có tác dụng tương tự đối với một đối tượng như việc thêm vào tập các giá trị có thể có của nó. Đây không phải là thứ Ken bịa ra; có các quy tắc rõ ràng và chính xác của khoa học máy tính giải thích tại sao nó hoạt động theo cách này. Hãy thử tìm kiếm hiệp phương sai và phương sai (không chỉ là thủ dâm học thuật, nó sẽ giúp bạn hiểu các quy tắc cho những thứ như chữ ký hàm và vùng chứa).
jwg

@Kieveli Giả sử bạn có một lớp 'StreamWriter' (lấy luồng - ghi luồng). Bạn sẽ tạo 'TextWriter: StreamWriter' (lấy dòng - viết văn bản). Bây giờ bạn tạo 'HtmlWriter: TextWriter' (lấy dòng - viết html khá đẹp). Bây giờ HtmlWriter rõ ràng là có nhiều thành viên hơn, các phương thức, v.v. Bây giờ hãy thử dùng một dòng suối từ một card âm thanh và ghi nó vào tập tin sử dụng HtmlWriter :)
evictednoise

Ví dụ này là giả thiết, và không chứng minh rằng không bao giờ nên có NHIỀU giá trị enum hơn trong lớp mở rộng. enumosystemParts {Giấy phép, Tốc độ, Công suất}; enum CarParts {DrivingWheel, Winshield}; + tất cả mọi thứ mà Xe ô tô cũng có
Bernoulli Lizard

12

Enums được cho là đại diện cho việc liệt kê tất cả các giá trị có thể có, vì vậy việc mở rộng thay vì đi ngược lại ý tưởng.

Tuy nhiên, những gì bạn có thể làm trong Java (và có lẽ là C ++ 0x) là có một giao diện thay vì một lớp enum. Sau đó, đặt bạn các giá trị tiêu chuẩn trong một enum triển khai tính năng. Rõ ràng là bạn không thể sử dụng java.util.EnumSet và những thứ tương tự. Đây là cách tiếp cận được thực hiện trong "các tính năng NIO khác", nên có trong JDK7.

public interface Result {
    String name();
    String toString();
}
public enum StandardResults implements Result {
    TRUE, FALSE
}


public enum WTFResults implements Result {
    FILE_NOT_FOUND
}

4

Bạn có thể sử dụng phản chiếu .NET để truy xuất các nhãn và giá trị từ một enum hiện có tại thời điểm chạy ( Enum.GetNames()Enum.GetValues()là hai phương pháp cụ thể mà bạn sẽ sử dụng) và sau đó sử dụng mã chèn để tạo một cái mới với những phần tử đó cùng với một số cái mới. Điều này có vẻ hơi khó chịu khi "kế thừa từ một enum hiện có".


2

Thêm enums là một việc khá phổ biến phải làm nếu bạn quay lại mã nguồn và chỉnh sửa, bất kỳ cách nào khác (kế thừa hoặc phản chiếu, nếu có thể) đều có khả năng quay lại và tấn công bạn khi bạn nhận được bản nâng cấp của thư viện và họ đã giới thiệu cùng một tên enum hoặc cùng một giá trị enum - tôi đã thấy rất nhiều mã cấp thấp trong đó số nguyên khớp với mã hóa nhị phân, nơi bạn sẽ gặp sự cố

Lý tưởng nhất là mã tham chiếu enum nên được viết dưới dạng chỉ bằng (hoặc chuyển mạch) và cố gắng trở thành bằng chứng trong tương lai bằng cách không mong đợi bộ enum là const


2

Nếu bạn muốn mở rộng theo nghĩa lớp Cơ sở, thì trong Java ... không.

Nhưng bạn có thể mở rộng một giá trị enum để có các thuộc tính và phương thức nếu đó là ý của bạn.

Ví dụ, phần sau sử dụng một enum Bracket:

class Person {
    enum Bracket {
        Low(0, 12000),
        Middle(12000, 60000),
        Upper(60000, 100000);

        private final int low;
        private final int high;
        Brackets(int low, int high) {
            this.low = low;
            this.high = high;
        }

        public int getLow() {
            return low;
        }

        public int getHigh() {
            return high;
        }

        public boolean isWithin(int value) {
           return value >= low && value <= high;
        }

        public String toString() {
            return "Bracket " + low + " to " + high;
        }
    }

    private Bracket bracket;
    private String name;

    public Person(String name, Bracket bracket) {
        this.bracket = bracket;
        this.name = name;
    }

    public String toString() {
        return name + " in " + bracket;
    }        
}

Điều này nghe giống như một kiểu giá trị (struct) trong .NET. Tôi không biết bạn có thể làm điều này bằng Java.
alastairs

2

Tôi không thấy ai khác đề cập đến vấn đề này nhưng giá trị thứ tự của một enum rất quan trọng. Ví dụ, với grails khi bạn lưu một enum vào cơ sở dữ liệu, nó sử dụng giá trị thứ tự. Nếu bằng cách nào đó bạn có thể mở rộng một enum, thì giá trị thứ tự của các phần mở rộng của bạn sẽ là gì? Nếu bạn mở rộng nó ở nhiều nơi, làm thế nào bạn có thể duy trì một số loại trật tự cho các thứ tự này? Sự hỗn loạn / không ổn định trong các giá trị thứ tự sẽ là một điều tồi tệ, có lẽ là một lý do khác khiến các nhà thiết kế ngôn ngữ không đề cập đến điều này.

Một khó khăn khác nếu bạn là nhà thiết kế ngôn ngữ, làm thế nào bạn có thể bảo toàn chức năng của phương thức giá trị () được cho là trả về tất cả các giá trị enum. Bạn sẽ gọi nó bằng cách nào và nó sẽ thu thập tất cả các giá trị như thế nào?




0

Hmmm - theo như tôi biết, điều này không thể được thực hiện - các bảng liệt kê được viết tại thời điểm thiết kế và được sử dụng như một sự thuận tiện cho lập trình viên.

Tôi khá chắc chắn rằng khi mã được biên dịch, các giá trị tương đương sẽ được thay thế cho các tên trong bảng liệt kê của bạn, do đó loại bỏ khái niệm về một kiểu liệt kê và (do đó) khả năng mở rộng nó.


0

Tôi muốn có thể thêm giá trị vào bảng liệt kê C # là sự kết hợp của các giá trị hiện có. Ví dụ (đây là những gì tôi muốn làm):

AnchorStyles được định nghĩa là

public enum AnchorStyles { None = 0, Top = 1, Bottom = 2, Left = 4, Right = 8, }

và tôi muốn thêm AnchorStyles.BottomRight = Right + Bottom để thay vì nói

my_ctrl.Anchor = AnchorStyles.Right | AnchorStyles.Bottom;

Tôi chỉ có thể nói

my_ctrl.Anchor = AnchorStyles.BottomRight;

Điều này không gây ra bất kỳ vấn đề nào đã được đề cập ở trên, vì vậy sẽ rất tuyệt nếu có thể.


0

Một thời gian trước, tôi thậm chí còn muốn làm điều gì đó như thế này và nhận thấy rằng phần mở rộng enum sẽ giải thích rất nhiều khái niệm cơ bản ... (Không chỉ là polymorphisim)

Nhưng bạn vẫn có thể cần phải làm gì nếu enum được khai báo trong thư viện bên ngoài và Hãy nhớ rằng bạn nên thận trọng đặc biệt khi sử dụng phần mở rộng enum này ...

public enum MyEnum { A = 1, B = 2, C = 4 }

public const MyEnum D = (MyEnum)(8);
public const MyEnum E = (MyEnum)(16);

func1{
    MyEnum EnumValue = D;

    switch (EnumValue){
      case D:  break;
      case E:  break;
      case MyEnum.A:  break;
      case MyEnum.B:  break;
   }
}

0

Đối với java, nó không được phép vì việc thêm các phần tử vào một enum sẽ tạo ra một lớp siêu cấp hơn là một lớp con.

Xem xét:

 enum Person (JOHN SAM}   
 enum Student extends Person {HARVEY ROSS}

Một trường hợp sử dụng chung của Đa hình sẽ là

 Person person = Student.ROSS;   //not legal

mà rõ ràng là sai.


0

Một giải pháp tạm thời / cục bộ , khi bạn chỉ muốn sử dụng rất cục bộ / một lần:

enum Animals { Dog, Cat }
enum AnimalsExt { Dog = Animals.Dog, Cat= Animals.Cat,  MyOther}
// BUT CAST THEM when using:
var xyz = AnimalsExt.Cat;
MethodThatNeedsAnimal(   (Animals)xyz   );

Xem tất cả các câu trả lời tại: Enum "Người thừa kế"

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.