1

I have trouble understanding if I should pass an argument to a method as a primitive value or as an already-obtained object.

Consider this simple example

interface Channel{

    String getId();

    String getName();
}

interface ChannelRepository{
    Channel findBy( String id);
}

public class DummyChannelRepository implements ChannelRepository{
    @Override
    public Channel findBy(String id) {
        return null;
    }
}

interface Customer{

    String getId();

    String getChannelId();

    String getName();
}

interface CustomerRepository{
    Customer findBy( String id);
}

public class DummyCustomerRepository implements CustomerRepository{
    @Override
    public Customer findBy(String id) {
        return null;
    }
}

Now I want to create a common calculator interface that receives a channel and a customer. For me it's not clear if the calculation method of my interface should use primitive parameters (in that case Ids) or it should use object instances. So I came up with two solutions.

Variant #1 using object instances

interface CalculatorThisWay{
    double calculate( Channel channel, Customer customer  );
}

public static  class DummyCalculatorThisWay implements CalculatorThisWay{
    public double calculate( Channel channel, Customer customer  ){
        // do whatever
        return 1.5d;
    }
}

public static void main(String[] args) {
    String channelId  = "Channel-1";
    String customerId = "Customer-999";

    ChannelRepository channelRepo = new DummyChannelRepository();
    CustomerRepository customerRepo = new DummyCustomerRepository();

    Channel channel = channelRepo.findBy( channelId );
    Objects.requireNonNull( channel, "Channel must not be null");

    Customer customer = customerRepo.findBy( customerId );
    Objects.requireNonNull( customer, "Customer must not be null");

    DummyCalculatorThisWay calc = new DummyCalculatorThisWay();
    calc.calculate(channel, customer);


}

Variant #2 using primitive identifiers

interface CalculatorThatWay{
    double calculate( String channelId, String customerId );
}

public class DummyCalculatorThatWay implements CalculatorThatWay{

    private final ChannelRepository channelRepo;

    private final CustomerRepository customerRepo;

    public DummyCalculatorThatWay(  ChannelRepository channelRepo,  CustomerRepository customerRepo ){
        this.channelRepo = channelRepo;
        this.customerRepo = customerRepo;
    }

    public double calculate( String channelId, String customerId ){

        Channel channel = channelRepo.findBy( channelId );
        Objects.requireNonNull( channel, "Channel must not be null");

        Customer csutomer = customerRepo.findBy( customerId );
        Objects.requireNonNull( csutomer, "Customer must not be null");

        // do whatever

        return 1.5d;
    }
}

public static void main2(String[] args) {
    String channelId  = "Channel-1";
    String customerId = "Customer-999";

    DummyCalculatorThatWay calc = new DummyCalculatorThatWay(
            new DummyChannelRepository(),
            new DummyCustomerRepository()
    );

    calc.calculate( channelId,  customerId);
}

So for me both ways feel correct but consider reusing the logic of how to get the channel and the customer object in different places. In Variant 1 I would have to duplicate that logic or put it in some Helper class where as in Variant 2 the logic is in the class itself and can be reused.

So is there any guide when to use what or any best practice?

2

Use object instances.

This is reminiscent to a known anti-pattern to use primitive types where they should be wrapped into their own class (I forgot the book). The argument is that additional abstraction finally reduces the influence of changes and their handling. Allowing special cases and so on.

If under the hood (other) objects are referenced, it is clear to start with objects.

If pure primitive type calculations are done, it might make sence to have a calculatory class decoupled of all those object classes. I guess this to be rare, and maybe having too many parameters. Of course it would be great for unit tests, test driven development - when feasible. It also has the advantage of not leaking to the objects, like setting a field in a calculation (a bad, maybe even undesirable side-effect).

So: use abstractions, actually decreasing the number of parameters.

2

Always pass objects around within your code, not their keys/names/references. References are ok when you need to communicate with external entities such as services or clients of your service, but the references should normally be resolved when they enter your system. An exception can be made for references embedded within objects, where you would often prefer to resolve them only when you need the referenced object.

0

In general which to use varies on a case by case basis which might also include not only what you're doing but how in terms of the set of different strategies chosen.

In your specific case you should pass the objects to calculate. That way you can easily retrieve them once and then use them against many different classes.

There's no need for the fetching of the items to be coupled with the operation on them especially when you're using ORM.

In a scenario where only the IDs are being passed, it might also be common for the operation to be run against persistence rather than fetching the objects from persistence, applying the changes then putting those back.

You can also put a method on your class for fetching the required objects from storage and then applying the calculation sort of giving you the best of both worlds for the time being.

0

Other answers have correctly specified using domain objects over primitive types.  However, though there are more choices than your variant #1 and #2.

The strength of your v#1 is use of domain types over primitive types, while the strength of v#2 is that you've introduced a new domain concept, a binding of repos.

The weakness of v#1 is that too much domain logic is in the consuming client (here two lookups by id in main), while the weakness of v#2 is both the use of primitive types, and that the introduced domain concept is perhaps too low level and perhaps yet still conflating more than one domain concept.

If the channel id & customer id are logically a pair then they should be turned into a single id object as soon as possible, so that the consumer can deal with the pair instead of individuals.  This could be applied both regarding primitive types and also regarding domain types as below.  Further, as we have reified the looked up pair, maybe this is a good home for the otherwise homeless calculate method:

// pair applied to id's
var idForCustomerInChannel = new IdForCustomerInChannel ( channelId, customerId );

// a domain object from an id pair
var customerInChannel = repo.findOrThrow ( idForCustomerInChannel );

// calculate on domain object
customerInChannel.calculate ();

Many variations on this are possible, one of which might be appropriate for your broader context (you did not show).

It appears to me that channel and customer repos are related so maybe we have:

var channel = channelRepo.findChannelOrThrow ( channelId );
var customer = channel.findCustomerOrThrow ( customerId );
customer.calculate ();

I look first to the simplicity or complexity of the consuming client before considering implementation details.  The consuming client's usage should be simple, direct, and sensible.  Finding the right (number of) domain abstractions helps in this regard.  Usually implementation simplicity follows having the right abstractions.

Your Answer

By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

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