9

I use Python but I guess my question applies to OOP in general. Whenever I create a class I am never sure whether I should put a parameter/attribute in the constructor or in the method the parameter relates to.

For example, let's take a Person class which has a days_away method. The role of days_away is to calculate how many days the Person has been away given some timestamps. The constructor of Person (i.e. __init__) will get name as parameter. Will it also get timestamps as parameter or should timestamps go as a parameter of the days_away method which is supposed to calculate how many days the person was away given some datetime periods? Why?

Edit: To add some context. This is to split an electricity bill between persons sharing an apartment. The bill will be split as a function of the number of days a person has been away so that they don't have to pay for those dates. Days away will be calculated by the days_away method given the timestamps a person left and returned to the apartment.

6
  • What is the context of "days away?" Days away from what? Their birthdate? Away from the country? Away from their destination? This might be part of your dilemma. Apr 11, 2020 at 15:08
  • Is the thing a property of the object or a property of the operation/calculation/event? Apr 11, 2020 at 19:25
  • @GregBurghardt days_away is a method that is supposed to calculate the number of days the person has been away given some start and end datetime timestamps. Apr 12, 2020 at 7:50
  • But away from what? How does this data relate to the person, or is this just an arbitrary date calculation? Apr 12, 2020 at 15:30
  • @Greg Burghardt please see my edits at the end of my question. Apr 13, 2020 at 1:44

6 Answers 6

9

I look at the using code.

What's the difference between this

duration = Person(then).days_away(now)

and this?

duration = Person().days_away(then, now)

nothing. But

duration = person.days_away(now)

is different. This code doesn't have to know then. It only has to know now. That's very powerful. This code works in places that only know now.

So the only question is if you have a need to decouple then from now. If you don't, meh.

4
  • "This code works in places that only know now" - and person! But they don't have to the details of what comprises that person.
    – Bergi
    Apr 11, 2020 at 18:38
  • @Bergi true. Provided person is properly encapsulated this hides ‘then’ nicely. Apr 11, 2020 at 19:47
  • @candied_orange I added some context to my question. Please see my edits. Apr 12, 2020 at 8:01
  • @AndyGondzer since your edit doesn’t tell me if you have a need to decouple ‘then’ from ‘now’, I stand by my previous meh. Apr 12, 2020 at 11:19
7

One way to understand whether to use an instance member, e.g. passed as parameter in the constructor, or a method parameter, or other is to consider the lifetime of the information.

If a Person has timestamps that effectively lives as long as the person object, then that is probably where it belongs.

To one way of looking at it, an object is a binding of information together into an abstraction.  We rather expect that this binding is appropriate for the duration of the object.  This binding is accomplished via constructor parameters (ideally, but there are reasons why that sometimes cannot be done).

But we don't want to use instance fields to pass parameters to a method — that would have many of the same problems as using global variables as parameters (e.g. cumbersome and error prone, and, not thread safe).

Is it possible to invoke the days_away methods on the same person in different situations with different timestamps?  If so, then timestamps is shorter lived state (at least it has arguably different lifetime) than the person itself, and thus belong as a method parameter rather than constructor parameter.

In summary, are you binding something together into a single abstraction, or, are you intending one item to be dynamically provided, e.g. set and reset, and used in some situations that are beyond what you would get with binding.

4
  • I like this answer but a simplified summary would improve it.
    – user949300
    Apr 11, 2020 at 1:34
  • 1
    "Is it possible to invoke the days_away methods on the same person in different situations with different timestamps?" - This. This is the only relevant question to consider.
    – Bergi
    Apr 11, 2020 at 18:37
  • @Erik Eidt to add some context. This is to split an electricity bill between persons sharing an apartment. The bill will be split as a function of the number of days a person has been away so that they don't have to pay for those dates. Days away will be calculated by the days_away method given the timestamps a person left and returned to the apartment. Apr 12, 2020 at 8:06
  • @AndyGondzer: Given your updates, I believe this is the most comprehensive answer. I didn't mean to keep poking and prodding you for more information, but that context helps decide where the data should go. Frankly given the situation, I'm not sure if the "days away" logic even belongs on the Person. It sounds like an "electricity bill" needs a "prorate" method that takes one or more dates, depending on the situation. Even so, this answer should provide the insight required to redesign this part of the system. Apr 13, 2020 at 11:02
4

It is about dependencies, scope and the thought process. Let's start with the latter.

You probably go about this kind of problem asking yourself how to make it work. You already have an application in mind for the object you are about to create and are focused on its task. That is not the OO way to go. You are jumping to a scenario based solution.

Once you recognize you need a Person class you focus on that and ask yourself what it is that identifies a person. Do you need to know anything about it before you can call it a person? When will a person object start to make sense in your application?

