+ All Categories

SOLID

Date post: 15-Jan-2015
Category:
Upload: eduards-sizovs
View: 411 times
Download: 3 times
Share this document with a friend
Description:
21.02.2013, DevClub
Popular Tags:
52
SOLID In the heart of OO design
Transcript
Page 1: SOLID

SOLIDIn the heart of OO design

Page 2: SOLID

Who is on the scene

Eduards Sizovslinkedin.com/in/eduardsi

Page 3: SOLID

• Introduction• SRP• OCP• LSP• ISP• DIP• Q&A

Agenda

Page 4: SOLID

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

Page 5: SOLID

SRPThe Single Responsibility

Principle

Page 6: SOLID

Definition

Class should have only one responsibility and only one reason to change

Robert Martinhttp://www.butunclebob.com/

ArticleS.UncleBob.PrinciplesOfOod

Page 7: SOLID

Rationale

Just because you can, does not mean you should.

We want systems of many small, simple, cohesive classes with clear purpose.

Page 8: SOLID

Milka cow violates SRP – it’s both cow & advertisement.

Page 9: SOLID

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.

Page 10: SOLID

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

Page 11: SOLID

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) { … }}

Page 12: SOLID

OCPThe Open Closed Principle

Page 13: SOLID

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

Page 14: SOLID

Rationale

Brain surgery is not necessary when putting on a hat.

Page 15: SOLID

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

Page 16: SOLID

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

Page 17: SOLID

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

Page 18: SOLID

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) { … } }

Page 19: SOLID

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; }}

Page 20: SOLID

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) { … }}

Page 21: SOLID

LSPThe Liskov Substitution

Principle

Page 22: SOLID

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

Page 23: SOLID

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

Page 24: SOLID

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()); }}

Page 25: SOLID

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) { … }}

Page 26: SOLID

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() { … }}

Page 27: SOLID

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() { … }}

Page 28: SOLID

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; }}

Page 29: SOLID

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

Page 30: SOLID

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

Page 31: SOLID

ISPThe Interface Segregation

Principle

Page 32: SOLID

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

Page 33: SOLID

• Are not cohesive• Violate SRP• Force implementations to violate SRP• Suffer from high afferent/efferent coupling

Less FAT

FAT interfaces

Page 34: SOLID

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();}

Page 35: SOLID

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);}

Page 36: SOLID

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 {…

}

Page 37: SOLID

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);}

Page 38: SOLID

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 {}…

Page 39: SOLID

DIPThe Dependency Inversion

Principle

Page 40: SOLID

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

Page 41: SOLID

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.

Page 42: SOLID

Increasing reuse

Page 43: SOLID

DwA != DI

In true Dependency Inversion, high-level module owns the abstraction.

Page 44: SOLID

Tangle elimination

Page 45: SOLID

Tangle elimination

Page 46: SOLID

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 }; }}

Page 47: SOLID

You did it.

Page 48: SOLID

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.

Page 49: SOLID
Page 50: SOLID

cv@4fi nance.lv

Page 51: SOLID

More

Page 52: SOLID

Q&A


Recommended