Должен ли мой валидатор иметь доступ ко всей моей модели? - PullRequest
3 голосов
/ 28 мая 2010

Как гласит заголовок, мне интересно, имеет ли смысл, чтобы мой класс проверки имел доступ ко всем свойствам из моей модели. В идеале я хотел бы сделать это, потому что некоторые поля требуют более 10 + других полей, чтобы проверить, является ли он действительным или нет. Я мог бы , но не хотел бы иметь функции с 10+ параметрами. Или это сделало бы модель и валидатор слишком связанными друг с другом? Вот небольшой пример того, что я имею в виду. Этот код, однако, не работает, потому что он дает бесконечный цикл!

Class User
    Private m_UserID
    Private m_Validator

    Public Sub Class_Initialize()
    End Sub

    Public Property Let Validator(value)
        Set m_Validator = value

        m_Validator.Initialize(Me)
    End Property

    Public Property Get Validator()
        Validator = m_Validator
    End Property

    Public Property Let UserID(value)
        m_UserID = value
    End property

    Public Property Get UserID()
        UserID = m_Validator.IsUserIDValid()
    End property End Class

Class Validator
    Private m_User

    Public Sub Class_Initialize()
    End Sub

    Public Sub Initialize(value)
        Set m_User = value
    End Sub

    Public Function IsUserIDValid()
        IsUserIDValid = m_User.UserID > 13
    End Function End Class

Dim mike : Set mike = New User 

mike.UserID = 123456  mike.Validator = New Validator

Response.Write mike.UserID

Если я прав и это хорошая идея, как я могу пойти навстречу и исправить бесконечный цикл с помощью свойства get UserID?

Спасибо.

Решение

<!-- #include file = "../lib/Collection.asp" -->

<style type="text/css">

td { padding: 4px; }

td.error 
{
    background: #F00F00;
}

td.warning 
{
    background: #FC0;
}

</style>

<%

Class UserModel
    Private m_Name
    Private m_Age
    Private m_Height

    Public Property Let Name(value)
        m_Name = value
    End Property

    Public Property Get Name()
        Name = m_Name
    End Property

    Public Property Let Age(value)
        m_Age = value
    End Property

    Public Property Get Age()
        Age = m_Age
    End Property

    Public Property Let Height(value)
        m_Height = value
    End Property

    Public Property Get Height()
        Height = m_Height
    End Property
End Class

Class NameValidation
    Private m_Name

    Public Function Init(name)
        m_Name = name
    End Function

    Public Function Validate()
        Dim validationObject

        If Len(m_Name) < 5 Then
            Set validationObject = New ValidationError
        Else
            Set validationObject = New ValidationSuccess
        End If

        validationObject.CellValue = m_Name

        Set Validate = validationObject
    End Function
End Class

Class AgeValidation
    Private m_Age

    Public Function Init(age)
        m_Age = age
    End Function

    Public Function Validate()
        Dim validationObject

        If m_Age < 18 Then
            Set validationObject = New ValidationError
        ElseIf m_Age = 18 Then
            Set validationObject = New ValidationWarning
        Else
            Set validationObject = New ValidationSuccess
        End If

        validationObject.CellValue = m_Age

        Set Validate = validationObject
    End Function
End Class

Class HeightValidation
    Private m_Height

    Public Function Init(height)
        m_Height = height
    End Function

    Public Function Validate()
        Dim validationObject

        If m_Height > 400 Then
            Set validationObject = New ValidationError
        ElseIf m_Height = 324 Then
            Set validationObject = New ValidationWarning
        Else
            Set validationObject = New ValidationSuccess
        End If

        validationObject.CellValue = m_Height

        Set Validate = validationObject
    End Function
End Class

Class ValidationError
    Private m_CSSClass
    Private m_CellValue

    Public Property Get CSSClass()
        CSSClass = "error"
    End Property

    Public Property Let CellValue(value)
        m_CellValue = value
    End Property

    Public Property Get CellValue()
        CellValue = m_CellValue
    End Property
End Class

Class ValidationWarning
    Private m_CSSClass
    Private m_CellValue

    Public Property Get CSSClass()
        CSSClass = "warning"
    End Property

    Public Property Let CellValue(value)
        m_CellValue = value
    End Property

    Public Property Get CellValue()
        CellValue = m_CellValue
    End Property
