Does such a design pattern exist? (Multi-Strategy/Multi-Traits)
https://softwareengineering.stackexchange.com/questions/308928
-
11-12-2020 - |
문제
Is there a design pattern that would allow a class from a hierarchy to 'subscribe to' concrete methods?
For example, say you have an abstract base class that requires the implementation of a method.
Public MustInherit Class Rule
' Common properties shared between sub classes
Public MustOverride Function Validate() As Boolean
End Class
But lets say that the sub classes only want to register to a set of concrete methods for validating themselves.
Lets say a subclass of Rule - RuleItemA Requires the following:
- The entity must exist
- The entity must be a number
- The number must be a prime
Where as subclass of Rule - RuleItemB Requires the following:
- The entity must exist
- The entity must be a string
- The string must be a valid URL
For sake of argument, lets say there are about 20 different concrete validations in place. I do not want to write all possible permutations of the 20 validations. What I would rather do is the following:
Class RuleItemA Inherits Rule, SubscribesTo(EntityExists,EntityIsNumber,NumberIsPrime)
Public Overrides Function Validate() As Boolean
' Invoke all concrete validations here
End Function
End Class
I feel like this is similar to traits, or a multi-strategy pattern, but I'm not sure how to implement it.
Is this a known pattern? Is there a way to implement it in .Net?
해결책
I do not know the pattern name (but it's probably using the Reflection pattern, see below), but many Web frameworks natively implement what your are trying to do here.
For example if you look at Ruby on Rails, ActiveRecord and Mongoid are the components used to represent a Database entity, and programmers usually implement their model like this :
class User
include Mongoid::Document # Mongoid is an ORM for MongoDB/noSQL database
# The attributes of your model
field :name
field :email
field :telephone
# Validation rules that guarantee consistency of your instances
validates_presence_of(:name, :email)
validates_uniqueness_of(:email)
validates :telephone { maximum: 10}
...
end
Then what happens is that everytime you try to save your resource to the database, all the validations will run before and prevent you from saving wrong models.
I would say the best way to go is keep a List of all the Validations that you must run on your class. You could also delegate these validations to a Validator class but here's how I would do it (Java style):
abstract class ValidatableItem
private List<Validation> toValidate;
protected validate(Field a, ValidationType type)
toValidate.push(type.getValidationFor(this, a))
end
private boolean runValidations(){
for(Validation v : toValidate){
if(v.isNotValid())
return false;
}
return true;
}
private Field field(String s){
return this.getclass.getDeclaredField("s")
}
}
class ItemA extends ValidatableItem
Attribute attrA;
Attribute attrB
void validations(){
validate(field("attrA"), ValidationType.EXISTS)
validate(field("attrB"), ValidationType.NON_NEGATIVE)
}
And you have to define your Validation / Validation types yourself.
EDIT
Unfortunately I don't see another way but to use Reflection to do that. This can quickly turn into an antipattern. I haven't used reflection a lot in Java, but here's how I would go for it.
class WebPage extends ValidatableItem{
String url;
String name;
int vistiCount;
Date lastVisit;
void validations(){
validate(field("visitCount"), ValidationType.EXISTS)
validate(field("url"), ValidationType.VALID_URL)
}
}
public abstract class Validation{
private Class<? extends ValidatableItem> instanceToValidate;
private Field fieldToValidate;
public Validation(Class<? extends ValidatableItem> c, Field f){
this.instanceToValidate = c;
this.fieldToValidate = f;
}
public abstract boolean isNotValid();
}
class ExistValidation extends Validation{
// Redefine same constructor as base class, use super()
public isNotValid(){
return(f.get(c) == null);
}
}
class ValidUrlValidation extends Validation{
// Redefine same constructor as base class, use super()
public isNotValid(){
return(urlRegexMatches(f.get(c));
}
}
Enum ValidationType{
EXISTS(ExistValidation.class)
VALID_URL(ValidUrlValidation.class)
private final Class<? implements Validation> vclass;
private ValidationTypes(Class<? implements Validation> c){
this.vclass = c
}
public getValidation(Class<? extends ValidatableItem> cl, Field f)
// Assume all Validation constructors look the same
return cl.getConstructor(ValidatableItem.class, Field.class).newInstance(cl, f)
}
Note : I don't really understand the context why you need to "validate" your inputs, but unless you need to do very specific things, you can most likely find libraries that already do what you want. Remember the 7 deadly sins of developers.
다른 팁
This sounds like combination of strategy and composite patterns.
Individual validators are strategy pattern. Then, composite is used to invoke multiple strategies as single one.