Question

I've got a case where "Tell, don't Ask" seems to conflict with the "Single responsibility" principle. I've looked at other discussions on the subject but not yet been able to work out the most appropriate object oriented approach for this situation.

I have a program which reads and manipulates collections of data from various sources. I have created a class to hold and manipulate the data (a "DataSet" class). It includes methods for performing various operations on the datasets, such as comparing two datasets to generate a new one which contains the differences, and writing datasets to a file.

I now want to perform some analysis on a dataset and output the results to a report. My first attempt at coding this interrogates the dataset to extract information from it and then constructs the report but this seems to go against the "Tell, don't ask" principle. So: should I put the analysis methods inside the DataSet class and tell the dataset to analyse itself and generate a report? Does this break the Single Responsibility principle? What if I want to perform other types of analysis in future - by DataSet class could become very bloated with lots of different analysis routines which are nothing to do with its core purpose.

Can anyone suggest the best approach here? Is there a particular design pattern which addresses this issue?

Was it helpful?

Solution

Whenever you design software, you always have to balance different principles, because many of them conflict. For instance, the DRY (Don't Repeat Yourself) Principle often conflicts with Single Responsibility Principle, particularly when two things do similar, but not exactly the same thing.

Often times you have to decide which principle is more important and emphasis that principle over another one (although you should try to adhere to as many as possible). Often times, principles work together, sometimes they work against each other.

In this case, Tell Don't Ask works with other principles, such as the Law of Demeter (which despite it's name is still a principle as far as software goes and is better described as the principle of least knowledge).

What the LoD tells us is that a method of an object should only call other methods

  • On Itself
  • On objects passed into it as a parameter
  • Any objects created/instantiated with an object passed by parameter
  • the objects direct component objects
  • Or a global variable

It's not specifically said, but I feel that the order of preference of selection of methods to call should be in that order as well, with global variables being last resort. But, that's neither here nor there.

So, if we combine Tell, Don't Ask with LoD then it's perfectly ok to pass objects to another object for "asking". Meaning, You have an Analysis object which you "Tell" to do something, passing the DataSet object as a parmeter. That's adhering to TDA. Inside the Analysis object's method you're adhering to LoD by only accessing "close friend" data.

This also conforms to SRP, since your DataSet is still just a DataSet and your Analysis object is an Analysis object.

The key take away here is that these principles are often "relativistic". Meaning that, from the perspective of the parent object that gets the data and wants to perform the analysis, you're "telling" the analysis object to do something.

The purpose of TDA is that your parent code should not query your DataSet for its state and then make decisions based on that. It should instead pass objects to other objects and have those objects perform their responsibilities, which may including querying those objects for their state, but that's ok because it's in the context of their responsibility.

Further reference here:

http://pragprog.com/articles/tell-dont-ask

EDIT:

If you would like a more authoritative source, there's no one better than Martin Fowler himself (read towards the end, you'll find this commentary)

http://martinfowler.com/bliki/TellDontAsk.html

But personally, I don't use tell-dont-ask. I do look to co-locate data and behavior, which often leads to similar results. One thing I find troubling about tell-dont-ask is that I've seen it encourage people to become GetterEradicators, seeking to get rid of all query methods. But there are times when objects collaborate effectively by providing information. A good example are objects that take input information and transform it to simplify their clients, such as using EmbeddedDocument. I've seen code get into convolutions of only telling where suitably responsible query methods would simplify matters 1. For me, tell-don't-ask is a stepping stone towards co-locating behavior and data, but I don't find it a point worth highlighting

OTHER TIPS

I would create a DataAnalyzer object who's responsibility would be to generate the report based on some analysis of the input data.

interface DataAnalyzer {

    public function analyze($input);

    public function report();
}

Now we can have different kinds of analysis we require

class AnalyzerOne implements DataAnalyzer {
    //one way of analyzing and reporting
}

class AnalyzerTwo implements DataAnalyzer {
   //other way of analyzing and reporting
}

I can make my DataSet object populate the Analyzer with some input for analysis and then delegate the reporting to it.

class DataSet {

    private $data;

    //... other methods

    public function report(DataAnalyzer $analyzer) {
        //prepare input for the analyzer from the current state
        $analyzer->analyze($input);
        return $analyzer->report();
    }

}

Finally the client would look like so

$dataSet = new DataSet();
//...

$firstReport = $dataSet->report(new AnalyzerOne());
$secondReport = $dataSet->report(new AnalyzerTwo());

So each object is responsible for separate tasks, dataSet does it's own business and analyzer is responsible for reports. However we do tell DataSet to use the analyzer to generate reports. DataSet then tells the analyzer what kind of input to use and returns the report.

Of course this is not the only way, but in general with this amount of information I think it's the right idea.

Sounds like perhaps a ViewModel is what you want. You have a Model (the DataSet), which is responsible for maintaining the state of your data and what that data represents. You have a View (the Report), which is responsible for displaying various pieces of data to the user but you want to transform the DataSet into a representation of the data appropriate for Viewing?

You could encapsulate the responsibility of preparing DataSet for viewing - DataSetViewModel for example. It could have functions on such as GetDataInReportFormat().

I think the main change is to shift your thinking to consider preparation of data for viewing as a separate responsibility.

May be a very simple application of inheritance can solve your problem.

So, you create a child class of Dataset to perform the analysis. In future if you need arise, you can create another Child class to performa that analysis.

The main benefit here, is Child classes inherit Dataset, internals, so they are from same family and can access the data Dataset.

I can give some code example. But let's first see what comments have to say here.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top