today i was thinking about “tell! don’t ask!” and experimenting with this code.
interfaces:
interface IValidationContext
{
void AddMessage(string text);
bool IsValid { set; }
}
interface IValidation
{
void ValidateInput(Input input, IValidationContext context);
void ValidateOutput(Output output, IValidationContext context);
}
interface ICalculator
{
Output Calculate(Input input);
}
implementations:
class CalculationService
{
private readonly ICalulator _calculator;
private readonly IValidation _validation;
public CalculationService(ICalculator calculator, IValidation validation)
{
_calculator = calculator;
_validation = validation;
}
public Output Calculate(Input input)
{
var context = new CalculationContext();
_validation.ValidateInput(input, context);
if (context.IsValid)
{
var output = _calculator.Calculate(input);
_validation.ValidateOutput(output, context);
return output
}
return null;
}
}
class CalculationContext : IValidationContext
{
public CalculationContext()
{
Messages = new List<string>();
}
public IList<string> Messages { get; private set; }
public void AddMessage(string text)
{
Messages.Add(text);
}
public bool IsValid { set; get; }
}
i know it is not always possible conform a design-principle.
but in the end i stuck with this code where i’m asking an object:
if (context.IsValid)
{
var output = _calculator.Calculate(input);
_validation.ValidateOutput(output, context);
}
is it possible solve it whether it is practical or not?
edit 1:
if i modify my IValidationContext and rename it:
interface ICalculationContext
{
void AddMessage(string text);
Output Calculate(ICalculator calculator, Input input);
bool IsValid { set; }
}
the context don’t need to be askd:
public Output Calculate(Input input)
{
_validation.ValidateInput(input, context);
var output = context.Calculate(_calculator, input);
_validation.ValidateOutput(output, context);
return output;
}
now the context is responsible to call calculation based on its internal state.
…it dont feel right…
edit 2:
i read a small article about “tell! don’t ask!” and it states that:
asking an object for its internal state and then tell that object something depending on that state, would violate “tell! don’t ask!” but it’s ok to tell another object something.
does this apply here?
btw. introducing an boolean-isvalid-result for ValidateInput and ValidateOutput.
could change the code to this, which is nice and nobody gets “asked” something:
public Output Calculate(Input input)
{
var isValid = _validation.ValidateInput(input, context);
if (isValid)
{
var output = _calculator.Calculate(input);
_validation.ValidateOutput(output, context);
return output
}
return null;
}
This line is the source of the issue
You should be injecting the
CalculationContextinto theCalculationServiceso you can interrogate it outside of the class. TheCalculatemethod tells the validator to validate the input and output. Asking in this context would be code like this:Here we’re asking the validator whether a particular input is valid, rather than telling it to validate something. Also, we are limiting ourselves to working with the
IsValidgetter on theIValidationContextobject. This is a little bit of a fuzzy situation, because accessing_context.Validatorcould be seen as violating the Law of Demeter, but this property is defined in an interface and only returns an interface so we are not coupled to any concrete implementation of these classes.Here’s a suggestion, assuming the following modifications to the interfaces