3

Let's say I have a class with some suitable instance variables which are then used for some computation (via a method of the class). As the computation will generate lots of data which needs to be accessed by other classes, it needs to be stored somewhere. What is the best way in this scenario? Obviously you can store the result inside the class but that couples logic and data. Another option might be generate separate data class but I am not sure if that is a bad design.

6
  • 8
    Obviously you can store the result inside the class but that couples logic and data -- Isn't that kinda the whole point of using classes? The problem is not coupling logic and data within the class, the problem is sharing the data between classes without coupling the classes (assuming you actually need to keep the classes decoupled, which may not be the case). – Robert Harvey May 5 at 20:00
  • 1
    Combining data and behavior is one of the key concepts of the OOP paradigm. – Rik D May 5 at 20:01
  • @RobertHarvey I'd like to point out that the data generated does no longer require the context of the class. – MaxxOr May 5 at 20:11
  • 1
    Your point being? – Robert Harvey May 5 at 20:13
  • 2
    This question is too vague to solicit a meaningful canonical opinion, IMO. Can you be more specific about "the instance variables", "the computation", and "the data"? What do you mean "it needs to be stored somewhere?" For some objects, the result of a "computation" is a return-value that the consumer decides where to store. And sometimes the "storage" is just a closure. Other times, the data is deterministic and memoizable. Sometimes "caching" is a better way to think about it, and you'd introduce another layer (class) ... So, can you be more specific? :) – svidgen May 5 at 22:54
7

A key principle of software engineering is separation of concerns. This leads to separate independent things, and group strongly related things. Your question seems to be about that.

Your wording unfortunately leaves a couple of open issues regarding the context. Moreover its terminology could mislead. So, let's first agree that classes and objects are meant to couple and encapsulate data and processing. So "coupling data and logic" is not an issue but simply a fact of object oriented life. Hence, what your use of the word "data" is to be understood as a colloquial way to refer about the information value of objects (which could be of basic types, or of a more complex class composing several types).

If we ignore the ambiguous wording we then have the following main options:

  • Case 1: The complex computation completely belongs to the responsibility of the class on which it is performed. You could then perfectly store the result of the computation in the class. In fact, this is the foundation of memoization.

    Example: The objects of a class Polygon are made of a sequence of many Point. The calculation of the Polygon's surface is a complex calculation that needs to identify a lot of triangles, and sum up the surface of all these triangles. The calculation of the surface is probably a responsibility of the Polygon. Other classes may access the results using the method calculateSurface() which might store internally in private properties the results to reuse them in subsequent calls and avoid to recalculate everytime.

  • Case 2: The complex computation is independent of the object on which it is performed, but the result makes only sense in the context of the computation. You'd prefer to create a separate object for the independent computation with its results. This decouples the algorithm and its results, from the input class.

    Example: a general-purpose statistical algorithm such as a linear regression can require time-consuming computations. You may want to use this algorithm on the points of the polygon. You'd therefore design a generic LinearRegression class, in which you inject an iterator that will access the input points. This ensures that the same algorithm could be reused also for real-time quotations on the stock market. You'd probably use an adapter that extracts the points from the polygon. The results would then be stored in `LinearRegression. This approach is also useful if it's not about one algorithm but a family of algorithms sharing some intermediary computations.

  • Case 3: Only the result of the computation matters, and it has a life on its own. THen, regardless of how you'd implement the computation, a result object should be created.

    Example: the input objects are pictures of a VTC camera. A complex image processing AI algorithm extracts a set of polygon of the picture. Each polygon corresponds to a human face identified in the picture. The polygons produced are then used independently of the original image, which could even be discarded, for example to find a match in a face-recognition database, or to compare faces identified accross several images.

So the most suitable approach depends heavily on the context, and especially how much the results are bound to the input or the algoirthm itself.

3

I suspect the real problem here is that there exists a point in time when you know the instance vars but you haven’t yet done the expensive calculation to get the data. Which means if you store the data in the object it now has two states: before and after. Bleh.

I think it’s better to have an object that knows the logic and vars used to make the data but that shoves the data into a new object that it hands to whatever asked for it.

This way there isn’t two states. There’s just an object that makes the data object when asked. Done this way it can immutable. It’s ready to make the data when it exists. And the data object is ready to be used when it exists.

What’s really nice is you don’t have to ask things if they’re ready to be used because if they exist, they’re ready.

If the problem is that you need the data in contexts where you don’t remember if it’s already been created you can make things more efficient by caching the data. Having immutable objects hold it doesn’t stop you from doing that. Memoization works with them just fine. We do with immutable Strings after all.

But sometimes the best way to solve a problem is to avoid it. If you need the data then let something inject it into you. Don’t go asking for it.

6
  • You can also cache or memoize the calculation so that you only take the cost once. – Robert Harvey May 5 at 21:13
  • @RobertHarvey better now? – candied_orange May 5 at 21:53
  • What I dislike in this answer is its notion of the described approach to be "generally better" (as anything else). Any standard memoization technique will deal with the fact an object has a "before" and an "after" state, in a clean manner, and there are lots of cases where this is perfectly fine. Christoph's answer explains this well. Said that, putting the result of a calculation in a separated object is ok for several situations, but may be overdesigned for others. – Doc Brown May 6 at 6:04
  • 1
    If I understand well your answer, you seem to assume that the computation and its result belong to the object and its state. But what if the result of the computation does not really belong to sate of the object, with a view to the SRP ? (e.g. Cartography could contain a graph of towns and nodes; and the complex calculation could be about a path between two towns: first there are many ways to calculate the path; second the path does not belong to the state of Cartography) – Christophe May 6 at 7:33
  • 2
    Which means if you store the data in the object it now has two states: before and after. Bleh. I mean, that vague definition would also include a Lazy<T>, so I'm not sure why you're outright dismissive of the whole idea. The benefits of deferred (and/or single single run, multi access) execution, when needed, tend to outweigh the idea that an object may have more than one state. If it's important to distinguish between the states, just enshrine their state in a property so you can easily tell. – Flater May 6 at 8:07

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.