rupicon 2014 solid

46
SOLID OO Design Tudor Pavel 1 / 46

Upload: rupicon

Post on 30-Jun-2015

209 views

Category:

Technology


1 download

DESCRIPTION

Rupicon 2014 solid

TRANSCRIPT

Page 1: Rupicon 2014 solid

SOLID OO Design

Tudor Pavel

1 / 46

Page 2: Rupicon 2014 solid

Change is inevitable.

2 / 46

Page 3: Rupicon 2014 solid

Design prepares you for it.

3 / 46

Page 4: Rupicon 2014 solid

Design Stamina Hypothesis

http://martinfowler.com/bliki/DesignStaminaHypothesis.html

4 / 46

Page 5: Rupicon 2014 solid

PrinciplesSingle Responsibility (SRP)

Open/Closed (OCP)

Liskov Substitution (LSP)

Interface Segregation (ISP)

Dependency Inversion (DIP)

5 / 46

Page 6: Rupicon 2014 solid

Single ResponsibilityA class/method should serve a single purpose.

There should never be more than one reason for a class/method to change.

6 / 46

Page 7: Rupicon 2014 solid

Open/ClosedA class/method should be open for extension but closed for modification.

7 / 46

Page 8: Rupicon 2014 solid

Liskov SubstitutionSubclasses should be substitutable for their base classes.

8 / 46

Page 9: Rupicon 2014 solid

Interface SegregationMany client-specific interfaces are better than one general-purpose interface.

9 / 46

Page 10: Rupicon 2014 solid

Dependency InversionDepend upon abstractions. Do not depend upon concretions.

10 / 46

Page 11: Rupicon 2014 solid

Design is all aboutdependencies.

11 / 46

Page 12: Rupicon 2014 solid

When you know something,you depend on it.

12 / 46

Page 13: Rupicon 2014 solid

If it changes, you might change.

13 / 46

Page 14: Rupicon 2014 solid

Example App

14 / 46

Page 15: Rupicon 2014 solid

RequirementsWe are the FBI and our buddies from the NSApost some encrypted messages for us in asecret RSS feed.

Our job is to parse the messages from the feedand store them in clear text in our DB, since wedon't understand encryption.

15 / 46

Page 16: Rupicon 2014 solid

EncryptionThe NSA are slightly better than us atencryption, so they've used e Caesar Cipher toencrypt the messages.

16 / 46

Page 17: Rupicon 2014 solid

Message modelclass Message < ActiveRecord::Baseend

17 / 46

Page 18: Rupicon 2014 solid

Public interfaceclass RSSMessagesParser def execute messages = fetch_messages messages.each { |m| Message.create(text: decrypt(m)) } end

private

def fetch_messages # ... end

def decrypt(message) # ... endend

18 / 46

Page 19: Rupicon 2014 solid

Fetching messagesrequire 'rss'

class RSSMessagesParser def execute messages = fetch_messages messages.each { |m| Message.create(text: decrypt(m)) } end

private

def fetch_messages RSS::Parser.parse(open('http://secret.nsa.gov/rss').read).items end

def decrypt(message) # ... endend

19 / 46

Page 20: Rupicon 2014 solid

Entire classrequire 'rss'

class RSSMessagesParser def execute messages = fetch_messages messages.each { |m| Message.create(text: decrypt(m)) } end

private

def fetch_messages RSS::Parser.parse(open('http://secret.nsa.gov/rss').read).items end

def decrypt(message) message.split('').map(&:ord).map { |a| a - 3 }.map(&:chr).join endend

20 / 46

Page 21: Rupicon 2014 solid

But what if...the "secret" URL changes?

the encryption method changes?

reading other RSS feeds is required?

21 / 46

Page 22: Rupicon 2014 solid

Knowing when to refactorIs it DRY?

Does it have a single responsibility?

Does everything in it change at the samerate?

Does it depend on more stable things?

! The answer needs to be YES for all of them to move on.

22 / 46

Page 23: Rupicon 2014 solid

Is it DRY?

23 / 46

Page 24: Rupicon 2014 solid

Does it have a singleresponsibility?

24 / 46

Page 25: Rupicon 2014 solid

What does RSSMessagesParser do?Fetches messages and decrypts them and saves them to the DB.

Words like and and or are design smells suggesting a violation of SRP. This classis doing too much!

25 / 46

Page 26: Rupicon 2014 solid

Extracting responsibilitiesclass MessagesParser def initialize @cipher = NSACipher.new @rss = RSSMessages.new end

