1

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;
}
mo.
  • 3,474
  • 1
  • 23
  • 20
  • 2
    Solve it how? You didn't state what the actual goal was. – Daniel Mann Aug 21 '12 at 18:41
  • the actual goal was to remove every "ask an object for its internal state"... – mo. Aug 21 '12 at 19:00
  • @mo.: I updated my answer to show you the type of changes that would be made to your interfaces. Note that I changed the `IsValid` property to be a getter and not a setter. The concrete implementations of the interfaces are responsible for defining how the `IsValid` property gets set. – Lucas Aug 21 '12 at 19:13

1 Answers1

3

This line is the source of the issue

var context = new CalculationContext();

You should be injecting the CalculationContext into the CalculationService so you can interrogate it outside of the class. The Calculate method tells the validator to validate the input and output. Asking in this context would be code like this:

public Output Calculate(Input input)
{
   var validator = _context.Validator;
   if (validator.IsInputValid(input)) {
       // ... snip ...
   }
}

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 IsValid getter on the IValidationContext object. This is a little bit of a fuzzy situation, because accessing _context.Validator could 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

interface IValidationContext
{
    void AddMessage(string text); 
    IValidation Validator { get; }
    bool IsValid { get; }
}

interface IValidation
{
    void ValidateInput(Input input); 
    void ValidateOutput(Output output); 
}

interface ICalculator
{
    Output Calculate(Input input);
}


class CalculationService
{
    private readonly ICalulator _calculator;
    private readonly IValidationContext _context;

    public CalculationService(ICalculator calculator, IValidationContext context)
    {
      _calculator = calculator;
      _context = context;
    }

    public Output Calculate(Input input)
    {
       _context.Validator.ValidateInput(input);

       if (_context.IsValid)
       {
          var output = _calculator.Calculate(input);
          _context.Validator.ValidateOutput(output);

          return output;
       }
       return null;
    }
}
Lucas
  • 8,035
  • 2
  • 32
  • 45
  • +1 you got me thinking...i will edit my question – mo. Aug 21 '12 at 18:47
  • Also, I made a small change to make sure that the `output` actually gets returned from the `Calculate` method. – Lucas Aug 21 '12 at 18:49
  • `validator.IsInputValid(input)` is the best way i think, tell the validator to validate and let it return a computational result. – mo. Aug 21 '12 at 19:50