Sử dụng struct để thực thi xác nhận loại tích hợp


9

Các đối tượng miền thông thường có các thuộc tính có thể được biểu thị bằng một loại dựng sẵn nhưng có các giá trị hợp lệ là tập hợp con của các giá trị có thể được biểu thị bằng loại đó.

Trong các trường hợp này, giá trị có thể được lưu trữ bằng cách sử dụng loại tích hợp nhưng cần phải đảm bảo các giá trị luôn được xác thực tại điểm nhập, nếu không chúng tôi có thể sẽ làm việc với giá trị không hợp lệ.

Một cách để giải quyết điều này là lưu trữ giá trị dưới dạng một tùy chỉnh structcó một private readonlytrường sao lưu duy nhất của loại tích hợp và hàm tạo của nó xác nhận giá trị được cung cấp. Sau đó chúng ta có thể luôn chắc chắn chỉ sử dụng các giá trị được xác thực bằng cách sử dụng structloại này .

Chúng tôi cũng có thể cung cấp các toán tử truyền từ và đến loại tích hợp bên dưới để các giá trị có thể nhập và thoát liền mạch làm loại bên dưới.

Lấy ví dụ về một tình huống trong đó chúng ta cần thể hiện tên của một đối tượng miền và các giá trị hợp lệ là bất kỳ chuỗi nào có độ dài từ 1 đến 255 ký tự. Chúng ta có thể biểu diễn điều này bằng cách sử dụng cấu trúc sau:

public struct ValidatedName : IEquatable<ValidatedName>
{
    private readonly string _value;

    private ValidatedName(string name)
    {
        _value = name;
    }

    public static bool IsValid(string name)
    {
        return !String.IsNullOrEmpty(name) && name.Length <= 255;
    }

    public bool Equals(ValidatedName other)
    {
        return _value == other._value;
    }

    public override bool Equals(object obj)
    {
        if (obj is ValidatedName)
        {
            return Equals((ValidatedName)obj);
        }
        return false;
    }

    public static implicit operator string(ValidatedName x)
    {
        return x.ToString();
    }

    public static explicit operator ValidatedName(string x)
    {
        if (IsValid(x))
        {
            return new ValidatedName(x);
        }
        throw new InvalidCastException();
    }

    public static bool operator ==(ValidatedName x, ValidatedName y)
    {
        return x.Equals(y);
    }

    public static bool operator !=(ValidatedName x, ValidatedName y)
    {
        return !x.Equals(y);
    }

    public override int GetHashCode()
    {
        return _value.GetHashCode();
    }

    public override string ToString()
    {
        return _value;
    }
}

Ví dụ chương trình các to- stringdàn diễn viên như implicitthế này không bao giờ có thể thất bại nhưng từ- stringcast như explicitvì điều này sẽ ném cho các giá trị không hợp lệ, nhưng tất nhiên những cả hai có thể là một trong hai implicithoặc explicit.

Cũng lưu ý rằng người ta chỉ có thể khởi tạo cấu trúc này bằng cách truyền từ string, nhưng người ta có thể kiểm tra xem một diễn viên như vậy sẽ thất bại trước khi sử dụng IsValid staticphương thức này.

Đây dường như là một mô hình tốt để thực thi xác thực các giá trị miền có thể được biểu thị bằng các loại đơn giản, nhưng tôi không thấy nó được sử dụng thường xuyên hoặc được đề xuất và tôi quan tâm đến lý do tại sao.

Vì vậy, câu hỏi của tôi là: bạn thấy ưu điểm và nhược điểm của việc sử dụng mẫu này là gì và tại sao?

Nếu bạn cảm thấy rằng đây là một mô hình xấu, tôi muốn hiểu tại sao và những gì bạn cảm thấy là sự thay thế tốt nhất.

NB Ban đầu tôi đã hỏi câu hỏi này trên Stack Overflow nhưng nó được đặt dưới dạng chủ yếu dựa trên ý kiến ​​(chủ quan trớ trêu thay) - hy vọng nó có thể tận hưởng thành công hơn ở đây.

Trên đây là văn bản gốc, bên dưới một vài suy nghĩ, một phần để đáp lại câu trả lời nhận được ở đó trước khi nó bị giữ lại:

  • Một trong những điểm chính được đưa ra bởi các câu trả lời là xung quanh số lượng mã tấm nồi hơi cần thiết cho mẫu trên, đặc biệt là khi nhiều loại như vậy được yêu cầu. Tuy nhiên, để bảo vệ mẫu, điều này có thể được tự động hóa phần lớn bằng cách sử dụng các mẫu và thực sự với tôi nó dường như không quá tệ, nhưng đó chỉ là ý kiến ​​của tôi.
  • Từ quan điểm khái niệm, có vẻ không lạ khi làm việc với một ngôn ngữ được gõ mạnh như C # để chỉ áp dụng nguyên tắc gõ mạnh cho các giá trị tổng hợp, thay vì mở rộng nó thành các giá trị có thể được biểu thị bằng một thể hiện của loại tích hợp?

