4

For example, lets say I have this class:

public class Account {
    private String username
    private String email
    private String phoneNumber
    private String zipCode
    private String website
}

Each of these fields could potentially be separated into their own class:

public class Account {
    private Username username
    private Email email
    private PhoneNumber phoneNumber
    private ZipCode zipCode
    private Url website
}

public class Username {
    ...
}

public class Email {
    ...
}

public class PhoneNumber {
    ...
}

public class ZipCode {
    ...
}

public class Url {
    ...
}

Is this considered a good practice? Is there a rule of thumb that I can use to determine if a field should be separated into its own class?

  • 1
    Nearly always. How will you check for a valid phone number or zip code? That logic probably belongs in a PhoneNumber or Zipcode class. – user949300 Feb 25 '18 at 2:47
8

I consider that a good practice provided one thing: Those objects had better have useful methods in them, and getters and setters don't count. Validation code would.

Primitive obsession is the name of the smell you're avoiding by doing this. If validation makes sense, because some values wouldn't make sense, then the field could probably be justified as being a type. You create the class to simulate the type. Also this helps cohesion by giving you a place to pull logic related to the field together in one place.

  • Good advice: "Those objects had better have useful methods in them, and getters and setters don't count." – user949300 Mar 21 '18 at 20:49
4

We need good abstractions. However, sometimes there is little point in providing abstractions that have no special utility. So, it is a trade off to make. If you need to err, though, do it on the side of creating the domain-specific type for a simple datum like int or string.

I consider it generally ok to include primitive date types within another abstraction (like your Account), as such fields in such a container provide the context/meaning for these types. BirthDate or AccountStartDate are examples, that I feel are sufficient as simple dates; their meaning is in context of the containing type. Wrapping these in extra types here doesn't necessarily offer value, and can make the values harder to use.

However number types and string types are so common (and low-level) that they can be very error prone in usage, so merit additional attention, e.g. being wrapped in domain-meaningful types. It is not ok is to find these primitive types/attributes on their own far outside of some containing abstraction. So, if you see a primitive field's value being shared broadly in code, detached from a containing object, then that is a smell that they should have their own types.

In the other direction, as with birth date, it can be be overkill to give each specific field a specific type. For another example, there's a notion of phone number for an emergency contact. Dialing this number is the same as dialing any other number, so we don't want or need special classes for dialing an emergency phone number, and we simply know that it is the emergency contact from the containing abstraction by the field rather than by type.

2

In most of the time is a good practice separate these fields in classes. OOP is your friend :).

I will give an example. Probably, your e-mail is a field that can appears in a lot of places in your application. Working only with e-mail as a field you need to have some kind of Helper/Util class with static methods to perform some e-mail validations/action in each place that the field appears. This is not very reusable and is annoying, because someone can forget to use the Helper/Util or even forget to call some important method inside the Helper/Util to perform some action on e-mail.

Creating a class Email, it's way easier to reuse the e-mail actions:

class Email {
    public String validate();
    public String getDomain();
    public String getValue();
    public String getValueForPublicWeb(); // return john at gmail dot com
}

And you can use in any class very easy:

class Person {
    private Email email;
}

class Login {
    private Email email;
}
1

Yes it is a good practice.

Considering that "dumb classes" are classes that have default constructor, and only methods getters and setters that do not perform any business rule.

It is a good approach when you do not use dumb classes, you are separating the responsabilities of your code to each classes that should care about its data. Your classes will be high cohesive. You may increase some coupling.

// a class that isn't dumb.
public class Username {
    private String username;
    public Username(String username){
        this.validateUsername(username);
        this.username = username;            
    }
    public setName(String username){
        this.validateUsername(username);
        this.username = username;
    }
    public String getName(){
        return this.username;
    }
    private validateUsername(String username){
        if(username == null){
            throw new UsernameShouldNotBeNullException();
        }
        if(username.length() <= 4){
            throw new UsernameShouldNotHaveLessThanFourLenghtException();
        }
    }
}

Doing this way, you are concentrating the business rule about Username in the class Username. You will not be spreading rules pertaining to username by the whole system. None service will know how to instantiate a valid username, just the Username class.

0

The relationship between Accounts and WebSites/PhoneNumbers/Data is relevant and depends on the actual data model.

Assuming" Account does not need to know the details of phone number (or other data) components: This would be if Account just needs generic phone numbers or websites, and that warehouse packaging has as much need for generic phone numbers and websites as well. But even phone numbers or websites have some specific validation issues, in my opinion. In this model Account just "has a" "PhoneNumber" and PhoneNumber class can be maintained on its own and released independently if the signatures of access-methods do not change.

Assuming Account really does have Account-specific usage for seemingly-primitive data: It could be that Accounts really do have Account-specific methods of handling data that cannot be shared with warehouse packaging. Then you have Account-only data forms defined with class Account. You could derive Account-specific variants of website-class or phonenumber-class, but that just seems too much work.

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.