4

What is the best practice when it comes to passing variables around? Is it better to pass objects (models) or is it better to pass primitives that will be required by the called method? What are the pros and cons of both approaches? For example one con of passing values (Example 2) when you suddenly require the user's first name you have to change method signature in your interface. One con of passing objects is that it tightly couples you to that interface.

Example 1:

class CustomerService{
    public function emailCustomer(UserModel $user, string $message)
    {
        //Get email address from model and send email
    }
}

Example 2:

class CustomerService{
    public function emailCustomer(string $userEmail, string $message)
    {
        //send email
    }
}
  • 3
    I think the claim that using objects "tightly couples you to that interface" suggests a misunderstanding of tight coupling. Step back from the terminology for a moment and focus on goals: you want to be able to make changes to the code without having to modify everything that interacts with it. Which approach aligns with that goal? – JimmyJames Mar 1 '19 at 16:45
  • 1
    There is no best practice. There are pros and cons to both. Coupling is one issue; there is also brevity, chattiness (if the call passes over a process boundary), multithreading concerns (if the objects are mutable), security (if the object contains sensitive information that is not needed for the call), etc. You'd have to provide a lot more specifics about your project for good advice on this topic. – John Wu Mar 2 '19 at 4:12
  • Also depends somewhat on the programming language. – Christian Hackl Mar 8 '19 at 9:12
5

Recently, I stumbled across Mark Seeman's old blog post titled At the boundaries applications are not Object Oriented, which I think it might help you out to decide which approach works best for you. And when.

In a nutshell, it depends on the level of abstraction you are working on. How close (or far) is from its respective boundary.

Software architecture is the art of drawing lines that I call boundaries. Those boundaries separate software elements from one another and restrict those on one side from knowing about those on the other.

Clean Architecture: A Craftsman's Guide to Software Structure and Design, First Edition by Robert C. Martin

Say we implement the domain of a mailing service. According to R.C.M, we are working on the innermost layer of the architecture. The core. At this level, the goal is modelling the domain to evoke as much as possible the ubiquitous language.

Domain layer - Data model

public interface Sender {

  void send(MimeMessage message);

}

MimeMessage is a first-citizen class of the domain. It looks after of its invariants and best describes what a mail's message is.

public MimeMessage {
   private InternetAddress from;
   private InternetAddress[] recipients;
   private InternetAddress[] cc;
   private InternetAddress[] bcc;
   private BodyPart bodyPart;
   private BodyMultiPart multipart;
   private String subject;

   public MimeMessage(
         InternetAddress from, 
         InternetAddress[] recipients, BodyPart bodyPart){..}

   public void addMultipart(BodyMultipart attachment){..}
}

Say now, we move across the upper levels of abstraction of the domain, up to its boundaries, where the implementation details begin to be seen distant and the boundaries of outer-layers begin to be seen closer. Here is where Mark Seeman's blog post begins to make sense.

At the boundaries, where things are at risk of getting tightly coupled, is where we find that making things less OO could be convenient because it makes life easier. Although, at expenses of missing a bit of expressiveness.

Domain layer - Service

public interface MailService {
  void send(String from, String[] to, String subject, String message);
  void send(String from, String[] to, String subject, String message, File[] attachments);

}

Mark Seeman introduce the concept of data structures like the way to represent data models at the moment of their transfer from layer to layer. Data structures to communicate components as if they were different processes communicating each other through the network. In other words, serialisation.

Think about it. MimeMessage at the edge of its domain's boundary, could be serialised in many ways, for example as formatted string, as an array of strings, as a Map or Set of objects or, why not, as 5 different Strings and not fall in the primitive obsession because its usage is narrowed to a very specific scope. The inter-layer communication. The MimeMessage is used where its expressiveness is valuable (within its domain's boundary).

Summarising

Why limiting the choice to one or another solution when we could use both? It only demand us to find the right place and be consistent. The thing is that both solutions are not mutually exclusive.

The alternative is like to increases the overall complexity. In some cases, the increase will be justified others it will arguable, so be pragmatic.


1: I won't enumerate the pros and cons because I think @DavidArno and @Robert already did

1

Depends on what you mean by "objects".

If you mean a data structure or a bag of properties, then there is virtually no difference at all. There are some syntactical differences of course. Passing a small amount of primitives may be somewhat more convenient, while passing an "object" is marginally safer type-wise.

The real difference in convenience and maintainability comes when you start to use objects as real concepts that have behavior. You will start to notice, that it is not your choice to use primitives or objects, instead it will be a consequence of modeling behavior. Things that don't have business-related behavior will stay primitive, and things that do will naturally form objects.

Note: using "Service" and "Model" suffixes in your object names suggest that you are not yet using objects to model behavior.

1

What is the best practice when it comes to passing variables around. Is it better to pass objects(models) or is it better to pass primitives that will be required by the called method

Passing around primitives is widely seen as a code smell, aka primitive obsession. A simple web search of "primitive obsession" will provide many articles on why its a bad idea.