bạn có thể tạo một phiên bản templated có một bool (T) lambda
ratchet freak

Câu trả lời:


4

Điều này khá phổ biến trong các ngôn ngữ kiểu ML như Standard ML / OCaml / F # / Haskell, nơi dễ dàng hơn nhiều để tạo các loại trình bao bọc. Nó cung cấp cho bạn hai lợi ích:

  • Nó cho phép một đoạn mã thực thi rằng một chuỗi đã trải qua xác nhận, mà không cần phải quan tâm đến việc xác thực đó.
  • Nó cho phép bạn bản địa hóa mã xác nhận ở một nơi. Nếu ValidatedNamebao giờ chứa giá trị không hợp lệ, bạn sẽ biết lỗi nằm trong IsValidphương thức.

Nếu bạn hiểu IsValidđúng phương pháp, bạn có một đảm bảo rằng bất kỳ hàm nào nhận ValidatedNameđược trên thực tế đều nhận được một tên hợp lệ.

Nếu bạn cần thực hiện các thao tác chuỗi, bạn có thể thêm một phương thức công khai chấp nhận hàm lấy Chuỗi (giá trị của ValidatedName) và trả về Chuỗi (giá trị mới) và xác thực kết quả của việc áp dụng hàm. Điều đó giúp loại bỏ bản tóm tắt của việc lấy giá trị Chuỗi bên dưới và bọc lại nó.

Một sử dụng liên quan để bọc các giá trị là để theo dõi xuất xứ của chúng. Ví dụ: API hệ điều hành dựa trên C đôi khi cung cấp xử lý cho các tài nguyên dưới dạng số nguyên. Thay vào đó, bạn có thể bọc các API hệ điều hành để sử dụng Handlecấu trúc và chỉ cung cấp quyền truy cập vào hàm tạo cho phần mã đó. Nếu mã tạo ra Handles là chính xác, thì chỉ có các thẻ điều khiển hợp lệ mới được sử dụng.


1

Bạn thấy ưu điểm và nhược điểm của việc sử dụng mẫu này là gì và tại sao?

tốt :

  • Nó là khép kín. Quá nhiều bit xác nhận có các đường gân đến các vị trí khác nhau.
  • Nó giúp tự làm tài liệu. Việc nhìn thấy một phương thức sẽ ValidatedStringlàm cho nó rõ ràng hơn nhiều về ngữ nghĩa của cuộc gọi.
  • Nó giúp giới hạn xác nhận ở một điểm thay vì cần phải sao chép trên các phương thức công khai.

xấu :

  • Các thủ thuật đúc được ẩn. Nó không phải là thành ngữ C #, vì vậy có thể gây nhầm lẫn khi đọc mã.
  • Nó ném. Có các chuỗi không đáp ứng xác nhận không phải là một kịch bản đặc biệt. Làm IsValidtrước khi diễn viên có chút bất cẩn.
  • Nó không thể cho bạn biết lý do tại sao một cái gì đó không hợp lệ.
  • Mặc định ValidatedStringlà không hợp lệ / xác nhận.

Tôi đã thấy loại điều này thường xuyên hơn với UserAuthenticatedUsersắp xếp mọi thứ, nơi đối tượng thực sự thay đổi. Nó có thể là một cách tiếp cận tốt, mặc dù có vẻ không phù hợp trong C #.


1
Cảm ơn, tôi nghĩ rằng "con" thứ tư của bạn là đối số hấp dẫn nhất đối với nó - sử dụng mặc định hoặc một mảng loại có thể cung cấp cho bạn các giá trị không hợp lệ (tất nhiên tùy thuộc vào việc chuỗi zero / null có phải là giá trị hợp lệ hay không). Đây là (tôi nghĩ) hai cách duy nhất để kết thúc với một giá trị không hợp lệ. Nhưng sau đó, nếu chúng ta KHÔNG sử dụng mẫu này, hai điều này vẫn sẽ cung cấp cho chúng ta các giá trị không hợp lệ, nhưng tôi cho rằng ít nhất chúng ta sẽ biết rằng chúng cần được xác nhận. Vì vậy, điều này có khả năng làm mất hiệu lực cách tiếp cận trong đó giá trị mặc định của loại cơ bản không hợp lệ đối với loại của chúng tôi.
gmoody1979

