law of demeter & objective sense of style
DESCRIPTION
Short introduction into Law of Demeter with examples of satisfaction and violation that demonstrates benefits of following the style as well as potential complexities. Work includes references to empirical validation of OO metrics including violations of Law of DemeterTRANSCRIPT
The Law Of Demeter& objective sense of style
vladimir tsukur partner @
team &tech lead @
Vladimir Tsukur / FOLLOW ME: twitter.com/flushdia or EMAIL TO: [email protected]
Example:B!" #$%&'( $ (&r)
Example: B!" #$%&'( $ (&r)class Girl {
private List<GirlRoutine> schedule = new ArrayList<>();
List<GirlRoutine> getSchedule() {
return schedule;
}
}
class GirlRoutine {
private String description;
private Date startTime;
private Date endTime;
boolean happensAt(Date time) {
return time.after(startTime) && time.before(endTime);
}
}
Law of Demeter
Example: B!" #$%&'( $ (&r)class Boy {
private SoccerGame soccerGame;
boolean arrangeDate(Girl girl, Date time) {
if (soccerGame.at(time))) return false;
boolean success = true;
for (GirlRoutine routine : girl.getSchedule()) {
if (routine.happensAt(time)) {
success = false;
break; // my heart :(
}
}
return success;
}
}
Law of Demeter
class Boy {
private SoccerGame soccerGame;
boolean arrangeDate(Girl girl, Date time) {
if (soccerGame.at(time))) return false;
boolean success = true;
for (GirlRoutine routine : girl.getSchedule()) {
if (routine.happensAt(time)) {
success = false;
break; // my heart :(
}
}
return success;
}
}
Law of Demeter
WHY IS THIS BAD?
WHY IS THIS BAD?class Boy {
...
boolean arrangeDate(Girl girl, Date time) {
...
...
for (GirlRoutine routine : girl.getSchedule()) {
if (routine.happensAt(time)) {
...
...
}
}
...
}
}
Law of Demeter
W*" %*+ *+)) ,*!-)# *+ )!!. $% *+r ,/*+#-)+???
WHY IS THIS BAD?class DecisionMaker {
...
boolean decide(Container container, Object param) {
...
...
for (Item item : container.getItems()) {
if (item.checkSomething(param)) {
...
...
}
}
...
}
}
Law of Demeter
Privacy!
technically• Boy will require Girl and GirlRoutine
during compilation• Changes in GirlRoutine will affect Boy
and Girl=> Boy, Girl and GirlRoutine are tightly coupled(structural coupling)
Law of Demeter
Questions
• What if the list of routines is null?• What if decision is not going to
depend on routines at all?• What if mother or father would like to
check if their daughter is free?
Law of Demeter
Law of Demeter
Better Boy
class Boy {
private SoccerGame soccerGame;
boolean tryArrangeDate(Girl girl, Date time) {
return !soccerGame.at(time) && girl.freeAt(time);
}
}
Better Girlclass Girl {
private List<GirlRoutine> schedule = new ArrayList<>();
boolean freeAt(Date time) {
boolean free = true;
for (GirlRoutine routine : schedule) {
if (routine.happensAt(time)) {
free = false;
break;
}
}
return free;
}
}
Law of Demeter
benefits
• Better models real-world scenario• GirlRoutine may change not affecting
Boy in any way• Implementation of freeAt() method
may now change easily
Law of Demeter
law of
Law of Demeter
Ian Holland1987
• oop design style• aka principle of least knowledge• specific case of loose coupling• Introduced by:
demeter
Law of Demeter
goddess of the harvest
« grow softwarein small steps »
only talk to your
class Boy {
private SoccerGame soccerGame;
boolean arrangeDate(Girl girl, Date time) {
if (soccerGame.at(time))) return false;
boolean success = true;
for (GirlRoutine routine : girl.getSchedule()) {
if (routine.happensAt(time)) {
success = false;
break; // my heart :(
}
}
return success;
}
}
Law of Demeter
law of demeter
lod-f formally
• O itself
• m's parameters
• Any objects created within m
• O's direct component objects
Law of Demeter
Method m of an object O may only invoke the methods of the following kinds of objects:
Law of Demeter
self: ALLOWEDclass Boy {
private SoccerGame soccerGame;
boolean arrangeDate(Girl girl, Date time) {
return freeAt(time) && girl.freeAt(time);
}
boolean freeAt(Date time) {
return !soccerGame.at(time);
}
}
Law of Demeter
fields: ALLOWEDclass Boy {
private SoccerGame soccerGame;
boolean arrangeDate(Girl girl, Date time) {
return freeAt(time) && girl.freeAt(time);
}
boolean freeAt(Date time) {
return !soccerGame.at(time);
}
}
Law of Demeter
parameters: ALLOWEDclass Boy {
private SoccerGame soccerGame;
boolean arrangeDate(Girl girl, Date time) {
return freeAt(time) && girl.freeAt(time);
}
boolean freeAt(Date time) {
return !soccerGame.at(time);
}
}
class Girl {
boolean freeAt(Date time) {
return new Random().nextBoolean();
}
}
Law of Demeter
new objects: ALLOWED
class Girl {
private static final Girl BEST_FRIEND = ...;
boolean freeAt(Date time) {
return BEST_FRIEND.freeAt(time);
}
}
Law of Demeter
global context: ALLOWED
class Seller {
void sell(Client client, Product product) {
Wallet wallet = client.getWallet();
if (wallet.getMoney() > product.getPrice()) {
wallet.setMoney(wallet.getMoney() - product.getPrice());
}
else {
throw new NotEnoughMoneyException();
}
}
}
Law of Demeter
LOD: VIOLATION
getThis().getThat(). getSomethingElse(). doTheWork();
Law of Demeter
LOD: VIOLATION
immediate friends only!
Law of Demeter
B A C
positive implications
• Simplifies modifications• Simplifies complexity of programming
Law of Demeter
positive implications• Less dependencies, loose coupling =>• Better maintainability• Better reuse• Less bugs
Law of Demeter
LOD &unit tests
class PageSecurityService {
PageSecurityService(SecurityContext securityContext) { ... }
boolean checkAccess(User user, Page page) {
return !securityContext.getGlobalLock().isEnabled() &&
securityContext.getApplicationContext().
getSecurityDao().userHasPermission(user, page);
}
}
Law of Demeter
page security service
@Test
public void user_should_have_access_to_an_open_page() {
User user = new User("John");
Page page = new Page("/john/hangouts");
/* Prepare System Under Test (SUT). */
PageSecurityService sut = ...;
assertThat(sut.checkAccess(user, page), is(true));
}
Law of Demeter
unit test (Shell)
...
GlobalLock globalLock = mock(GlobalLock.class);
when(globalLock.isEnabled()).thenReturn(false);
SecurityDao securityDao = mock(SecurityDao.class);
when(securityDao.userHasPermission(user, page)).thenReturn(true);
ApplicationContext applicationContext = mock(ApplicationContext.class);
when(applicationContext.getSecurityDao()).thenReturn(securityDao);
SecurityContext securityContext = mock(SecurityContext.class);
when(securityContext.getGlobalLock()).thenReturn(globalLock);
when(securityContext.getApplicationContext()).thenReturn(applicationContext);
PageSecurityService sut = new PageSecurityService(securityContext);
...
Law of Demeter
unit test (SUT setup)
thanks no!
class PageSecurityService {
private final SecurityDao securityDao;
private final GlobalLock globalLock;
PageSecurityService(SecurityContext securityContext) {
securityDao = securityContext.getAppContext().getSecurityDao();
globalLock = securityContext.getGlobalLock();
}
...
boolean checkNonAuthenticatedAccess(Page page) {
return !globalLock.isEnabled() && page.isPublic();
}
}
Law of Demeter
put it to constructor?
even worse
class PageSecurityService {
PageSecurityService(GlobalLock aGlobalLock,
SecurityDao aSecurityDao) {
this.globalLock = aGlobalLock;
this.securityDao = aSecurityDao;
}
boolean hasAccessTo(User user, Page page) {
return !globalLock.isEnabled() &&
securityDao.userHasPermission(user, page);
}
}
Law of Demeter
better service
...
/* Prepare SecurityContext. */
GlobalLock globalLock = mock(GlobalLock.class);
when(globalLock.isEnabled()).thenReturn(false);
SecurityDao securityDao = mock(SecurityDao.class);
when(securityDao.userHasPermission(user, page)).thenReturn(true);
PageSecurityService sut =
new PageSecurityService(globalLock, securityDao);
...
Law of Demeter
unit test (SUT setup)
«shy» codeis beneficial
Law of Demeter
class PageSecurityService {
PageSecurityService(SecurityContext securityContext) { ... }
boolean hasAccessTo(User user, Page page) {
return !securityContext.getGlobalLock().isEnabled() &&
securityContext.getApplicationContext().
getSecurityDao().userHasPermission(user, page);
}
}
Law of Demeter
response for class
RFC = 7
violates lod
class PageSecurityService {
PageSecurityService(GlobalLock globalLock,
SecurityDao securityDao) { ... }
boolean hasAccessTo(User user, Page page) {
return !globalLock.isEnabled() &&
securityDao.userHasPermission(user, page);
}
}
Law of Demeter
response for class
RFC = 4
Satisfies lod
LAW OF DEMETER=> (usually) Lower RFC
response for classS!"#$
WHAT?Number of distinct methods and constructors invoked by a class
why bother?The larger the RFC, the larger the probability of fault detection
0%
3%
6%
9%
12%
All New Ext DB UI
Law of Demeter
∆ψ[1]
[1] Basili, Victor; Briand, L.; Melo, W. L. (1996-10). http://www.cs.umd.edu/~basili/publications/journals/J62.pdf
class RedirectService {
void service(HttpServletRequest request,
HttpServletResponse response) throws IOException {
if («GET».equals(request.getMethod())) {
String uri = String.format("http://to.com%s?sessionId=%s",
request.getRequestURI(),
request.getSession(true).getId());
response.sendRedirect(uri);
}
}
}
Law of Demeter
weighted methods per class
WMC = 2
violates lod
class RedirectService {
void service(HttpServletRequest request,
HttpServletResponse response) throws IOException {
if («GET».equals(request.getMethod())) {
String uri = String.format("http://to.com%s?sessionId=%s",
request.getRequestURI(),
getSessionId(request.getSession()));
response.sendRedirect(uri);
}
}
private String getSessionId(HttpSession session) {
return session.getId();
}
}
Law of Demeter
weighted methods per class
WMC = 3
Satisfies lod
weighted methods per classS!"#$
WHAT?The sum of the complexities of all class methods
why bother?The larger the WMC, the larger the probability of fault detection
0%
3%
6%
9%
12%
All New Ext DB UI
Law of Demeter
∆ψ[1]
[1] Basili, Victor; Briand, L.; Melo, W. L. (1996-10). http://www.cs.umd.edu/~basili/publications/journals/J62.pdf
law of demeter
Lower RFC> (is more important than)
Higher WMCLaw of Demeter
0%
3%
6%
9%
12%
All New Ext DB UI
0%
3%
6%
9%
12%
All New Ext DB UI
Law of Demeter
bugs-LOD violations correlation [1]
Title LOC SVLoD WVLoD
eclipse.jdt.core 0.76 0.67 0.59
eclipse.pde.core 0.64 0.61 0.61
eclipse.jface 0.82 0.73 0.67
eclipse.compare 0.62 0.43 0.42
eclipse.debug.core 0.72 0.7 0.62
Law of Demeter[1] Yi guo, michael wursch, emanuel giger, harald c. gall, An Empirical Validation of the Benefits of
Adhering to the Law of Demeter (2011). http://www.ccs.neu.edu/home/lieber/LoD/LoD-2011-Zurich.pdf
S!"#$
person.toString().toUpperCase()
Law of Demeter
violates lod but looks ok!
List<Number> collection = ...;
int value = collection.get(0).intValue();
Law of Demeter
violates lod but looks ok!
CustomerDTO customer = ...;
customer.getAddressDTO().getCountryDTO();
Law of Demeter
violates lod but looks ok!
Column column = builder.createColumn().
withId(«res»).
withName(«Resolution»).
build();
Law of Demeter
violates lod but looks ok!
Law of Demeter
use only one dot!
object.method()
BUT ...
column. setId(«res»). setName(«Resolution»);
Law of Demeter
looks ok!
follows lod-F but still SO-Soclass Boy {
private SoccerGame soccerGame;
boolean arrangeDate(Girl girl, Date time) {
return !soccerGame.at(time) && girlIsFree(girl.getSchedule(), time);
}
private boolean girlIsFree(List<GirlRoutine> schedule, Date time) {
for (GirlRoutine routine : schedule) {
if (routineAt(routine, time)) return false;
}
return true;
}
private boolean routineAt(GirlRoutine routine, Date time) {
return routine.at(time);
}
}
Law of Demeter
law of
Law of Demeter
1. DRY2. min method arguments 3. min methods per class
+good style =
Law of Demeter
tooling
5.0 +
Questions?
At first sightthe idea of any rules or principles
being superimposed on the creative mindseems more likely to hinder than to help, but
this is really quite untrue in practice.
Disciplined thinking focuses inspiratioNrather than blinkers it[1]
Law of Demeter [1] Gordon l. glegg. "the design of design". cambridge university press. 1969