Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

SOLID

877 views

Published on

21.02.2013, DevClub

Published in: Technology
  • Be the first to comment

SOLID

  1. 1. SOLIDIn the heart of OO design
  2. 2. Who is on the scene Eduards Sizovs linkedin.com/in/eduardsi
  3. 3. Agenda• Introduction• SRP• OCP• LSP• ISP• DIP• Q&A
  4. 4. SOLID • SRP The Single Responsibility • OCP Principle • LSP The Open Closed Principle • ISP The Liskov Substitution Principle • DIP The Interface Segregation PrincipleRobert Martin The Dependency Inversion Principle
  5. 5. SRPThe Single Responsibility Principle
  6. 6. DefinitionClass should have only oneresponsibility and only one reason tochange Robert Martin http://www.butunclebob.com/ArticleS.UncleBob.PrinciplesOfO od
  7. 7. Rationale Just because you can, does not mean you should.We want systems of many small, simple,cohesive classes with clear purpose.
  8. 8. Milka cow violates SRP – it’s bothcow & advertisement.
  9. 9. In quest of responsibilityAsk yourself:• What concept does the class represent?• What does the class do?• What causes the class to change?• For existing classes: is class X responsible for doing Z?Answer must be one-liner.
  10. 10. Violation symptoms• Managers, Services, Facades…• Long lists of variables, dependencies, methods, arguments• Problematic unit-testing (e.g. when you need Spies)• Different levels of abstractions inside a class• A lot of details to tackle, implicitness
  11. 11. Overloaded responsibilitiesinterface JsonService { public class TaxCalculator { Json marshall(Object object); @Autowired Object unmarshall(Json json); private EmployeeRepository employeeRepository; void prettyPrint(); void validate(Json json, Wadl wadl); Tax calculate(Id employeeId) {} Employee employee = employeeRepository.findBy(employeeId); return calculate(employee); } }class FooValidator { class User { void validate(Foo foo) { public User(Registration registration) { if (foo.isSuitableForValidation()) { this.email = registration.getEmail(); // actual validation this.firstName = registration.getFirstName(); } this.lastName = registration.getLastName(); } }} public User(Invitation invitation) { … } }
  12. 12. OCPThe Open Closed Principle
  13. 13. DefinitionSoftware entities (classes, modules,functions etc.) should be open forextension, but closed for modification. Robert Martin http://www.objectmentor.com/resources/articles/ocp.pdf
  14. 14. RationaleBrain surgery is notnecessary when puttingon a hat.
  15. 15. Example – Interceptors [ 1 ]com.ocp.mvc; com.ocp.mvc.extensions;class InterceptorRegistry { public class SecurityInterceptor { @Autowired void intercept(WebRequest request) { ... } } private WebApplication webapp; public class CachingInterceptor { @Autowired void intercept(WebRequest request) { ... } private SecurityInterceptor securityInterceptor; } @Autowired private CachingInterceptor cachingInterceptor; @PostConstruct public void register() { webapp.registerInterceptor(securityInterceptor); webapp.registerInterceptor(cachingInterceptor); }} • InterceptorRegistry disrespects OCP • Implementations cannot be hidden
  16. 16. Example – Interceptors [ 2 ]com.ocp.mvc; com.ocp.mvc.extensions;class InterceptorRegistry { class SecurityInterceptor implements Interceptor { @Autowired void intercept(WebRequest request) { ... } } private WebApplication webapp; class CachingInterceptor implements Interceptor { @Autowired void intercept(WebRequest request) { ... } private Collection<Interceptor> interceptors; } @PostConstruct public void register() { webapp.registerInterceptors(interceptors); }}interface Interceptor { void intercept(WebRequest request);} • InterceptorRegistry respects OCP • Interceptors are hidden • Dependency direction has
  17. 17. Example – Interceptors [ 3 ] com.ocp.mvc; com.ocp.mvc.extensions; public class InterceptorRegistry { class SecurityInterceptor implements Interceptor { @Autowired @Autowired private InterceptorRegistry interceptorRegistry; private WebApplication webapp; @PostConstruct public void register(Interceptor interceptor) { void register() { webapp.registerInterceptor(interceptor); interceptorRegistry.register(this); } } } void intercept(WebRequest request) { ... } } interface Interceptor { void intercept(WebRequest request); class CachingInterceptor implements Interceptor { } @Autowired private InterceptorRegistry interceptorRegistry; @PostConstruct void register() { interceptorRegistry.register(this);• Interceptors violate SRP? }• InterceptorRegistry exposed void intercept(WebRequest request) { ... } }
  18. 18. Example – NotificationService [ 1]class NotificationService { class SmsNotifier { @Autowired void notify(Notification notification) { … } SmsNotifier smsNotifier; } @Autowired class EmailNotifier { EmailNotifier emailNotifier; notify(Notification notification) { … } } void notify(Notification notification) { if (notification.getType() == NotificationType.Sms) { smsNotifier.notify(notification); } if (notification.getType() == NotificationType.Email) { emailNotifier.notify(notification); } }}
  19. 19. Example – NotificationService [ 2]interface Notifier { class SmsNotifier implements Notifier { void notify(Notification notification) { … } void notify(Notification notification) { … } boolean isApplicableFor(NotificationType boolean isApplicableFor(NotificationTypenotificaionType); notificationType) {} return notification.getType() == NotificationType.Sms;class NotificationService { } @Autowired } Collection<Notifier> notifiers; class EmailNotifier implements Notifier { void notify(Notification notification) { notify(Notification notification) { … } Notifier notifier = findApplicableNotifier(notification); boolean isApplicableFor(NotificationType notifier.notify(notification); notificationType) { } return notification.getType() == NotificationType.Email; } private Notifier findApplicableNotifier(Notifier notifier) { } for (Notifier notifier : notifiers) { if (notifier.isApplicableFor(notification)) { returnnotifier; } } … }}
  20. 20. Example – Repositoryinterface EntityRepository { interface EntityRepository { interface By { Entity findByName(String); Entity find(By); } Entity findByPage(Page); } class ByName implements By { Entity findById(Id); public ByName(String name) { … } Entity findByVersion(Version); }} class ByPage implements By { public ByPage(Page page) { … } } class ById implements By { public ById(Id id) { … } } class ByVersion implements By { public ByVersion(Version version) { … } }
  21. 21. LSPThe Liskov Substitution Principle
  22. 22. DefinitionsThe LSP specifies that functions that usepointers of references to base classesmust be able to use objects of derivedclasses without knowing it. Robert Martin http://www.objectmentor.com/resources/articles/lsp.pdf
  23. 23. Syntactical conformance is not enough! It’s allabout semantics:• Pre-conditions (can’t strengthen, can weaken)• Post-conditions (can’t weaken, can strengthen)• Preserved supertype’s invariants
  24. 24. Strengthening pre-conditionsinterface FileReader { class OpenFileReader implements FileReader { @FileStateAgnostic public void read(File file) { void read(File file);} Preconditions.checkArgument(file.isOpen()); }class Client { } @Autowired void doIt(FileReader fileReader) { File file = getFile(); fileReader.read(file); }}
  25. 25. Weakening pre-conditionsclass FileReader { class SmartFileReader extends FileReader { @CheckIsOpen public void read(File file) { void read(File file) { … }Preconditions.checkArgument(file.isOpen()); } }}class Client { @Autowired void doIt(FileReader fileReader) { File file = getFileAndEnsureOpen(); fileReader.read(file); }}
  26. 26. Weakening post-conditionsinterface Collection { class Queue implements Collection { @DecreasesSizeOfCollectionBy(1) @RetainsItemIfQueueIsBusy void remove(Item item); public void remove(Item item) { … } boolean isEmpty(); public boolean isEmpty() { … }} }class Client { void cleanUp(Collection collection) { while(!collection.isEmpty()) {collection.remove(collection.anyItem()); } }}
  27. 27. Strengthening post-conditionsinterface Collection { class Queue implements Collection { @DecreasesSizeOfCollectionBy(1) @RemovesHead void removeItem(); public void removeItem() { … } boolean isEmpty(); public boolean isEmpty() { … }} }class Client { void cleanUp(Collection collection) { while(!collection.isEmpty()) { collection.removeItem(); } }}
  28. 28. Breaking class’ invariantsclass Money { class Euro extends Money { protected BigDecimal amount; // Does not defend invariants… public void setAmount(BigDecimal amount) { @NonNegative this.amount = amount; public BigDecimal getAmount() { } return amount; } }}
  29. 29. Communicating contract• Self-descriptive names (e.g. doIfSomething());• Meta-annotations • JSR 305 ( @Nullable @CheckForNull, @Immutable … ) • Guava ( @VisibleForTesting ) • Custom• Executable specifications • Unit tests + Story Teller (storyteller.socosomi.com)• JavaDocs• Assertions
  30. 30. Violation indicators• Modification of existing code on new subtype• Unnatural class hierarchies• Funny restrictions (do not use this class if…)• instanceof, if, switch …• InvalidOperationException() or similar
  31. 31. ISPThe Interface Segregation Principle
  32. 32. DefinitionsNo client should be forced to dependon methods it does not use.Many client specific interfaces arebetter than one general purposeinterface. Robert Martin Agile Software Development, 2002
  33. 33. Less FATFATinterfaces • Are not cohesive • Violate SRP • Force implementations to violate SRP • Suffer from high afferent/efferent coupling
  34. 34. Flexible layering interface RegistrationSettings { com.4finance.cola.client int getMaxAttempts(); } interface BonusSettings {com.4finance.cola.product com.4finance.cola.client.bonus BonusAmount getBonusAmount(); } interface SolvencySettings {interface ProductSettings { com.4finance.cola.client.solvency Income getMinIncomeForSubmission(); int getMaxRegistrationAttempts(); } BonusAmount getBonusAmount(); Income getMinIncomeForSubmission();} Consider • Impact of change • Dependency management
  35. 35. Extensibility interface JsonMarshaller { Json marshall(Object object);interface JsonService { } Json marshall(Object json); Object unmarshall(Json json); interface JsonUnmarshaller { ValidationResult validate(Json json, Wadl wadl); Object unmarshall(Json json); void prettyPrint(Json json); }} interface JsonValidator { ValidationResult validate(Json json, Wadl wadl); } Consider interface JsonPrettyPrinter { • Creating an adapter } void prettyPrint(Json json); • Creating a test double • Creating an alternative implementation • Creating Special Case (NoOp, Null Object)
  36. 36. Lower coupling interface JsonService { class JsonServiceImpl implements JsonService { Json marshall(Object json); … Object unmarshall(Json json); } ValidationResult validate(Json json, Wadl wadl); void prettyPrint(Json json); } Pretty printing Validation (Un)marshallingConsider• High afferent coupling: all clients relying on particular Json functionality are coupled with Wadl.• High efferent coupling: JsonService implementation will violate SRP as it relies on all dependencies required for fulfilling the contract.
  37. 37. Understandabilityinterface EventPublisher { interface EventPublisher { void publish(); void publish(); void setJsonService(JsonService void setJsonMarshaller(JsonMarshallerjsonService); jsonMarshaller);} }Consider • When JsonService is set, you need to dive into EventPublisher implementor’s details in order to understand what parts of it are in use.
  38. 38. Example – PageRouterinterface PageRouter { interface PageRouter{ Route getRouteToProfile(); Route getRoute(); Route getRouteToRegistration(); }} interface ProfilePageRouter extends PageRouter {class DefaultPageRouter implements PageRouter { } …} interface RegistrationPageRouter extends PageRounter { }class RuPageRouter { // RU overrides both routes class DefaultProfilePageRouter implements ProfilePageRounter} { } …Consider • What if alternative implementation appeared that overrides only one route?
  39. 39. DIPThe Dependency Inversion Principle
  40. 40. DefinitionHigh-level modules should not dependon low-level modules. Both shoulddepend on abstractions. Robert Martin http://www.objectmentor.com/resources/articles/dip.pdf
  41. 41. << interface >> AS ervice Component A Component A << interface >> BS vice er Component B Component B Component C Component CA directly depends on B in order to realize itspotential.A defines external needs with interfaces it owns.B indirectly satisfies needs of A by implementingthem.
  42. 42. Increasing reuse 1 2 3 Widgets Widgets Widgets DatePicker DatePicker DatePicker DateFormatterLoans System Loans SystemDateTimeUtils DateFormatter Loans System DateTimeUtils DateTimeUtils
  43. 43. DwA != DIIn true Dependency Inversion, high-level module owns the abstraction.
  44. 44. Tangle elimination
  45. 45. Tangle elimination com.isp.client com.isp.client Client Client com.isp.client.salary com.isp.client.salary Salary ExperienceSalary SalaryCalculator SalaryCalculator
  46. 46. Some codepackage com.system; package com.system;@Configuration interface ScanPackages {@ComponentScan(“com.system”) String[] get();class HibernateConfiguration { } @Autowired private Collection<ScanPackages> scanPackages; package com.system; @Component @Bean class DefaultScanPackages implements ScanPackages { public SessionFactory createInstance() { public String[] get() { return new LocalSessionFactoryBean() return new String[] { “com.system.model” }; .setPackagesToScan(scanPackages()) } .newSessionFactory(); } } private String[] scanPackages() { package com.system.subsystem // iterate over “scanPackages” @Component // and aggregate String[] array class SubsystemScanPackages implements ScanPackages { } public String[] get() {} return new String[] { “com.system.sub.model }; } }
  47. 47. You did it.
  48. 48. But… Why care?SOLID contribute to clean code. Writing cleancode is what you must do in order to callyourself a professional. There is noreasonable excuse for doing anything lessthan your best.
  49. 49. cv@4finance.l v
  50. 50. More
  51. 51. Q&A

×