I'm trying to build a rule-checker for my project that cycle through a list of rules and return me if any of those have the flag true. Below a code i wrote as sample of what i'm trying to do (not the actual code, please ignore eventually not observed best practices or niceties).
using System.Collections;
using System.Collections.Generic;
using System.Linq;
public interface ICounterValued {
int CounterValue {get;}
int CounterLimit {get;}
}
public interface ITimeValued {
float ElapsedTime {get;}
float TimeLimit {get;}
}
public class MyMainClass : ICounterValued , ITimeValued {
// Counter data
private int _counterValue = 0;
private int _counterLimit = 10;
// Time data
private float _elapsedTime = 0;
private float _timeLimit = 10f;
// ICounterValued
public int CounterValue => _counterValue;
public int CounterLimit => _counterLimit;
// ITimeValued
public float ElapsedTime => _elapsedTime;
public float TimeLimit => _timeLimit;
public RuleChecker ruleChecker;
public void InitializeMe(){
ruleChecker = new RuleChecker();
ruleChecker.rules.Add( new TimeRule() );
ruleChecker.rules.Add( new CounterRule() );
}
public void UpdateRuleChecker(){
ruleChecker.CheckRules<MyMainClass>( this );
}
public bool NeedToStop(){
return ruleChecker.SomeRuleTriggered();
}
}
public class RuleChecker {
public List<Rule> rules = new List<Rule>();
public void CheckRules<T>( T data ){
rules.ForEach( x => x.UpdateMe<T>(data) );
}
public bool SomeRuleTriggered(){
return rules.Any( x => x.IsTriggered() );
}
}
public abstract class Rule {
protected bool _triggered;
public virtual bool IsTriggered() => _triggered;
// check logic that will set the _triggered flag to true
public abstract void UpdateMe<T>( T data );
}
public class TimeRule : Rule {
public override void UpdateMe<ITimeValued>( ITimeValued data ){
_triggered = (data.ElapsedTime >= data.TimeLimit);
}
}
public class CounterRule : Rule {
public override void UpdateMe<ICounterValued>( ICounterValued data ){
_triggered = (data.CounterValue >= data.CounterLimit);
}
}
The point of that is to use the same rule-checker in future for different "MainClasses" that have one or more interfaces and the possibility to reuse that rules or add new ones like "modules" with his own triggering logic.
The code above doesn't work because when i call the generic voids (UpdateMe<ICounterValued>
and UpdateMe<ITimeValued>
) data value cannot access the attributes i defined inside the interfaces (CounterValue
,CounterLimit
,ElapsedTime
,TimeLimit
).
I'm assuming that there are some conversion or interface problem to this void generics approach; Is not necessary for me to use generics but i didn't find a more clever solution to this; I've tryied something with delegates as parameter of UpdateMe
in Rule
class but nothing that can be extended with different type parameter and let the rule-checker call all in one by passing the instance of MyMainClass
for each one.
Thanks in andavace for any suggestions.
CodePudding user response:
Structuring (your approach) this way makes more sense to me:
public class RuledClass{
public List<Rule> Rules = new List<Rule>();
public bool AnyViolated(){
return Rules.Any(r => r.IsViolated());
}
}
public class MyMainClass : RuledClass, ICounterValued , ITimeValued {
// Counter data
public int CounterValue {get;set;}
public int CounterLimit {get;set;}
// Time data
public float ElapsedTime {get;set;}
public float TimeLimit {get;set;}
// ICounterValued
public MyMainClass(){
Rules.Add( new TimeRule(this) );
Rules.Add( new CounterRule(this) );
}
}
public abstract class Rule {
public abstract bool IsViolated();
}
public class TimeRule : Rule {
private ITimeValued _thing;
public TimeRule(ITimeValued thing){
_thing = thing;
}
public override bool IsViolated(){
return (_thing.ElapsedTime >= _thing.TimeLimit);
}
}
public class CounterRule : Rule {
private ICounterValued _thing;
public CounterRule(ICounterValued thing){
_thing = thing;
}
public override bool IsViolated(){
return (_thing.CounterValue >= _thing.CounterLimit);
}
}
I couldn't work out, with your approach, why class X would have a list of rules that one could potentially pass a class Y to; the rules and the class they apply to are tied together in my mind.. as such I make that link at construction time
..But even then I don't know if I'd do it this way
I think I'd more likely do something like:
public class RuledClass{
public List<Func<bool>> Rules = new List<Func<bool>>();
public bool AnyViolated(){
return Rules.Any(r => r());
}
}
public class MyMainClass : RuledClass {
// Counter data
public int CounterValue {get;set;}
public int CounterLimit {get;set;}
// Time data
public float ElapsedTime {get;set;}
public float TimeLimit {get;set;}
// ICounterValued
public MyMainClass(){
Rules.Add(()=>ElapsedTime>TimeLimit);
Rules.Add(()=>CounterValue>CounterLimit);
}
}
CodePudding user response:
I've found a solution that seems to work and is close to what I was searcing for. The first solution of @Caius Jard worked too but I think that it would be better to avoid keep pointing the checking instance inside a variable of a rule. Below the same sample code in my question but with comments next to the updated code:
using System.Collections;
using System.Collections.Generic;
using System.Linq;
public interface ICounterValued {
int CounterValue {get;}
int CounterLimit {get;}
}
public interface ITimeValued {
float ElapsedTime {get;}
float TimeLimit {get;}
}
public class MyMainClass : ICounterValued , ITimeValued {
// Counter data
private int _counterValue = 0;
private int _counterLimit = 10;
// Time data
private float _elapsedTime = 0;
private float _timeLimit = 10f;
// ICounterValued
public int CounterValue => _counterValue;
public int CounterLimit => _counterLimit;
// ITimeValued
public float ElapsedTime => _elapsedTime;
public float TimeLimit => _timeLimit;
public RuleChecker ruleChecker;
public void InitializeMe(){
ruleChecker = new RuleChecker();
ruleChecker.rules.Add( new TimeRule() );
ruleChecker.rules.Add( new CounterRule() );
}
// I don't need this anymore, the condition will be checked
// inside SomeRuleTriggered
// public void UpdateRuleChecker(){
// ruleChecker.CheckRules<MyMainClass>( this );
// }
public bool NeedToStop(){
// The instance will be passed as argument
// return ruleChecker.SomeRuleTriggered();
return ruleChecker.SomeRuleTriggered( this );
}
}
public class RuleChecker {
public List<Rule> rules = new List<Rule>();
// As commented above, I don't need to update a stored
// flag in Rule (and direved) anymore
// public void CheckRules<T>( T data ){
// rules.ForEach( x => x.UpdateMe<T>(data) );
// }
// Now the instance will be passed as a generic object and parsed
// inside IsTriggered method
// public bool SomeRuleTriggered(){
// return rules.Any( x => x.IsTriggered() );
// }
public bool SomeRuleTriggered( object target ){
return rules.Any( x => x.IsTriggered( target ) );
}
}
public abstract class Rule {
// No need to update a falg anymore
// protected bool _triggered;
// No need to update a falg anymore
// public virtual bool IsTriggered() => _triggered;
// No need to update a falg anymore
// public abstract void UpdateMe<T>( T data );
public abstract bool IsTriggered( object target );
}
public class TimeRule : Rule {
// No need to update a falg anymore
// public override void UpdateMe<ITimeValued>( ITimeValued data ){
// _triggered = (data.ElapsedTime >= data.TimeLimit);
// }
public override bool IsTriggered( object target ){
ITimeValued target_timed = target as ITimeValued;
if( null == target_timed)
return false;
return (target_timed.ElapsedTime >= target_timed.TimeLimit);
}
}
public class CounterRule : Rule {
// No need to update a falg anymore
// public override void UpdateMe<ICounterValued>( ICounterValued data ){
// _triggered = (data.CounterValue >= data.CounterLimit);
// }
public override bool IsTriggered( object target ){
ICounterValued target_counter = target as ICounterValued;
if( null == target_counter)
return false;
return (target_counter.CounterValue >= target_counter.CounterLimit);
}
}
As you can see the RuleChecker
can call the method on any Rule
derived class passing the checking instance as a generic object
. Any class that extends Rule
can implement his rule-logic overriding IsTriggered
with an explicit cast inside, and then finally start checking.