Tất cả các nhược điểm là vấn đề thực hiện chứ không phải là vấn đề với khái niệm này. Ngoài ra, tôi thấy "ngoại lệ nên là ngoại lệ" một khái niệm mờ và không rõ ràng. Cách tiếp cận thực tế nhất là cung cấp cả phương pháp dựa trên ngoại lệ và không ngoại lệ và để người gọi chọn.
Doval

@Doval Tôi đồng ý trừ khi được ghi chú trong bình luận khác của tôi. Toàn bộ quan điểm của mẫu là phải biết chắc chắn rằng nếu chúng ta có một Tên hợp lệ, thì nó phải hợp lệ. Điều này bị phá vỡ nếu giá trị mặc định của loại cơ bản không phải là giá trị hợp lệ của loại tên miền. Tất nhiên điều này phụ thuộc vào miền nhưng nhiều khả năng là trường hợp (tôi đã nghĩ) đối với các loại dựa trên chuỗi hơn là các loại số. Mẫu hoạt động tốt nhất trong đó mặc định của loại cơ bản cũng phù hợp làm mặc định của loại tên miền.
gmoody1979

@Doval - Tôi thường đồng ý. Bản thân khái niệm này là ổn, nhưng nó thực sự cố gắng biến các loại sàng lọc thành một ngôn ngữ không hỗ trợ chúng. Luôn luôn có vấn đề thực hiện.
Telastyn

Như đã nói, tôi cho rằng bạn có thể kiểm tra giá trị mặc định trên cast "outward" và ở bất kỳ vị trí cần thiết nào khác trong các phương thức của struct và throw nếu không được khởi tạo, nhưng điều đó bắt đầu trở nên lộn xộn.
gmoody1979

0

Cách của bạn khá nặng nề và chuyên sâu. Tôi thường định nghĩa các thực thể miền như:

public class Institution
{
    private Institution() { }

    public Institution(int organizationId, string name)
    {
        OrganizationId = organizationId;            
        Name = name;
        ReplicationKey = Guid.NewGuid();

        new InstitutionValidator().ValidateAndThrow(this);
    }

    public int Id { get; private set; }
    public string Name { get; private set; }        
    public virtual ICollection<Department> Departments { get; private set; }

    ... other properties    

    public Department AddDepartment(string name)
    {
        var department = new Department(Id, name);
        if (Departments == null) Departments = new List<Department>();
        Departments.Add(department);            
        return department;
    }

    ... other domain operations
}

Trong hàm tạo của thực thể, xác thực được kích hoạt bằng FluentValidation.NET, để đảm bảo bạn không thể tạo một thực thể có trạng thái không hợp lệ. Lưu ý rằng các thuộc tính đều chỉ đọc - bạn chỉ có thể đặt chúng thông qua hàm tạo hoặc các hoạt động miền chuyên dụng.

Xác thực thực thể này là một lớp riêng biệt:

public class InstitutionValidator : AbstractValidator<Institution>
{
    public InstitutionValidator()
    {
        RuleFor(institution => institution.Name).NotNull().Length(1, 100).WithLocalizedName(() =>   Prim.Mgp.Infrastructure.Resources.GlobalResources.InstitutionName);       
        RuleFor(institution => institution.OrganizationId).GreaterThan(0);
        RuleFor(institution => institution.ReplicationKey).NotNull().NotEqual(Guid.Empty);
    }  
}

Các trình xác nhận này cũng có thể dễ dàng được sử dụng lại và bạn viết mã soạn sẵn ít hơn. Và một lợi thế khác là nó có thể đọc được.


Downvoter có quan tâm để giải thích lý do tại sao câu trả lời của tôi bị hạ cấp không?
L-Four

Câu hỏi là về một cấu trúc để hạn chế các loại giá trị và bạn đã chuyển sang một lớp mà không giải thích TẠI SAO. (Không phải là một downvoter, chỉ đưa ra một gợi ý.)
DougM

Tôi đã giải thích lý do tại sao tôi thấy đây là một lựa chọn tốt hơn và đây là một trong những câu hỏi của anh ấy. Cảm ơn vi đa trả lơi.
L-Four

0

Tôi thích cách tiếp cận này với các loại giá trị. Khái niệm này là tuyệt vời, nhưng tôi có một số gợi ý / phàn nàn về việc thực hiện.