End Class

Class ValidationSuccess
    Private m_CSSClass
    Private m_CellValue

    Public Property Get CSSClass()
        CSSClass = ""
    End Property

    Public Property Let CellValue(value)
        m_CellValue = value
    End Property

    Public Property Get CellValue()
        CellValue = m_CellValue
    End Property
End Class

Class ModelValidator
    Public Function ValidateModel(model)
        Dim modelValidation : Set modelValidation = New CollectionClass

        ' Validate name
        Dim name : Set name = New NameValidation
        name.Init model.Name
        modelValidation.Add name

        ' Validate age
        Dim age : Set age = New AgeValidation
        age.Init model.Age
        modelValidation.Add age

        ' Validate height
        Dim height : Set height = New HeightValidation
        height.Init model.Height
        modelValidation.Add height

        Dim validatedProperties : Set validatedProperties = New CollectionClass
        Dim modelVal
        For Each modelVal In modelValidation.Items()
            validatedProperties.Add modelVal.Validate()
        Next

        Set ValidateModel = validatedProperties
    End Function
End Class

Dim modelCollection : Set modelCollection = New CollectionClass

Dim user1 : Set user1 = New UserModel
user1.Name = "Mike"
user1.Age = 12
user1.Height = 32
modelCollection.Add user1

Dim user2 : Set user2 = New UserModel
user2.Name = "Phil"
user2.Age = 18
user2.Height = 432
modelCollection.Add user2

Dim user3 : Set user3 = New UserModel
user3.Name = "Michele"
user3.Age = 32
user3.Height = 324
modelCollection.Add user3

' Validate all models in the collection
Dim modelValue
Dim validatedModels : Set validatedModels = New CollectionClass
For Each modelValue In modelCollection.Items()
    Dim objModelValidator : Set objModelValidator = New ModelValidator
    validatedModels.Add objModelValidator.ValidateModel(modelValue)
Next

%>

<table>
    <tr>
        <td>Name</td>
        <td>Age</td>
        <td>Height</td>
    </tr>
    <%

    Dim r, c
    For Each r In validatedModels.Items()
        %><tr><%
        For Each c In r.Items()
            %><td class="<%= c.CSSClass %>"><%= c.CellValue %></td><%        
        Next
        %></tr><%
    Next

    %>
</table>

Который производит Решение image http://i46.tinypic.com/zsvk9z.png

Хотя и не идеально, но лучше, чем я начал. В основном я решил использовать шаблон декоратора. Мой следующий шаг - скорее всего удалить функцию Init () из каждой проверки и заменить ее на функцию SetModel () или что-то в этом роде. Таким образом, каждая проверка может иметь доступ ко всем свойствам в моей модели.

Спасибо всем.

Ответы [ 2 ]

2 голосов
/ 28 мая 2010

Я обычно определяю валидатор, который проверяет всю модель; В этом случае у меня будет класс UserValidator, который имеет метод, который принимает пользователя и возвращает ValidationResult, который включает в себя список ошибок проверки.

Это позволяет вам изменять реализацию класса User, не влияя на проверку (например, вам не нужно добавлять новый метод в класс Validator каждый раз, когда вы добавляете новое свойство, или изменять сигнатуру метода, если вы хотите изменить способ проверки идентификатора пользователя и т. д.).

1 голос
/ 02 июня 2010

Я думаю, что вы правы, когда валидатор проверяет всю модель. Чтобы разорвать бесконечный цикл, вы можете передать значение в валидатор

Public Property Get UserID()
     UserID = m_Validator.IsUserIDValid(m_userID)
End property 

// in Validator
Public Function IsUserIDValid(userID)
    IsUserIDValid = userID > 13
End Function

В качестве альтернативы, если вы предпочитаете инкапсуляцию, вы можете добавить функции Friend для доступа к свойству без проверки.

Public Property Get UserID()
     UserID = m_Validator.IsUserIDValid()
End property 

Friend Function GetUserID()
   GetUserID = m_userID
End Function

// in Validator
Public Function IsUserIDValid()
    // "private" access - to get the unvalidated property
    IsUserIDValid = m_user.GetUserID > 13
End Function

Третий способ сделать это - отделить ваш объект от проверки. Базовый класс определяет все свойства без проверки. Затем вы определяете дочерний класс, который добавляет проверку:

