Getters/Setters Revisited - Validation of Order Class
This is a continuation of the post devoted to some code smells that was vigorously commented in favour of ugly, anti-OO code design :). Thanks God, I also got an interesting question regarding this problem.
Seems to me, that this topic should be discussed one more time.
Introduction
Let me remind you what the problem is.
The original code (in some class, other than Order
class) looks like this:
String custID = order.getCustomerID();
String itemID = order.getItemID();
int qty = order.getQty();
double price = order.getPrice();
if (custID != null && itemID != null
&& !custID.equals("")
&& !itemID.equals("") && qty > 0
&& price > 0.0) {
return "ORD1234";
}
This is definitely not OO, and against the famous Tell don't ask principle.
The code is procedural, which means, that other objects take values from Order
, examine them, and make judgements based on thems. All getters are now part of the Order
API, which makes any changes of Order
class troublesome.
A more OO approach is to hide the validation logic inside Order
class:
if (order.isValid())
return "ORD1234";
}
And the Question Is ...
Surprisingly I got few comments in favour of "getters" approach ((btw. all comments so far were anonymous).
One of the readers asked an interesting question:
Maybe different clients of the class have different
validation requirements. Ever considered that?
That is a good question. What if for different clients using Order
class "a valid order" means different things ? Let us see.
I think that there are two possible situations:
- you know what kinds of validation will be required while writing
Order
class; - you expect that various kinds of validation will be required, but you don't know what kinds exactly.
Let us take a closer look at each of them.
List of Required Validations is Known
In such (unrealistic) case, you simply add as many validation methods as required. You end up with something like this:
public class Order {
// a lot of fields and methods that we don't care about at the moment
....
// here comes validation methods
public boolean isValidA {
...
}
public boolean isValidB {
...
}
... /// more of validation methods
}
Of course, you would choose more meaningful methods names!
This case is unrealistic, let us move on.
Allow Client Do Whatever Validation They Wish But Protect Your Code
This time we want to allow clients to provide their own validation methods and at the same time, we refuse them access to the internal structure of our Order
class. Is it possible? Yes.
First, create an interface for order validators:
public interface OrderValidator {
void setCustomerId(String customerId);
void setItemId(String itemId);
// more similar methods here
// and now the validation method
boolean validate();
}
Please notice, that the setters do not have to inject all the fields of Order
class. They should allow validators to acquire all information that is important for validation - and nothing more.
The Order
class should provide a method, that accepts implementations of OrderValidator
interface:
public class Order {
// a lot of fields and methods that we don't care about at the moment
....
public boolean isValid(OrderValidator validator) {
validator.setCustomerId(customerId);
validator.setItemId(itemId);
// more setting of values here
...
// at this point the validator has all the data
// and can do its job
return validator.validate();
}
}
Now the clients can come up with whatever fancy implementation of OrderValidator
they wish, and you are even able to perform some manipulations on the the internals of Order
class without breaking the clients. Sweet.
And that would be it. The question is answered. ...but I feel that you would like to know why do I expect you to trouble yourself with additional interfaces and classes, wouldn't you ? Keep on reading then.
But What About Tests ?
Let us say we have another class - OrderService
- that rejects orders that do not validate, and saves only valid ones - sth like this:
public void saveOrder(Order order) {
if (// TODO check that order is valid) {
orderDAO.saveOrder();
} else {
throw new SomeMeaningfulBusinessException(...);
}
}
Now, you want to test the behaviour of this method of OrderService
. In particular you want to know that:
saveOrder
method ofOrderDAO
is executed only on valid order;- in case of order that is not valid, a proper exception is thrown.
Please notice that for the sake of simplicity a pseudocode will be used in the examples of tests.
Testing - OO approach
The tests are straightforward:
public void validOrderIsSaved() {
expect(mockOrder.isValid()).andReturn(true);
expect(mockOrderDAO.saveOrder(order))
replay(mockOrder,mockOrderDAO);
orderService.saveOrder(mockOrder);
verify(mockOrder, mockOrderDAO);
}
@Test(ExpectedExceptions = SomeMeaningfulBusinessException.class)
public void forNotValidOrderAnExceptionIsThrownAndOrderIsNotSaved() {
expect(mockOrder.isValid()).andReturn(false);
replay(mockOrder, mockOrderDAO);
// probably try/catch needed here, so the verification
// of mockOrderDAO is executed, but let us skip it
orderService.saveOrder(mockOrder);
verify(mockOrder, mockOrderDAO);
}
Testing - "getters" approach
In case of using "getter" approach you need to know a lot about the validation of an order - you need to know the algorithm of validation, so you can return the proper values to receive true
and false
respectively.
public void validOrderIsSaved() {
// doesn't matter if I use a mock or a real Order object
order.setCustomerId("123");
order.setItemID("456");
order.setQty(111);
order.setPrice(2.3);
expect(mockOrderDAO.saveOrder(order))
replay(mockOrderDAO);
orderService.saveOrder(order);
verify(mockOrderDAO);
}
@Test(ExpectedExceptions = SomeMeaningfulBusinessException.class)
public void forNotValidOrderAnExceptionIsThrownAndOrderIsNotSaved() {
// doesn't matter if I use a mock or a real Order object
order.setCustomerId("123");
order.setItemID("456");
order.setQty(111);
order.setPrice(-7.8);
replay(mockOrderDAO);
// probably try/catch needed here, so the verification
// of mockOrderDAO is executed, but let us skip it
orderService.saveOrder(order);
verify(mockOrderDAO);
}
The tests are a little bit longer, but this is not the problem here. What will really hit you hard during further development is that many of your classes are tightly coupled with Order
class and its validation logic.
Conclusion
The code speaks for itself, but let me summarise what we have just learned.
getters/setters | OO approach | |
---|---|---|
initial effort | limited | more work required (additional interface and implementation class) |
writing more validators | validation logic is spread across many classes. No code reuse possible. | validation logic is kept inside implementations of OrderValidator interface. It is possible to reuse these classes. |
Single Responsibility Principle | broken; OrderService is responsible for its own business logic and for the validation of Order class |
every class has a single responsibility:
|
impact of changes of Order class on clients code |
significant - OrderService breaks if Orders API changes |
limited - only implementations of OrderValidator must be updated. In case of minor changes of Order class it is even possible to introduce changes only to validate method of Order class. |
testing of OrderService |
test needs to repeat the validation logic | simple; no knowledge of validation logic required |
impact of changes of Order class on tests of clients |
tests need to be changed if Order class changes | tests are not fragile; no rippling effect |
The conclusions are rather obvious:
- If you think in long term, there is no way you will use get/set approach.
- If you hope for a short living relationship with your code, you might be tempted to use the "get/set" approach. As your relationship lasts longer (than expected) you will regret your decision.
BTW. Have you noticed, that you will never get into "get/set" horror design if you really, honestly, passionately test your code ? (Especially if you do it test-first).
The Dark Side is Very Strong
P.S. Picture of Yoda was taken from druffmix.com site.
This used to be my blog. I moved to http://tomek.kaczanowscy.pl long time ago.
Validation is a part of some specific case and model is general.
Very bad approach is to mix the "description of structures" (bean/entities) with a very specific to the case implementation.
CORBA and EJB 1-2.1 Entity Beans showed us how bad it was.
Session Beans, Web Services and SOA are examples of old good structural approach - not object oriented one.
In large systems, domain model is generated from from UML, and need to regenerated very often - where is a place for validations function, how to preserve the from be overwritten?
Model might be generic, and very elastic, with reuse and inheritance of entities like Address, Person, etc. What if domain model (set of bean/entity classes, of course) is common and critical resource for the large system, and different teams reuse this holly model - should every developer be free to rubbish entities of the model with some very specific validation?
More, ORM and relational to object mapping allows for multiple inheritance in database, and domain model might be not very clear, with "repeats". Where to put validation function in that case?
Get/setters are the most
Get/setters are the most embarrassing part of all classes. Bad, anti-OO part of code. However many programmers think this is "true and valid" encapsulation that provide "true and valid" object oriented applications. In the other hand in many courses, books etc. you could read that getters/setters are icon of encapsulation.
In my opinion validators as you present in this post are good thing that should be solution of getters/setters hell.
Koziołek
http://koziolekweb.pl
Good post, but as
Good post, but as OO-evangelist I must disagree in general concept:P
Solution has a leaky abstraction problem: If you change inner impl of Order class than You have to change Validator interface - here is where abstraction leaks. Of course we can assume that Validator is kind of Visitor Patterns and live with this.
But don't get me wrong... I thing that Your solution is best of all possible. But if it still has a problem than i suspect that we are fighting WRONG PROBLEM here.
Your approach solves problem defined like this "there are different validation rules in system". Problem is defined probably by someone who does not think in OO, so it forces You to solve wrong thing:)
We must consider if this kind of problem is not a symptom of wrong architecture. I assume that if there different validation rules than we have different domains. (Bounded context in the meaning of of Eric Evans' DDD). If so, than need to model them using specific Order Classes - each specific for context.
My assumptions may be wrong, and someone can present specific counter-example. But as we discuss on general level it is worth to mention typical antipattern: common_enterprise_monter_anemic_model. It looks something like that: we have multiple modules (ex invoices, ordering, shipping, etc) covered with one common (and meaningless) anemic model.
According to getetrs/setetrs. It is nothing wrong if class is meant to to play role of stupid data transfer objects. For example as data-packet to communicate between bounded context. Ok, let i be, but where is the real model? Problem begins when official platform promotes this role (dto) as domain model:)
Sławek
http://art-of-software.blogspot.com