def execute messages = @rss.fetch messages.each { |m| Message.create(text: @cipher.decrypt(m)) } endend

26 / 46

Page 27: Rupicon 2014 solid

SRP should be applied tomethods tooclass MessagesParser def initialize @cipher = NSACipher.new @rss = RSSMessages.new end

def execute messages = @rss.fetch messages.each { |m| parse(m) } end

private

def parse(message) Message.create(text: @cipher.decrypt(m)) endend

27 / 46

Page 28: Rupicon 2014 solid

The NSACipherclass NSACipher def decrypt(message) message.split('').map(&:ord).map { |a| a - 3 }.map(&:chr).join endend

28 / 46

Page 29: Rupicon 2014 solid

The RSSMessages classrequire 'rss'

class RSSMessages def fetch RSS::Parser.parse(open('http://secret.nsa.gov/rss').read).items endend

29 / 46

Page 30: Rupicon 2014 solid

Is it DRY?

30 / 46

Page 31: Rupicon 2014 solid

Does it have a singleresponsibility?

31 / 46

Page 32: Rupicon 2014 solid

Does everything in it changeat the same rate?

32 / 46

Page 33: Rupicon 2014 solid

The RSS URL and the Cipher stepcan change more often than theclasses, because the NSA want tostay at least ahead of the novicehackers by changing themperiodically.

33 / 46

Page 34: Rupicon 2014 solid

Using Open/Close Principleclass NSACipher def initialize(step=3) @step = step end

def decrypt(message) message.split('').map(&:ord).map { |a| a - @step }.map(&:chr).join endend

Add default step to simplify current callsThe class is now open for extension and reusability.NSACipher is essentially a generic Caesar cipher that can be used to decryptmessages, it has nothing to do with NSA, so we can rename it.

34 / 46

Page 35: Rupicon 2014 solid

Reusable CaesarCipherclass CaesarCipher def initialize(step=3) @step = step end

def decrypt(message) message.split('').map(&:ord).map { |a| a - @step }.map(&:chr).join endend

35 / 46

Page 36: Rupicon 2014 solid

The same with RSSFetcherrequire 'rss'

class RSSFetcher def initialize(url='http://secret.nsa.gov/rss') @url = url end

def fetch RSS::Parser.parse(open(@url).read).items endend

36 / 46

Page 37: Rupicon 2014 solid

Since we're using Rails 4.1 and thetwo defaults are secret values, wecan move them to secrets.yml.development: default_caesar_step: 3 default_rss_url: 'http://secret.nsa.gov/rss'

def initialize(url=Rails.application.secrets.default_rss_url)

def initialize(step=Rails.application.secrets.default_caesar_step)

37 / 46

Page 38: Rupicon 2014 solid

MessagesParser classclass MessagesParser def initialize @cipher = CaesarCipher.new @rss = RSSFetcher.new end

def execute messages = @rss.fetch messages.each { |m| parse(m) } end

private

def parse(message) Message.create(text: @cipher.decrypt(m)) endend

38 / 46

Page 39: Rupicon 2014 solid

Does it depend on morestable things?

39 / 46

Page 40: Rupicon 2014 solid

Using Dependency Inversionclass MessagesParser def initialize(cipher, fetcher) @cipher = cipher @fetcher = fetcher end

def execute messages = @fetcher.fetch messages.each { |m| parse(m) } end

private

def parse(message) Message.create(text: @cipher.decrypt(m)) endend

MessagesParser now depends upon abstractions being injected by callers

40 / 46

Page 41: Rupicon 2014 solid

Alternative ciphers nowsupported

AES

DES

...

41 / 46

Page 42: Rupicon 2014 solid

Alternative fetchers nowsupported

Files

Twitter :)

...

42 / 46

Page 43: Rupicon 2014 solid

ConclusionsThe App is easily adaptable to futurechanges because of SOLID Design

Beautiful and abstract class designs, evenDesign Patterns (next), were discovered byrefactoring and following basic rules andprinciples

Learn to trust your nose and to be preparedfor the inevitable changes that are coming

43 / 46

Page 44: Rupicon 2014 solid

ReferencesSOLID Object-Oriented Design, Sandi Metz,https://speakerdeck.com/skmetz/solid-object-oriented-design

44 / 46

Page 45: Rupicon 2014 solid

BonusTo make sure the concept of SRP sinks in:

http://youtu.be/vlN17gMhnEk (get ready for British English and humour)

45 / 46

Page 46: Rupicon 2014 solid

Questions?

46 / 46