If it is a name then that is what the constructor gets. If it is for a government application in which people are numbers you will need that number to make a person whole. If it is for a delivery service you may find you are more interested in the address after all and the person itself is just a nice-to-know attribute of the address, and you change your focus.

The above should prevent you from injecting stuff into an object constructor just because you may need it later for some task. The task of the constructor is to construct your object, not to prepare for some behavior.

Now your example. DaysAway is not an act, it should not be a method. Apparently you have to deal with absence. If that is all, you can include a list of time slots to Person as a property and add slots as it becomes known the person will be away for another period but you may be better served with an agenda object property. So I think it is not a good example.

If data is not tied to the object but rather to the act you should pass it to the method as parameters. An object is not a coat rack for variables you may need at some point for some task in some context, object data should make sense to the object itself for the life time of the object. This covers both the scope and dependency issues.

2
  • Sorry, I should have added some context to my question. I love your answer though. This is indeed for a small app calculates how much each flatmate has to pay from the electricity bill. Days away of each person are taken into consideration when doing the calculation. So, a Person is a flatmate. The days_away method is supposed to calculate the number of days given the dates a Person left and returned to the apartment for the month of the bill. Would you put the timestamps in the constructor? Apr 12, 2020 at 8:34
  • @Andy Gondzer No I would not put the time slots in the constructor. I would keep a table of in and out moments for all tenants and process that at the end of the month, calculating the fraction of the month for each tenant they have been in even before a Person object is instantiated. It does not seem to me you need a person object for anything, you can just produce the report straight from the table and the total of the heating bill. Your one class could be HeatingBillReportMaker. Apr 12, 2020 at 10:40
3

Data that belongs to the class should go into the constructor. This data should ideally be encapsulated by placing it in private fields. Methods can operate on this data, but sometimes a method needs additional data which doesn’t belong to the class.

Let’s say a person has a date of birth as a private field and you want to know the person’s age at a given date. That second date should be an argument of the method that calculates the age.

1
  • This is for a program that splits the electricity bill between Persons sharing the apartments. The days_away method will calculate the number of days a Person was away given the dates they left and returned to the apartment. Apr 12, 2020 at 8:10
2

One way to think of a constructor is a function that instantiates an object instance with whatever it requires to be "complete."

If you want an object of Bag, and a Bag can be empty, then its constructor, or a constructor for it, could be created that requires no variables passed to it

Bag b = new Bag();

But, if the object is, perhaps, a Database connection, then its constructor could require attributes, say the dataBasePassword and databaseUserName, as those are "required" for the object to connect to the Database, for this contrived illustration.

DatabaseConnection dc = new DatabaseConnection(user, pwd);
1
  • If your Bag class had to have an items parameter which would be a list containing the items inside the bag and a number_of_items method returning the number of items in the bag, then would you pass items=[] to the constructor, or would you pass items to number_of_items? So the first or the second class in here: repl.it/repls/SubduedTrickyActiveserverpages Apr 12, 2020 at 8:15
0

I have found the following heuristic to be quite useful in most cases.

Premise

  • The places where you instantiate the object, and where you call its methods are separated, both in time, and in scope.

Theorem

  • The best design emerges when you define the injection of parameters as early as possible, early meaning with respect to the flow of your code.

Corollary

  • If, in your design flow, you get to know the parameter value at the construction time of your object, you define it as an input to the constructor, otherwise you have to define it as an input to the method.

Narrative

If you take the Premise as an axiom of good object-oriented design, then the Theorem may help guide your design, given that you actually have one at hand (and not blindly naming objects just for the sake of the class names).

In its TL;DR version, this answer reads: if the parameter value becomes known at the very latest moment during your design, just put it in the method. If you knew it any earlier in advance, take it out, either in the constructor, or in another method.

Examples

  • Example 1

Create a Point class, with a distanceFrom method, to calculate the distance from another point. Does it make sense to have the method distanceFrom(x, y, xOther, yOther)? Nope, because you knew x and y of the point upon its construction, so you should have passed that to the constructor. So the proper method is distanceFrom(x,y) (or, more likely, distanceFrom(point))

  • Example 2 (your case)

When you calculate days away of a Person, you have to subtract two timestamps. One of them is now. The other one of them is sometime in the past. The past timestamp was known earlier so the optimal design is to avoid putting it in the method. Find some way to acquaint your Person object with the past timestamp as early as possible, preferably the moment it becomes known, i.e., possibly, in the constructor or, if you want to change it along the lifetime of your Person object, create another method along the lines of Person.cameBack(timestamp), so that the Person can mark the timestamp to use it later for the daysAway method.

1
  • Thanks for the answer. timestamps is actually a list of datetimes denoting the date when a person left and returned to the apartment. Please see the edit in my question for more context. Apr 12, 2020 at 8:45

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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