TL; DR Cuộn xuống dưới cùng.
Từ những gì tôi thấy, bạn đang triển khai một ngôn ngữ mới trên đầu trang C #. Các enum dường như biểu thị loại định danh (hoặc, bất cứ thứ gì có tên và xuất hiện trong mã nguồn của ngôn ngữ mới), dường như được áp dụng cho các nút được thêm vào biểu diễn cây của chương trình.
Trong tình huống cụ thể này, có rất ít hành vi đa hình giữa các loại nút khác nhau. Nói cách khác, trong khi cây cần có thể chứa các nút thuộc các loại (biến thể) rất khác nhau, thì việc truy cập thực tế của các nút này về cơ bản sẽ dùng đến một chuỗi if-then-other (hoặc instanceof
/ is
check) khổng lồ . Những kiểm tra khổng lồ này có thể sẽ xảy ra ở nhiều nơi khác nhau trên toàn dự án. Đây là lý do tại sao enums có vẻ hữu ích, hoặc, ít nhất chúng cũng hữu ích như instanceof
/ is
kiểm tra.
Mẫu khách truy cập vẫn có thể hữu ích. Nói cách khác, có nhiều kiểu mã hóa khác nhau có thể được sử dụng thay vì chuỗi khổng lồ instanceof
. Tuy nhiên, nếu bạn muốn thảo luận về các lợi ích và nhược điểm khác nhau, bạn sẽ chọn hiển thị một ví dụ mã từ chuỗi xấu nhất instanceof
trong dự án, thay vì ngụy biện về enum.
Điều này không có nghĩa là các lớp và hệ thống phân cấp thừa kế không hữu ích. Hoàn toàn ngược lại. Mặc dù không có bất kỳ hành vi đa hình nào hoạt động trên mọi loại khai báo (ngoài thực tế là mọi tuyên bố đều phải có Name
tài sản), có rất nhiều hành vi đa hình phong phú được chia sẻ bởi anh chị em gần đó. Ví dụ, Function
và Procedure
có thể chia sẻ một số hành vi (cả hai đều có thể gọi được và chấp nhận danh sách các đối số đầu vào được nhập) và PropertyGet
chắc chắn sẽ kế thừa các hành vi từ Function
(cả hai đều có ReturnType
). Bạn có thể sử dụng enum hoặc kiểm tra thừa kế cho chuỗi if-then-other khổng lồ, nhưng các hành vi đa hình, tuy nhiên bị phân mảnh, vẫn phải được thực hiện trong các lớp.
Có nhiều lời khuyên trực tuyến chống lại việc lạm dụng instanceof
/ is
kiểm tra. Hiệu suất không phải là một trong những lý do. Thay vào đó, lý do là để ngăn chặn lập trình viên phát hiện ra các hành vi đa hình phù hợp, như thể instanceof
/ is
là một cái nạng. Nhưng trong tình huống của bạn, bạn không có lựa chọn nào khác, vì các nút này có rất ít điểm chung.
Bây giờ đây là một số gợi ý cụ thể.
Có một số cách để đại diện cho các nhóm không lá.
So sánh đoạn trích sau đây của mã gốc của bạn ...
[Flags]
public enum DeclarationType
{
Member = 1 << 7,
Procedure = 1 << 8 | Member,
Function = 1 << 9 | Member,
Property = 1 << 10 | Member,
PropertyGet = 1 << 11 | Property | Function,
PropertyLet = 1 << 12 | Property | Procedure,
PropertySet = 1 << 13 | Property | Procedure,
LibraryFunction = 1 << 23 | Function,
LibraryProcedure = 1 << 24 | Procedure,
}
đến phiên bản sửa đổi này:
[Flags]
public enum DeclarationType
{
Nothing = 0, // to facilitate bit testing
// Let's assume Member is not a concrete thing,
// which means it doesn't need its own bit
/* Member = 1 << 7, */
// Procedure and Function are concrete things; meanwhile
// they can still have sub-types.
Procedure = 1 << 8,
Function = 1 << 9,
Property = 1 << 10,
PropertyGet = 1 << 11,
PropertyLet = 1 << 12,
PropertySet = 1 << 13,
LibraryFunction = 1 << 23,
LibraryProcedure = 1 << 24,
// new
Procedures = Procedure | PropertyLet | PropertySet | LibraryProcedure,
Functions = Function | PropertyGet | LibraryFunction,
Properties = PropertyGet | PropertyLet | PropertySet,
Members = Procedures | Functions | Properties,
LibraryMembers = LibraryFunction | LibraryProcedure
}
Phiên bản sửa đổi này tránh phân bổ bit cho các loại khai báo không cụ thể. Thay vào đó, các kiểu khai báo không cụ thể (các nhóm trừu tượng của các kiểu khai báo) chỉ đơn giản là có các giá trị enum là bitwise-hoặc (union của các bit) trên tất cả các phần tử con của nó.
Có một cảnh báo: nếu có một kiểu khai báo trừu tượng có một con duy nhất và nếu cần phân biệt giữa kiểu trừu tượng (cha mẹ) với kiểu cụ thể (con), thì kiểu trừu tượng vẫn sẽ cần bit riêng của nó .
Một cảnh báo cụ thể cho câu hỏi này: Property
ban đầu là một định danh (khi bạn chỉ nhìn thấy tên của nó, mà không thấy cách nó được sử dụng trong mã), nhưng nó có thể chuyển thành PropertyGet
/ PropertyLet
/ PropertySet
ngay khi bạn thấy nó được sử dụng như thế nào trong mã. Nói cách khác, ở các giai đoạn phân tích cú pháp khác nhau, bạn có thể cần phải đánh dấu một mã Property
định danh là "tên này đề cập đến một thuộc tính" và sau đó thay đổi thành "dòng mã này đang truy cập thuộc tính này theo một cách nhất định".
Để giải quyết cảnh báo này, bạn có thể cần hai bộ enum; một enum biểu thị tên (định danh) là gì; enum khác biểu thị những gì mã đang cố gắng làm (ví dụ: khai báo phần thân của một cái gì đó; cố gắng sử dụng một cái gì đó theo một cách nhất định).
Xem xét liệu thông tin phụ trợ về mỗi giá trị enum có thể được đọc từ một mảng thay thế hay không.
Đề xuất này loại trừ lẫn nhau với các đề xuất khác vì nó yêu cầu chuyển đổi các giá trị lũy thừa của hai giá trị thành các giá trị nguyên không âm nhỏ.
public enum DeclarationType
{
Procedure = 8,
Function = 9,
Property = 10,
PropertyGet = 11,
PropertyLet = 12,
PropertySet = 13,
LibraryFunction = 23,
LibraryProcedure = 24,
}
static readonly bool[] DeclarationTypeIsMember = new bool[32]
{
?, ?, ?, ?, ?, ?, ?, ?, // bit[0] ... bit[7]
true, true, true, true, true, true, ?, ?, // bit[8] ... bit[15]
?, ?, ?, ?, ?, ?, ?, true, // bit[16] ... bit[23]
true, ... // bit[24] ...
}
static bool IsMember(DeclarationType dt)
{
int intValue = (int)dt;
return (intValue < 0 || intValue >= 32) ? false : DeclarationTypeIsMember[intValue];
// you can also throw an exception if the enum is outside range.
}
// likewise for IsFunction(dt), IsProcedure(dt), IsProperty(dt), ...
Bảo trì sẽ có vấn đề.
Kiểm tra xem ánh xạ một-một giữa các loại C # (các lớp trong hệ thống phân cấp thừa kế) và các giá trị enum của bạn.
(Ngoài ra, bạn có thể điều chỉnh các giá trị enum của mình để đảm bảo ánh xạ một-một với các loại.)
Trong C #, rất nhiều thư viện lạm dụng Type object.GetType()
phương pháp tiện lợi , tốt hay xấu.
Bất cứ nơi nào bạn đang lưu trữ enum như một giá trị, bạn có thể tự hỏi liệu bạn có thể lưu trữ Type
dưới dạng giá trị thay thế không.
Để sử dụng thủ thuật này, bạn có thể khởi tạo hai bảng băm chỉ đọc, cụ thể là:
// For disambiguation, I'll assume that the actual
// (behavior-implementing) classes are under the
// "Lang" namespace.
static readonly Dictionary<Type, DeclarationType> TypeToDeclEnum = ...
{
{ typeof(Lang.Procedure), DeclarationType.Procedure },
{ typeof(Lang.Function), DeclarationType.Function },
{ typeof(Lang.Property), DeclarationType.Property },
...
};
static readonly Dictionary<DeclarationType, Type> DeclEnumToType = ...
{
// same as the first dictionary;
// just swap the key and the value
...
};
Sự minh chứng cuối cùng cho những người gợi ý các lớp và hệ thống phân cấp thừa kế ...
Một khi bạn có thể thấy rằng các enum là một xấp xỉ với hệ thống phân cấp thừa kế , lời khuyên sau đây giữ:
- Thiết kế (hoặc cải thiện) hệ thống phân cấp thừa kế của bạn trước,
- Sau đó quay trở lại và thiết kế enum của bạn để ước tính thứ bậc kế thừa đó.
DeclarationType
. Nếu tôi muốn xác định xem có phảix
là một kiểu con hay khôngy
, có lẽ tôi sẽ muốn viết nó như làx.IsSubtypeOf(y)
, chứ không phảix && y == y
.