SOLID
-
Upload
eduards-sizovs -
Category
Technology
-
view
411 -
download
3
description
Transcript of SOLID
SOLIDIn the heart of OO design
Who is on the scene
Eduards Sizovslinkedin.com/in/eduardsi
• Introduction• SRP• OCP• LSP• ISP• DIP• Q&A
Agenda
SOLID
Robert Martin
• SRP
• OCP
• LSP
• ISP
• DIP
The Single Responsibility
Principle
The Open Closed Principle
The Liskov Substitution
Principle
The Interface Segregation
Principle
The Dependency Inversion
Principle
SRPThe Single Responsibility
Principle
Definition
Class should have only one responsibility and only one reason to change
Robert Martinhttp://www.butunclebob.com/
ArticleS.UncleBob.PrinciplesOfOod
Rationale
Just because you can, does not mean you should.
We want systems of many small, simple, cohesive classes with clear purpose.
Milka cow violates SRP – it’s both cow & advertisement.
In quest of responsibility
Ask 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.
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
Overloaded responsibilitiesinterface JsonService { Json marshall(Object object); Object unmarshall(Json json); void prettyPrint(); void validate(Json json, Wadl wadl);}
public class TaxCalculator { @Autowired private EmployeeRepository employeeRepository;
Tax calculate(Id employeeId) { Employee employee = employeeRepository.findBy(employeeId); return calculate(employee); }}
class FooValidator { void validate(Foo foo) { if (foo.isSuitableForValidation()) { // actual validation } }}
class User { public User(Registration registration) { this.email = registration.getEmail(); this.firstName = registration.getFirstName(); this.lastName = registration.getLastName(); } public User(Invitation invitation) { … }}
OCPThe Open Closed Principle
Definition
Software entities (classes, modules, functions etc.) should be open for extension, but closed for modification.
Robert Martinhttp://www.objectmentor.com/resources/articles/
ocp.pdf
Rationale
Brain surgery is not necessary when putting on a hat.
Example – Interceptors [ 1 ]com.ocp.mvc; class InterceptorRegistry { @Autowired private WebApplication webapp;
@Autowired private SecurityInterceptor securityInterceptor;
@Autowired private CachingInterceptor cachingInterceptor;
@PostConstruct public void register() { webapp.registerInterceptor(securityInterceptor); webapp.registerInterceptor(cachingInterceptor); }}
com.ocp.mvc.extensions; public class SecurityInterceptor { void intercept(WebRequest request) { ... } }
public class CachingInterceptor { void intercept(WebRequest request) { ... } }
• InterceptorRegistry disrespects OCP
• Implementations cannot be hidden
Example – Interceptors [ 2 ]com.ocp.mvc; class InterceptorRegistry { @Autowired private WebApplication webapp;
@Autowired private Collection<Interceptor> interceptors;
@PostConstruct public void register() { webapp.registerInterceptors(interceptors); }}
interface Interceptor { void intercept(WebRequest request); }
com.ocp.mvc.extensions; class SecurityInterceptor implements Interceptor { void intercept(WebRequest request) { ... } }
class CachingInterceptor implements Interceptor { void intercept(WebRequest request) { ... } }
• InterceptorRegistry respects OCP
• Interceptors are hidden• Dependency direction has
changed
Example – Interceptors [ 3 ]com.ocp.mvc; public class InterceptorRegistry { @Autowired private WebApplication webapp;
public void register(Interceptor interceptor) { webapp.registerInterceptor(interceptor); }}
interface Interceptor { void intercept(WebRequest request); }
com.ocp.mvc.extensions; class SecurityInterceptor implements Interceptor { @Autowired private InterceptorRegistry interceptorRegistry; @PostConstruct void register() { interceptorRegistry.register(this); }
void intercept(WebRequest request) { ... }}
class CachingInterceptor implements Interceptor { @Autowired private InterceptorRegistry interceptorRegistry; @PostConstruct void register() { interceptorRegistry.register(this); }
void intercept(WebRequest request) { ... } }
• Interceptors violate SRP?• InterceptorRegistry exposed
Example – NotificationService [ 1 ]
class NotificationService { @Autowired SmsNotifier smsNotifier;
@Autowired EmailNotifier emailNotifier;
void notify(Notification notification) { if (notification.getType() == NotificationType.Sms) { smsNotifier.notify(notification); } if (notification.getType() == NotificationType.Email) { emailNotifier.notify(notification); } }}
class SmsNotifier { void notify(Notification notification) { … } }
class EmailNotifier { notify(Notification notification) { … } }
Example – NotificationService [ 2 ]
interface Notifier { void notify(Notification notification) { … } boolean isApplicableFor(NotificationType notificaionType);}
class NotificationService { @Autowired Collection<Notifier> notifiers;
void notify(Notification notification) { Notifier notifier = findApplicableNotifier(notification); notifier.notify(notification); }
private Notifier findApplicableNotifier(Notifier notifier) { for (Notifier notifier : notifiers) { if (notifier.isApplicableFor(notification)) { return notifier; } } … }}
class SmsNotifier implements Notifier { void notify(Notification notification) { … } boolean isApplicableFor(NotificationType notificationType) { return notification.getType() == NotificationType.Sms; }}
class EmailNotifier implements Notifier { notify(Notification notification) { … } boolean isApplicableFor(NotificationType notificationType) { return notification.getType() == NotificationType.Email; }}
Example – Repositoryinterface EntityRepository { Entity findByName(String); Entity findByPage(Page); Entity findById(Id); Entity findByVersion(Version);}
interface EntityRepository { Entity find(By);}
interface By {}
class ByName implements By { public ByName(String name) { … }}
class ByPage implements By { public ByPage(Page page) { … }}
class ById implements By { public ById(Id id) { … }}
class ByVersion implements By { public ByVersion(Version version) { … }}
LSPThe Liskov Substitution
Principle
Definitions
The LSP specifies that functions that use pointers of references to base classes must be able to use objects of derived classes without knowing it.
Robert Martinhttp://www.objectmentor.com/resources/articles/
lsp.pdf
Syntactical conformance is not enough! It’s all about semantics:
• Pre-conditions (can’t strengthen, can weaken)
• Post-conditions (can’t weaken, can strengthen)
• Preserved supertype’s invariants
Strengthening pre-conditions
interface FileReader { @FileStateAgnostic void read(File file);}
class Client { @Autowired void doIt(FileReader fileReader) { File file = getFile(); fileReader.read(file); }}
class OpenFileReader implements FileReader { public void read(File file) { Preconditions.checkArgument(file.isOpen()); }}
Weakening pre-conditionsclass FileReader { @CheckIsOpen void read(File file) { Preconditions.checkArgument(file.isOpen()); }}
class Client { @Autowired void doIt(FileReader fileReader) { File file = getFileAndEnsureOpen(); fileReader.read(file); }}
class SmartFileReader extends FileReader { public void read(File file) { … }}
Weakening post-conditionsinterface Collection { @DecreasesSizeOfCollectionBy(1) void remove(Item item); boolean isEmpty();}
class Client { void cleanUp(Collection collection) { while(!collection.isEmpty()) { collection.remove(collection.anyItem()); } }}
class Queue implements Collection { @RetainsItemIfQueueIsBusy public void remove(Item item) { … } public boolean isEmpty() { … }}
Strengthening post-conditions
interface Collection { @DecreasesSizeOfCollectionBy(1) void removeItem(); boolean isEmpty();}
class Client { void cleanUp(Collection collection) { while(!collection.isEmpty()) { collection.removeItem(); } }}
class Queue implements Collection { @RemovesHead public void removeItem() { … } public boolean isEmpty() { … }}
Breaking class’ invariantsclass Money { protected BigDecimal amount;
@NonNegative public BigDecimal getAmount() { return amount; }}
class Euro extends Money { // Does not defend invariants… public void setAmount(BigDecimal amount) { this.amount = amount; }}
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
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
ISPThe Interface Segregation
Principle
Many client specific interfaces are better than one general purpose interface.
Definitions
No client should be forced to depend on methods it does not use.
Robert MartinAgile Software Development, 2002
• Are not cohesive• Violate SRP• Force implementations to violate SRP• Suffer from high afferent/efferent coupling
Less FAT
FAT interfaces
Flexible layering
• Impact of change• Dependency
management
Consider
interface ProductSettings { int getMaxRegistrationAttempts(); BonusAmount getBonusAmount(); Income getMinIncomeForSubmission();}
interface RegistrationSettings { int getMaxAttempts();}
interface BonusSettings { BonusAmount getBonusAmount();}
interface SolvencySettings { Income getMinIncomeForSubmission();}
Extensibility
• Creating an adapter• Creating a test double• Creating an alternative implementation• Creating Special Case (NoOp, Null Object)
Consider
interface JsonService { Json marshall(Object json); Object unmarshall(Json json); ValidationResult validate(Json json, Wadl wadl); void prettyPrint(Json json);}
interface JsonMarshaller { Json marshall(Object object);}
interface JsonUnmarshaller { Object unmarshall(Json json);}
interface JsonValidator { ValidationResult validate(Json json, Wadl wadl);}
interface JsonPrettyPrinter { void prettyPrint(Json json);}
Lower coupling
• 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.
Consider
interface JsonService { Json marshall(Object json); Object unmarshall(Json json); ValidationResult validate(Json json, Wadl wadl); void prettyPrint(Json json);}
class JsonServiceImpl implements JsonService {…
}
Understandability
• When JsonService is set, you need to dive into EventPublisher implementor’s details in order to understand what parts of it are in use.
Consider
interface EventPublisher {void publish();void setJsonService(JsonService
jsonService);}
interface EventPublisher {void publish();void setJsonMarshaller(JsonMarshaller
jsonMarshaller);}
Example – PageRouter
• What if alternative implementation appeared that overrides only one route?
Consider
interface PageRouter {Route getRouteToProfile();Route getRouteToRegistration();
}
class DefaultPageRouter implements PageRouter {
…}
class RuPageRouter {// RU overrides both routes
}
interface PageRouter{Route getRoute();
}
interface ProfilePageRouter extends PageRouter {}
interface RegistrationPageRouter extends PageRounter {}
class DefaultProfilePageRouter implements ProfilePageRounter {}…
DIPThe Dependency Inversion
Principle
Definition
High-level modules should not depend on low-level modules. Both should depend on abstractions.
Robert Martinhttp://www.objectmentor.com/resources/articles/
dip.pdf
A directly depends on B in order to realize its potential.
A defines external needs with interfaces it owns. B indirectly satisfies needs of A by implementing them.
Increasing reuse
DwA != DI
In true Dependency Inversion, high-level module owns the abstraction.
Tangle elimination
Tangle elimination
Some codepackage com.system;@Configuration@ComponentScan(“com.system”)class HibernateConfiguration { @Autowired private Collection<ScanPackages> scanPackages;
@Bean public SessionFactory createInstance() { return new LocalSessionFactoryBean() .setPackagesToScan(scanPackages()) .newSessionFactory(); }
private String[] scanPackages() { // iterate over “scanPackages” // and aggregate String[] array }}
package com.system;interface ScanPackages { String[] get();}
package com.system;@Componentclass DefaultScanPackages implements ScanPackages { public String[] get() { return new String[] { “com.system.model” }; }}
package com.system.subsystem@Componentclass SubsystemScanPackages implements ScanPackages { public String[] get() { return new String[] { “com.system.sub.model }; }}
You did it.
But… Why care?
SOLID contribute to clean code. Writing clean code is what you must do in order to call yourself a professional. There is no reasonable excuse for doing anything less than your best.
cv@4fi nance.lv
More
Q&A