Filip Bartuzi
Filip Bartuzi

Reputation: 5931

How much can object know about other object? Is it example of demeter's law violation?

I've got an issue with understanding how well can I allow objects to know each other.

In my simple example I've got classes: Bill, Customer, Checkout and Cart. Customer has list of their bills, cart is associated to customer and checkout finalize buying operation (add bill to customer)

SO in method public void checkouts(Cart cart) I initliaze new bill Bill bill = new Bill(cart) and then I want to add bill to the customer. I've got 3 possible ways to do it:

cart.getCustomer().getBills().add(bill);
//   ^^^ directly add bill to ArrayList in Customer
cart.getCustomer().addBill(bill);
//   ^^^ Customer get bill and put it itself in their own method
cart.addBillToCustomer(bill);
//   ^^^ In this example class Checkout doesn't now anything about customer. 
//       Responsibility goes to cart which calls getCustomer().addBill(bill)

I guess the first one is really wrong because Checkout has to know that getBills() returns array list(which is dangerous)

In the second example class checkouts gets customer which isn't directly associated with it and than it has to know customer interface to add bills.

In third example checkout, which isn't directly associated with customer, don't use any knowledge about customer it just delegate task to cart which is directly connected to customer (it has field Customer customer)

Which solution is the best,why and why others are worse?

Upvotes: 3

Views: 129

Answers (4)

Hoa Nguyen
Hoa Nguyen

Reputation: 14560

First, let us try to figure out what law of demeter states:

Law of Demeter (LoD):

A method f of a class C should only call the methods of these:

  1. C
  2. An object created by f
  3. An object passed as an argument to f
  4. An object held in an instance variable of C

Let us consider some examples using your Checkout class:

public class Checkout {
    private MyLogger logger;

    public void log(String msg) {
        // Write down the msg here
    }

    // 1. method log1 should call method(s) of class Checkout, i.e Checkout.log
    public void log1() {
        log("Hello");
    }

    // 2. method log2 should call method(s) of an object created by itself, i.e Date.toString
    public void log2() {
        Date now = new Date();
        System.out.println(now.toString());
    }

    // 3. method log3 should call method(s) of an object passed as an argument to it, i.e Cart.toString
    public void log3(Cart cart) {
        System.out.println(cart.toString());
    }

    // 4. method log4 should call method(s) of an object which is an instance variable of C, i.e Logger.log
    public void log4() {
        logger.log("some log message");
    }


    public void checkouts(Cart cart) {
        Bill bill = new Bill()
        cart.getCustomer().getBills().add(bill);
        //   ^^^ directly add bill to ArrayList in Customer
        cart.getCustomer().addBill(bill);
        //   ^^^ Customer get bill and put it itself in their own method
        cart.addBillToCustomer(bill);
        //   ^^^ In this example class Checkout doesn't now anything about customer. 
        //       Responsibility goes to cart which calls getCustomer().addBill(bill)
    }
}

    public class Cart {
        Customer customer;
    }
    
    public class Customer {
        List<Bill> bills;
    
        public void addBill(Bill bill) {
    
        }
    }
    
    public class Bill {}

And now, back to your question:

1. How much can object know about other object?

It's clearly stated in law of demeter: A method f of a class C should only call the methods of these:

  1. C
  2. An object created by f
  3. An object passed as an argument to f
  4. An object held in an instance variable of C".

2. Is it example of demeter's law violation?

  • First and second cases do.
  • Third case is OK - An object (Cart) passed as an argument to f

Explanation:

For the first one cart.getCustomer().getBills().add(bill);, if we change the implementation as below:

public class Customer {
    Bill bills[]; // error bills.add(Bill)
}

Then the code will no longer work as array does not have the add method.

Also, for the second one: cart.getCustomer().addBill(bill);

public class Customer {
    Bill bills[];

    public void addBillBefore (Bill bill) {
        bills.add(bill);
    }

Must be changed to

    public void addBillAfter(Bill bill) {
        // Assume that current index is correctly setup
        bills[currentIndex] = bill
    }
}

That is, Implementation of Customer has to be changed.

Upvotes: 2

gaRos
gaRos

Reputation: 2763

Why do you need a separate Bill and Cart object? It looks really weird that you have a cart, and you get the customer from that and you add a bill to the customer.

Custumer could have just Cart objects with state paid or not paid, why is it important to separate them?

(I'm asking this because if you can make the abstraction simpler now, it could benefit later)

Upvotes: 0

biziclop
biziclop

Reputation: 49794

Option 1: cart.getCustomer().getBills().add(bill);

The main problem with this is that you're relying on the mutability of the collection getBills() returns. This is a bad idea as it breaks encapsulation, which is why usually an immutable copy/view of the internal collection is returned in getters. And at that point this solution simply won't work.

Option 2: cart.getCustomer().addBill(bill);

This assumes the knowledge that a cart will have a customer associated with it, and that customers can have bills added to them. There's nothing wrong with these assumptions but there's a third, implied assumption: Cart doesn't need to know when a customer is issued with a new bill. In fact Cart doesn't even have to exist.

Option 3: cart.addBillToCustomer(bill);

Continuing from the above, the semantics of the third call are very different. What this call says is: Customers can only ever be billed through Carts.

Conclusion

To sum it all up: option 1 is plain wrong but whether you go with option 2 or 3 depends on what you want your model to express and they represent an actual difference between the business logic.

Upvotes: 3

Jordi Castilla
Jordi Castilla

Reputation: 26991

Three are valid in Java, but best solution is cart.addBillToCustomer(bill); only IF you make the necessary validations in addBillToCustomer method.

Upvotes: 0

Related Questions