class User
    Private m_userID
    Public Property Get UserID()
         UserID = m_userID
    End property 
End Class

class ValidatedUser inherits User
   Public Overrides Property Get UserID()
       if (m_userID<15)
           // handle invalid case, e.g. throw exception with property that is invalid
       UserID = m_userID
   End Property

   Public Function Validate()
    ' class-level validation
   End Function
End Class

Последний вариант использует делегирование, чтобы отделить основные пользовательские свойства от проверенных. Мы делаем User абстрактным классом, так как нам нужны реализации - одна с проверкой, а другая без.

Class MustInherit User
   Public MustInherit Property Get UserID()
End Class

' A simple implementation of User that provides the properties
Class DefaultUser Inherits User
   Private m_UserID
   Public Overrides Property Get UserID()
      UserID = m_UserID
   End Property   
End Class

Class ValidatedUser Inherits User
   private Validator m_validator
   private User m_User

   Public Property Let Validator(value)
        Set m_Validator = value
        m_Validator.Initialize(m_User)
        ' note that validator uses m_User - this breaks the infinite recursion
    End Property

   Public Overrides Property Let UserID(value)
      m_User.UserID = value;
   End Property

   Public Overrides Property Get UserID()
      UserID = m_validator.IsUserValid();
   End Property
End Class   

В последнем примере ValidatedUser выглядит аналогично исходному коду, но ключевое отличие состоит в том, что сам ValidatedUser не имеет значений свойств - он делегирует все средства доступа к свойствам объекту m_User. Валидатор использует объект m_user, который предоставляет простые свойства без проверки, поэтому бесконечная рекурсия исчезает.

В настоящее время проверка выполняется при получении свойства. Я полагаю, что это сделано, потому что вы хотите проверить данные перед их использованием и избежать временных ошибок проверки при назначении свойств. В дополнение к проверке на уровне свойств вы можете также определить метод проверки «всего объекта», который проверяет все свойства вашего объекта, особенно те, которые связаны с ограничениями нескольких свойств. Например, если у вас есть ограничение A + B + C <50, то проверка AB и C как отдельных свойств приведет к тому, что это условие (A + B + C <50) будет оцениваться 3 раза, что не является необходимым, а также вводит в заблуждение так как ошибка появится в одном конкретном свойстве, тогда это действительно проблема со всеми 3 свойствами. Ваш валидатор уровня объекта может проверить это условие только один раз и отметить ошибку, которая указывает, что все 3 свойства недействительны. </p>

Все вышеперечисленное связывает Validation с классом User, так что клиенты могут использовать User без заботы о проверке. У этого подхода есть свои преимущества и недостатки. Преимущество - прозрачность - клиент может использовать объекты User и получать подтверждение за кулисами, не запрашивая его явно. Недостатком является то, что он очень тесно связывает валидацию с вашей моделью. Альтернатива - полностью отделить валидацию от объекта User. Это не только разъединяет валидацию, но и обеспечивает валидацию «всего объекта». Э.Г.

' User is now a simple class (like DefaultUser above '
' with just properties, no validation '
Class UserValidator

   Public Function Validate(user)
     ' validate the given user object, return a list of
     ' validation errors, each validation error object
     ' that describes the property or properties
     ' that caused the validation error and why it's an error     
     ' E.g. '
     Dim ve As ValidationError 
     ve = new ValidationError
     ve.obj = user;   ' the object that failed validation
     ve.property = "userID"
     ve.msg = "userId must be < 15"
     ' potentially put several of these in a list and return to caller     
   End
End Class

Любой код, манипулирующий Пользователем, будет вынужден явно вызывать Validate после внесения изменений, но обычно это не проблема, и уровень контроля намного лучше, чем когда он выполняется автоматически. (По моему опыту, вам почти всегда приходится отменять «автоматические» действия в какой-то момент, потому что они мешают.)

Я написал больше, чем хотел. Я надеюсь, что это полезно!

PS: я не делаю много VB, поэтому будьте снисходительны к случайной синтаксической ошибке. Я программист ОО, поэтому я знаю, что принципы верны. И я только что заметил тег «asp-classic» - в некоторых примерах используются функции, которые могут быть недоступны в классическом asp, хотя отдельный код Validator - последний пример должен подойти для классического asp.

...