Reputation: 5931
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
Reputation: 14560
Law of Demeter (LoD):
A method f of a class C should only call the methods of these:
- C
- An object created by f
- An object passed as an argument to f
- 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 {}
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:
2. Is it example of demeter's law violation?
Cart
) passed as an argument to fFor 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
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
Reputation: 49794
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.
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.
cart.addBillToCustomer(bill);
Continuing from the above, the semantics of the third call are very different. What this call says is: Customer
s can only ever be billed through Cart
s.
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
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