In general, the advantages of using objects are:

  1. improved clarity. A string could be anything; a UserModel is clearer that we aren't dealing with any old data, but with a user model.
  2. Improved data integrity. Again, a string could contain anything. I might accidently pass the username rather than the email address for example. So I then have to consider validating that string within my method. Pass me a UserModel though and those issues decrease.
  3. Potentially better code organisation and less risk of duplication. If we have actions we wish to perform on eg email addresses, those actions could all live within the UserModel class. This is very much a "rich domain model" OO "benefit" though so may not be relevant to you.

What's more difficult to find is details on when it isn't a code smell and/or what the disadvantages of using simple objects instead of primitives. Put simply, an obsession with primitive obsession can lead to over-engineering code:

  1. I now have to write, test and maintain a UserModel class, rather than just using a string, which I know already works. I've complicated my code.
  2. Is UserModel used widely, especially at API boundaries? If so, then it makes sense to use it. But otherwise, it's questionable whether that extra type brings enough benefit to outweigh the added complexity.

In summary, avoid primitives for methods that form an API and use objects. For internal implementation-details code though, primitives may be the better choice in order to keep things simple.

  • 1
    I wouldn't categorically exclude passing primitives though. If you need to create a method to sign in, you don't need a UserLoginInfo class, only two strings. Adding classes to represent data/parameters weighs the program down. With each class, it is important to evalute if that extra weight and complication is ultimately improving the code or if it is superfluous and unnecessary. – Neil Mar 1 '19 at 10:43
0

In general use whatever makes more sense.

Using objects pros:

  • Aggregates parameters. Instead of using drawPoint(int x, int y, int z) it is more convenient to use drawPoint(Point3D point)
  • Easier to read (the same example with point).

Using objects cons:

  • Increases coupling. If you change UserModel then emailCustomer might have to change.
  • Harder to test. Now you have to mock UserModel instead of sending simple string.
  • Easier to misuse see Law of Demeter
  • 1
    I kind of get where the coupling thing is coming from but I'm not convinced that using an object necessarily increases coupling. If you pass primitives, there is tighter coupling to the method signature. For example let say you need to do something different for users depending on their role. If you pass the object, you just update the email method. If you were passing primitives you now need to every call in the code and add another parameter. And if every call to this method looks like: emailCustomer(model.user) you're no less coupled to the UserModel type anyway. – JimmyJames Mar 7 '19 at 19:06
  • For first part. I don't really understand why passing User helps. Possible things that might happen: email should not be sent, email should be sent to different email address, different message should be sent. None of this is responsibility of this method. It has well defined signature that allows it to follow single responsibility principle. In this method you should not have any logic related to how message is rendered or how email address is picked. Imagine sentence: "user model has changed therefore we should change how we send email" it does not make sense. – Žilvinas Rudžionis Mar 7 '19 at 19:38
  • For second part. If you use emailCustomer(model.user) you have one less method that is coupled with User. I don't see why this is not better. – Žilvinas Rudžionis Mar 7 '19 at 19:39
  • "None of this is responsibility of this method" Sending email is not the responsiblity of the sendEmail method? Whose responsibility is it? – JimmyJames Mar 7 '19 at 20:33
  • 1
    "sendEmail should be concerned only with sending email and not with getting email address or getting message that should be sent." Honestly, I think it would make the most sense to have a generic email function/class and then have a user.sendEmail method that uses it. My point here is that SRP is not an excuse to violate DRY. SRP is subject to semantics and has no real objective measure. It's a great principle to guide design but you can't think of it as a hard and fast rule. Trying to do so turns it into rhetoric. – JimmyJames Mar 8 '19 at 16:06
0

I don't refer any book or document to prove something but I can say that it is totally depends on your usage considering pros and cons. Thus, I can just offer a better way that I try to use everytime:

  1. Use interface as parameter on Public methods.

    Your signature can be used by many place and it is really hard to change those. To ignore this, you can use interfaces as parameter as possible. If it looks hard to play with interfaces, then you can use concrete classes instead.

  2. Try to use primitive types for inner private business methods as possible.

    Using interfaces or concrete classes need initialization and null reference control. You can simplify your business methods by using primitive types as parameters. Besides, managing reference types is not easy. Instead of changing reference type items values, return your calculated value and let them to change from caller. Of course you need to create private class that needs interface or concrete class as parameters but it is not change the main approach. I just recommend you to keep your private methods simple as possible.

0

You could use "String" as the interface type. You could have a class "EmailAddress", then that would very likely be a candidate for the interface type. Your "ModelUser" may have a String or EmailAddress member. Or it might have several.

I'd say best interface would be "EmailAddress". You should be able to create an EmailAddress from a String containing an email address, and you should have a method to get an EmailAddress from a ModelUser. Second best would be String, especially if you don't have an EmailAddress class. "ModelUser" has a problem when a user can have multiple email addresses, because you'd need some way to choose which one to use.

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.