Refactoring to Patterns
How Mephisto went from a single engine Lada
to a multi-engine jet figher
The Story
Behind a multi-engine
Mephisto
The Story
The Story
237 spams
The Story
Over a weekend
The Story
The Story
Long
The Story
Long
Arduous
The Story
Long
Arduous
Ooops!
Hazardous
The Story Solution
The Story Solution
The Story Solution
Carl kindly asked for a
Defensio plugin on
Mephisto
The Story Solution
I wanted to try Git
The Story Solution
Mephisto arrived on
GitHub
The Story Solution
Time to Refactor
to Design Patterns
Refactoring
What is refactoring ?
Refactoring
Refactoring (noun): a change made to the
internal structure of software to make it easier to
understand and cheaper to modify without
changing its observable behavior.
Martin Fowler, Refactoring: Improving the Design of Existing Code, page 53
Refactoring
Refactoring:
Improving the Design
of Existing Code
Refactoring
Without changing its external behavior
Refactoring Example
class Comment
def check_approval
# ...
end
end
Refactoring Example
class Comment
def ham?
# ...
end
end
Design Patterns
Each pattern describes a problem which occurs
over and over again in our environment, and
then describes the core of the solution to that
problem, in such a way that you can use this
solution a million times over, without ever doing
it the same way twice
Christopher Alexander, A Pattern Language, page x
Design Patterns
Design Patterns:
Elements of Reusable
ObjectOriented
Software
Design Patterns Example
Strategy: Define a
family of algorithms,
encapsulate each one,
and make them
interchangeable.
Refactoring to Patterns
Refactoring, with the intention
of instantiating a design pattern
Mephisto 0.8 Drax
class Comment
def check_approval(site, request)
self.approved = site.approve_comments?
if valid_comment_system?(site)
akismet = Akismet.new(site.akismet_key, site.akismet_url)
self.approved = !akismet.comment_check(
comment_spam_options(site, request))
end
end
end
Mephisto 0.8 Drax + Defensio
class Comment
def check_approval(site, request)
self.approved = site.approve_comments?
if valid_akismet_comment_system?(site)
akismet = Akismet.new(site.akismet_key, site.akismet_url)
self.approved = !akismet.comment_check(
akismet_comment_spam_options(site, request))
elsif valid_defensio_comment_system?(site)
defensio = Defensio.new(site.defensio_key,
site.defensio_url)
self.approved = !defensio.comment_check(
defensio_comment_spam_options(site, request))
end
end
end
Mephisto 0.8 Drax + Defensio + Chummy
class Comment
def check_approval(site, request)
self.approved = site.approve_comments?
case
when valid_akismet_comment_system?(site)
akismet_validation(site, request)
when valid_defensio_comment_system?(site)
defensio_validation(site, request)
when valid_chummy_comment_system?(site)
chummy_validation(site, request)
# and so on...
end
end
end
Core of the problem ?
Be able to support multiple
algorithms to detect comment spam
How ?
Strategy Design Pattern
Refactoring to Strategy Pattern
Refactoring to Strategy Pattern
Introduce Class
class Mephisto::SpamDetectionEngines::AkismetEngine
end
Refactoring to Strategy Pattern
Move Method
class Comment
def check_approval(site, request)
self.approved = site.approve_comments?
if valid_comment_system?(site)
akismet = Akismet.new(
site.akismet_key, site.akismet_url)
self.approved = !akismet.comment_check(
comment_spam_options(site, request))
end
end
end
class Mephisto::SpamDetectionEngines::AkismetEngine
end
Refactoring to Strategy Pattern
Move Method
class Comment
def check_approval(site, request)
Mephisto::SpamDetectionEngines::AkismetEngine.new(
site.akismet_key, site.akismet_url).check_approval(
self, site, request)
end
end
class Mephisto::SpamDetectionEngines::AkismetEngine
def check_approval(comment, site, request)
comment.approved = !akismet.comment_check(
comment_spam_options(comment, site, request))
end
end
Refactoring to Strategy Pattern
Structural Changes
Refactoring to Strategy Pattern
Introduce Subclass
class Mephisto::SpamDetectionEngines::DefensioEngine <
Mephisto::SpamDetectionEngines::AkismetEngine
end
Refactoring to Strategy Pattern
Add Behavior
class Mephisto::SpamDetectionEngines::DefensioEngine <
Mephisto::SpamDetectionEngines::AkismetEngine
def check_approval(comment, site, request)
!defensio.check_comment(
comment_spam_options(comment, site, request))
end
def comment_spam_options(comment, site, request)
# Defensio-specific spam options
end
end
Refactoring to Strategy Pattern
Structural Changes
Refactoring to Strategy Pattern
Extract Superclass
module Mephisto::SpamDetectionEngines
class Base
end
class DefensioEngine < Base
end
class AkismetEngine < Base
end
end
Refactoring to Strategy Pattern
Structural Changes
Creational Patterns
Abstract Factory Pattern
Creational Patterns
class Site
def spam_engine
klass_name = read_attribute(:spam_detection_engine)
if klass_name.blank?
Mephisto::SpamDetectionEngines::
NullEngine.new(self)
else
klass_name.constantize.new(self)
end
end
end
Refactoring to Strategy Pattern
Interaction Diagram
Adapter Pattern
Convert the interface of a class
into another interface clients expect.
Adapter Pattern
def check_approval
# implementation
end
Adapter Pattern
def ham?
# implementation
end
Refactoring to Adapter Pattern
Before
class CommentsController
def create
if @comments.check_approval(site, request) then
# thanks
else
# thanks, but moderated
end
end
end
class Comment
def check_approval(site, request, options={})
# implementation
end
end
Refactoring to Adapter Pattern
Rename Method
class CommentsController
def create
if @comments.ham?(site, request) then
# thanks
else
# thanks, but moderated
end
end
end
class Comment
def check_approval(site, request, options={})
# implementation
end
end
Refactoring to Adapter Pattern
Interaction Diagram
Who am I ?
François Beausoleil
Who am I ?
François Beausoleil
A Single Programmer's
Blog
Who am I ?
François Beausoleil
A Single Programmer's
Blog
XLsuite
Creative Commons Attributions
dave_7
http://flickr.com/photos/daveseven/204585649/
DanieVDM
http://flickr.com/photos/dvdmerwe/251163984/