Đúc : Tôi không thích sử dụng đúc trong trường hợp này. Việc truyền từ chuỗi rõ ràng không phải là một vấn đề, nhưng không có nhiều khác biệt giữa (ValidatedName)nameValuemới và mới ValidatedName(nameValue). Vì vậy, nó có vẻ như không cần thiết. Các diễn viên ẩn chuỗi là vấn đề tồi tệ nhất. Tôi nghĩ rằng việc nhận giá trị chuỗi thực tế sẽ rõ ràng hơn, bởi vì nó có thể vô tình được gán cho chuỗi và trình biên dịch sẽ không cảnh báo bạn về khả năng "mất độ chính xác". Đây là loại mất chính xác nên được rõ ràng.

ToString : Tôi thích sử dụng ToStringquá tải chỉ cho mục đích gỡ lỗi. Và tôi không nghĩ trả lại giá trị thô cho nó là một ý tưởng tốt. Đây là vấn đề tương tự như với chuyển đổi thành chuỗi ẩn. Lấy giá trị nội bộ nên được hoạt động rõ ràng. Tôi tin rằng bạn đang cố gắng làm cho cấu trúc hoạt động như một chuỗi bình thường đối với mã bên ngoài, nhưng tôi nghĩ khi làm như vậy, bạn đang mất một số giá trị bạn nhận được khi triển khai loại này.

EqualsGetHashCode : Các cấu trúc đang sử dụng đẳng thức cấu trúc theo mặc định. Vì vậy, bạn EqualsGetHashCodeđang nhân đôi hành vi mặc định này. Bạn có thể loại bỏ chúng và nó sẽ là điều tương tự.


Đúc: Về mặt ngữ nghĩa, điều này đối với tôi giống như chuyển đổi một chuỗi thành một Tên hợp lệ hơn là việc tạo một Tên hợp lệ mới: chúng tôi xác định một chuỗi hiện có là một Tên hợp lệ. Vì vậy, với tôi các diễn viên có vẻ đúng hơn về mặt ngữ nghĩa. Đồng ý có rất ít sự khác biệt trong cách gõ (của các ngón tay trên bàn phím đa dạng). Tôi không đồng ý với việc truyền vào chuỗi: ValidatedName là một tập hợp con của chuỗi, vì vậy không bao giờ có thể mất độ chính xác ...
gmoody1979

ToString: Tôi không đồng ý. Đối với tôi ToString là một phương pháp hoàn toàn hợp lệ để sử dụng bên ngoài các kịch bản gỡ lỗi, giả sử nó phù hợp với yêu cầu. Ngoài ra trong tình huống này khi một loại là một tập hợp con của một loại khác, tôi nghĩ sẽ hợp lý khi biến khả năng chuyển từ tập hợp con thành siêu tập hợp dễ dàng nhất có thể, để nếu người dùng mong muốn, họ gần như có thể coi nó là thuộc loại siêu tập hợp, tức là chuỗi ...
gmoody1979

Equals và GetHashCode: Có các cấu trúc sử dụng đẳng thức cấu trúc, nhưng trong trường hợp này là so sánh tham chiếu chuỗi, không phải giá trị của chuỗi. Do đó chúng ta cần ghi đè bằng. Tôi đồng ý rằng điều này sẽ không cần thiết nếu loại cơ bản là loại giá trị. Theo hiểu biết của tôi về việc triển khai GetHashCode mặc định cho các loại giá trị (khá hạn chế), điều này sẽ cho cùng một giá trị nhưng sẽ hiệu quả hơn. Tôi thực sự nên kiểm tra xem đó có phải là trường hợp không nhưng đó là một vấn đề phụ đối với điểm chính của câu hỏi. Cảm ơn bạn đã trả lời bằng cách này :-).
gmoody1979

@ gmoody1979 Cấu trúc được so sánh bằng cách sử dụng Bằng trên mọi trường theo mặc định. Không nên là một vấn đề với chuỗi. Tương tự với GetHashCode. Đối với cấu trúc là tập hợp con của chuỗi. Tôi thích nghĩ về các loại như mạng lưới an toàn. Tôi không muốn làm việc với ValidatedName và sau đó vô tình trượt để sử dụng chuỗi. Tôi thích hơn nếu trình biên dịch làm cho tôi xác định rõ ràng rằng bây giờ tôi muốn làm việc với dữ liệu không được kiểm tra.
Euphoric

Xin lỗi, điểm tốt trên Equals. Mặc dù việc ghi đè sẽ thực hiện tốt hơn do hành vi mặc định cần sử dụng sự phản chiếu để thực hiện so sánh. Đúc: có thể là một lý lẽ tốt để làm cho nó một diễn viên rõ ràng.
gmoody1979 7/1/2015
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.