Transcript

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

top related