2009-12-18 27 views
9

Tôi muốn có các thiết lập sau:.NET - cách tạo một lớp sao cho chỉ có một lớp cụ thể khác có thể khởi tạo nó?

class Descriptor 
{ 
    public string Name { get; private set; } 
    public IList<Parameter> Parameters { get; private set; } // Set to ReadOnlyCollection 

    private Descrtiptor() { } 
    public Descriptor GetByName(string Name) { // Magic here, caching, loading, parsing, etc. } 
} 

class Parameter 
{ 
    public string Name { get; private set; } 
    public string Valuie { get; private set; } 
} 

Toàn bộ cấu trúc sẽ được đọc-chỉ một lần nạp từ một file XML. Tôi muốn làm như vậy, chỉ có lớp Descriptor mới có thể khởi tạo một tham số. Một cách để làm điều này là tạo một giao diện IParameter và sau đó đặt riêng lớp Parameter trong lớp Mô tả, nhưng trong việc sử dụng thực tế, Tham số sẽ có một số thuộc tính và tôi muốn tránh xác định lại chúng hai lần. .

Điều này có thể bằng cách nào đó không?

Trả lời

18

Biến nó thành lớp lồng nhau riêng để triển khai một giao diện cụ thể. Sau đó, chỉ có các lớp bên ngoài có thể khởi tạo nó, nhưng bất cứ ai cũng có thể tiêu thụ nó (thông qua giao diện). Ví dụ:

interface IParameter 
{ 
    string Name { get; } 
    string Value { get; } 
} 

class Descriptor 
{ 
    public string Name { get; private set; } 
    public IList<IParameter> Parameters { get; private set; } 

    private Descriptor() { } 
    public Descriptor GetByName(string Name) { ... } 

    class Parameter : IParameter 
    { 
     public string Name { get; private set; } 
     public string Value { get; private set; } 
    } 
} 

Nếu bạn thực sự phải tránh giao diện, bạn có thể tạo ra một lớp trừu tượng nào đó có tất cả các thuộc tính nhưng tuyên bố một constructor được bảo vệ. Sau đó bạn có thể tạo một lớp lồng nhau riêng kế thừa từ abstract trừu tượng mà chỉ có thể được tạo bởi lớp bên ngoài và trả về các cá thể của nó như là kiểu cơ sở. Ví dụ:

public abstract AbstractParameter 
{ 
    public string Name { get; protected set; } 
    public string Value { get; protected set; } 
} 

class Descriptor 
{ 
    public string Name { get; private set; } 
    public IList<AbstractParameter> Parameters { get; private set; } 

    private Descriptor() { } 
    public Descriptor GetByName(string Name) { ... } 

    private class NestedParameter : AbstractParameter 
    { 
     public NestedParameter() { /* whatever goes here */ } 
    } 
} 
+0

Điều này vẫn cho phép bạn tạo một lớp con khác của Tham số và khởi tạo qua đó. Làm cho hàm tạo 'nội bộ được bảo vệ', có lẽ. –

+1

@Martinho: bạn không chính xác. Đó là một quan niệm sai lầm phổ biến rằng "bảo vệ nội bộ" ngăn chặn các lớp học từ bên ngoài lắp ráp từ subclassing. Bạn nên nghĩ về nó như được bảo vệ HOẶC nội bộ, không được bảo vệ VÀ nội bộ - tức là lớp có thể được bắt nguồn từ bất kỳ lớp nào khác bên trong hoặc bên ngoài assembly, nhưng chỉ có thể được khởi tạo trong nội bộ. – Joe

1

Bạn có thể sử dụng hàm tạo được đánh dấu Nội bộ.

Bằng cách đó công khai với các lớp học trong hội đồng, và riêng tư cho các lớp học bên ngoài.

4

LBushkin có ý tưởng đúng. Nếu bạn muốn tránh phải nhập lại tất cả các thuộc tính chỉ cần nhấp chuột phải vào tên của lớp và chọn "Refactor"> "Extract Interface", giao diện đó sẽ cung cấp cho bạn giao diện chứa tất cả các thuộc tính đó. (Điều này làm việc trong VS 2008, tôi không biết về các phiên bản trước đó.)

C# thường có cách tiếp cận thay vì tránh mã dự phòng, VS sẽ giúp bạn viết nhanh hơn.

0

Có một cách khác: kiểm tra ngăn xếp cuộc gọi cho loại cuộc gọi.

+3

Kiểm tra thời gian chạy như thế này thường không thể được khắc phục - cả bởi vì chúng đắt tiền (do phản ánh) và mong manh - nếu các lớp dẫn xuất mới được thêm vào cũng nên có khả năng khởi tạo kiểu. – LBushkin

+1

Chúng cũng mong manh vì các lý do khác: phương pháp nêu rõ bất kỳ ai? –

+0

Obfuscators cũng phá vỡ điều này. –

-1

Nếu bạn chỉ muốn lớp Mô tả khởi tạo Tham số, thì bạn có thể đặt lớp Mô tả thành lớp Tham số lồng nhau. (KHÔNG phải là cách khác xung quanh) Điều này phản trực giác khi lớp chứa hoặc lớp cha là lớp lồng nhau.

public class Parameter 
{ 
    private Parameter() { } 
    public string Name { get; private set; } 
    public string Value { get; private set; } 
    public static Parameter.Descriptor GetDescriptorByName(string Name) 
    { 
    return Parameter.Descriptor.GetByName(Name); 
    } 
    public class Descriptor 
    { // Only class with access to private Parameter constructor 
    private Descriptor() { // Initialize Parameters } 
    public IList<Parameter> Parameters { get; private set; } // Set to ReadOnlyCollection 
    public string Name { get; private set; } 
    public static Descriptor GetByName(string Name) { // Magic here, caching, loading, parsing, etc. } 
    } 
} 
0

Đánh dấu các lớp học để được "bảo vệ" từ instantiation (Parameter) với StrongNameIdentityPermission attributeSecurityAction.LinkDemand option:

[StrongNameIdentityPermission(SecurityAction.LinkDemand, PublicKey="...")] 
class Parameter 
{ 
    ... 
} 

Bạn sẽ cần phải cung cấp các khóa công khai thích hợp. Vì bạn đang yêu cầu thời gian liên kết (JIT-time, trên thực tế) kiểm tra trên lớp Parameter, điều này có nghĩa là nó chỉ có thể được sử dụng từ một hội đồng được ký với tên mạnh sử dụng khóa riêng khớp với khóa công khai bạn cung cấp trong hàm tạo thuộc tính ở trên. Tất nhiên, bạn sẽ cần phải đặt các lớp học Descriptor trong một hội đồng riêng biệt và cung cấp cho nó một tên mạnh cho phù hợp.

Tôi đã sử dụng kỹ thuật này trong một vài ứng dụng và nó hoạt động rất tốt.

Hy vọng điều này sẽ hữu ích.

Các vấn đề liên quan