Java – Designing a Class to take whole classes as parameters rather than individual properties

cdesignjavasolid

Let's say, for example, you have an application with a widely shared class called User. This class exposes all information about the user, their Id, name, levels of access to each module, timezone etc.

The user data are obviously widely referenced throughout the system, but for whatever reason, the system is set up so that instead of passing this user object into classes that depend on it, we're just passing in individual properties from it.

A class that requires the user id, will simply require the GUID userId as a parameter, sometimes we might need the username as well, so that is passed in as a separate parameter. In some cases, this is passed to individual methods, so the values are not held at the class level at all.

Every single time I need access to a different piece of information from the User class, I have to make changes by adding parameters and where adding a new overload is not appropriate, I have to change every reference to the method or class constructor as well.

The user is just one example. This is widely practiced in our code.

Am I right in thinking this is a violation of the Open/Closed principle? Not just the act of changing existing classes, but setting them up in the first place so that widespread changes are very likely to be required in the future?

If we just passed in the User object, I could make a small change to the class I am working with. If I have to add a parameter, I might have to make dozens of changes to references to the class.

Are any other principles broken by this practice? Dependency inversion perhaps? Although we're not referencing an abstraction, there is only one kind of user, so there isn't any real need to have a User interface.

Are there other, non-SOLID principles being violated, such as basic defensive programming principles?

Should my constructor look like this:

MyConstructor(GUID userid, String username)

Or this:

MyConstructor(User theUser)

Post Edit:

It has been suggested that the question is answered in "Pass ID or Object?". This does not answer the question of how the decision to go either way affects an attempt to follow the SOLID principles, which is at the core of this question.

Best Answer

There is absolutely nothing wrong with passing an entire User object as a parameter. In fact, it might help clarify your code, and make it more obvious to programmers what a method takes if the method signature requires a User.

Passing simple data types is nice, until they mean something other than what they are. Consider this example:

public class Foo
{
    public void Bar(int userId)
    {
        // ...
    }
}

And an example usage:

var user = blogPostRepository.Find(32);
var foo = new Foo();

foo.Bar(user.Id);

Can you spot the defect? The compiler can't. The "user Id" being passed is just an integer. We name the variable user but initialize its value from the blogPostRepository object, that presumably returns BlogPost objects, not User objects - yet the code compiles and you end up with a wonky runtime error.

Now consider this altered example:

public class Foo
{
    public void Bar(User user)
    {
        // ...
    }
}

Maybe the Bar method only uses the "user Id" but the method signature requires a User object. Now let's go back to the same example usage as before, but amend it to pass the entire "user" in:

var user = blogPostRepository.Find(32);
var foo = new Foo();

foo.Bar(user);

Now we have a compiler error. The blogPostRepository.Find method returns a BlogPost object, which we cleverly call "user". Then we pass this "user" to the Bar method and promptly get a compiler error, because we can't pass a BlogPost to a method that accepts a User.

The Type System of the language is being leveraged to write correct code quicker, and identify defects at compile time, rather than run time.

Really, having to refactor lots of code because user information changes is merely a symptom of other problems. By passing an entire User object you gain the benefits above, in addition to the benefits of not having to refactor all method signatures that accept user information when something about the User class changes.

Related Topic