2010-05-28 6 views
3

Comme le titre l'indique, je me demande si c'est une bonne idée que ma classe de validation ait accès à toutes les propriétés de mon modèle. Idéalement, je voudrais faire cela parce que certains champs nécessitent 10+ autres champs pour vérifier si elle est valide ou non. I pourrait mais préférerait ne pas avoir de fonctions avec plus de 10 paramètres. Ou cela rendrait-il le modèle et le validateur trop couplés les uns aux autres? Voici un petit exemple de ce que je veux dire. Ce code ne fonctionne cependant pas car il donne une boucle infinie!Mon validateur doit-il avoir accès à tout mon modèle?

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 

Si je ne me trompe pas, et c'est une bonne idée, comment puis-je une tête et fixer la boucle infinie avec l'obtenir la propriété UserID?

Merci.

Solution

<!-- #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> 

qui produit Solution image http://i46.tinypic.com/zsvk9z.png

Bien que pas parfait, il est bien mieux que ce que j'ai commencé. Fondamentalement, j'ai décidé d'utiliser le motif décorateur. L'étape suivante consiste à supprimer la fonction Init() de chaque validation et à la remplacer par une fonction SetModel() ou quelque chose. De cette façon, chaque validation peut avoir accès à toutes les propriétés de mon modèle.

Merci à tous.

Répondre

1

Je pense que vous avez raison de faire valider le modèle complet par le validateur. Pour briser la boucle infinie, vous pouvez passer la valeur au validateur

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

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

Alternativement, si vous préférez l'encapsulation, vous pouvez ajouter des fonctions pour accéder à la Friend propriété sans validation. Une troisième façon de faire est de séparer votre objet de la validation. La classe de base définit toutes les propriétés sans validation. Ensuite, vous définissez une classe enfant qui ajoute la validation:

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 

Une dernière variante utilise la délégation pour maintenir les propriétés utilisateur de base séparées de celles validées. Nous faisons de l'utilisateur une classe abstraite, puisque nous avons des implémentations - une avec validation, et une sans.

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 

Dans le dernier exemple ValidatedUser ressemble à votre code d'origine, mais la principale différence est que ValidatedUser elle-même n'a pas de valeurs de propriété - elle délègue tous les biens accesseurs à l'objet m_User. Le validateur utilise l'objet m_user qui fournit des propriétés simples sans validation, de sorte que la récursion infinie disparaît.

Actuellement, la validation est effectuée lorsque la propriété est récupérée. J'imagine que cela est fait parce que vous voulez valider les données avant leur utilisation, et pour éviter les erreurs de validation transitoires que les propriétés sont affectées. Outre la validation au niveau de la propriété, vous pouvez également définir une méthode de validation "objet entier" qui vérifie toutes les propriétés de votre objet, en particulier celles impliquées dans les contraintes multi-propriétés. Par exemple, si vous avez la contrainte A + B + C < 50, alors la vérification de AB et C en tant que propriétés séparées conduira à l'évaluation de cette condition (A + B + C < 50), ce qui est inutile et déroutant puisque l'erreur apparaîtra sur une propriété spécifique, quand c'est vraiment un problème avec les 3 propriétés. Votre validateur au niveau de l'objet peut vérifier cette condition une seule fois et signaler une erreur indiquant que les trois propriétés ne sont pas valides. Tout ce qui précède lie la classe Validation à l'utilisateur, de sorte que les clients peuvent utiliser l'utilisateur sans se soucier de la validation. Il y a des avantages et des inconvénients avec cette approche. L'avantage est la transparence - le client peut utiliser des objets Utilisateur et obtenir une validation en coulisse sans le demander explicitement. L'inconvénient est qu'il lie la validation très étroitement avec votre modèle. Une alternative consiste à séparer complètement la validation de l'objet Utilisateur. Ceci non seulement dissocie la validation, mais fournit également une validation de "l'objet entier". Par exemple.

' 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 

Tout code manipulation utilisateur devra alors appeler explicitement Valider après avoir fait des changements, mais cela est généralement pas un problème, et le niveau de contrôle est beaucoup mieux que de l'avoir fait automatiquement. (Dans mon expérience, vous devez presque toujours annuler les actions "automatiques" à un moment donné parce qu'elles gênent.)

J'ai écrit plus que ce que j'avais prévu. J'espère que ceci est utile! PS: Je ne fais pas beaucoup de VB, donc s'il vous plaît soyez indulgent de l'erreur de syntaxe occasionnelle. Je suis un programmeur OO, donc je sais que les principes sont corrects. Et je viens de remarquer la balise "asp-classic" - certains exemples utilisent des fonctionnalités qui ne sont pas forcément disponibles en ASP classique, bien que le code de validateur séparé - le dernier exemple, devrait convenir à ASP classique.

+0

Woah! Ce post est très apprécié, merci. :) Alors que ma solution finale n'est pas 100% ce que vous avez posté. C'est assez proche. – Mike

2

Je définis généralement un validateur qui valide un modèle entier; Dans ce cas, j'aurais une classe UserValidator qui a une méthode qui accepte un utilisateur et retourne un ValidationResult, qui inclut une liste d'erreurs de validation. Cela vous permet de modifier l'implémentation de la classe User sans affecter la validation (par exemple, vous n'avez pas besoin d'ajouter une nouvelle méthode à la classe Validator chaque fois que vous ajoutez une nouvelle propriété ou de modifier une signature de méthode si vous le souhaitez changer la façon dont l'ID utilisateur est validé, etc.)

+0

Pourriez-vous s'il vous plaît poster un exemple? – Mike

Questions connexes