Buscar

Essential Object Oriented Desig - Bala Paranj

Faça como milhares de estudantes: teste grátis o Passei Direto

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Você também pode ser Premium ajudando estudantes

Faça como milhares de estudantes: teste grátis o Passei Direto

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Você também pode ser Premium ajudando estudantes

Faça como milhares de estudantes: teste grátis o Passei Direto

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Você também pode ser Premium ajudando estudantes
Você viu 3, do total de 118 páginas

Faça como milhares de estudantes: teste grátis o Passei Direto

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Você também pode ser Premium ajudando estudantes

Faça como milhares de estudantes: teste grátis o Passei Direto

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Você também pode ser Premium ajudando estudantes

Faça como milhares de estudantes: teste grátis o Passei Direto

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Você também pode ser Premium ajudando estudantes
Você viu 6, do total de 118 páginas

Faça como milhares de estudantes: teste grátis o Passei Direto

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Você também pode ser Premium ajudando estudantes

Faça como milhares de estudantes: teste grátis o Passei Direto

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Você também pode ser Premium ajudando estudantes

Faça como milhares de estudantes: teste grátis o Passei Direto

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Você também pode ser Premium ajudando estudantes
Você viu 9, do total de 118 páginas

Faça como milhares de estudantes: teste grátis o Passei Direto

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Você também pode ser Premium ajudando estudantes

Prévia do material em texto

Table of Contents
 
1. Introduction 
2. Basics of Abstraction 
3. Single Purpose Principle 
4. Stepwise Refinement 
5. Dependency Inversion Principle 
6. Basic Three Rules of Design 
7. The Art of Uniform Interface 
8. Localized Change vs Additive Change 
9. Coupling Basics : Dependency Direction 
10. Concrete Class vs Abstract Messages 
11. Flexible Design 
12. Open Closed Principle 
Essential Object Oriented Design
This book covers the basic Object Oriented Design concepts using Ruby
programming language. The goal is to provide a solid foundation to build
upon. This book distills my Object Oriented Design knowledge into a concise
and easy-to-read format. Repetition is key to learning Ruby. We will visit the
concepts from different angles. You will get the most benefit out of the book
if you work through every example as you read through the book. This book
uses Ruby 2.3.0.
About the Author
Bala Paranj has a Master's degree in Electrical Engineering from Wichita
State University. He began working in the IT industry in 1996. He started his
career as a Technical Support Engineer and then became a Web Developer
using Perl, Java and Ruby.
He is available for freelance work. Please contact him at support@zepho.com
or via Ruby Plus. He is also working on screencasts based on this book. If
you want notification about the release, please contact him.
https://www.rubyplus.com/contact
Basics of Abstraction
In this chapter, you will learn the basics of abstraction using examples.
Definition
The dictionary says abstraction means to draw away , to remove characteristics
from something in order to reduce it to a set of essential characteristics. It is a
tool for simplification. We find the essence by ignoring irrelevant details.
In his paper Is Abstraction the Key to Computing?, Jeff Kramer says
abstraction also means:
 
The process of formulating general concepts by abstracting common
properties of instances, and;
 
A general concept formed by extracting common features from specific
examples.
Example 1
Chemistry is an abstraction of physics.
VS
Biology is an abstraction of chemistry.
VS
Genetics is an abstraction of biology.
VS
Example 2
The London Underground map that overlays the underground system onto a
conventional geographical map.
In this map, you can see:
 
River Thames
Relative distances between stations.
Harry Beck's simplified map.
 
This fits the purpose of navigating around the London Underground.
It is misleading for other purposes.
Let's consider two extremes:
Too abstract
The map would not provide sufficient information for the purpose.
Too detailed
The map becomes confusing and less comprehensible.
The level, benefit and value of a particular abstraction depend on its purpose.
Abstraction in Software
To quote Booch in Object-Oriented Analysis and Design with
Applications:
An abstraction denotes the essential characteristics of an object that
distinguish it from all other kinds of objects and thus provide crisply
defined conceptual boundaries, relative to the perspective of the viewer."
The term perspective of the viewer needs an explanation. Let us consider a
House object, when a banker sees this house, he thinks in terms of the value
of the property, opportunity for appreciation, etc whereas when a decorator
views it, he thinks in terms of what color the house should be painted, total
area to be painted, etc. The same object House can be viewed from different
perspectives and can lead to entirely different abstractions by different
people.
Booch, Fairsmith, Henderson-Sellers define abstraction as:
Any model that includes the most important, essential, or distinguishing
aspects of something while suppressing or ignoring less important,
immaterial, or diversionary details.
Coad, Fairsmith, Henderson-Sellers, Rumbaugh define abstraction as:
The cognitive tool for rationalizing the world by considering only those
details necessary for the current purpose.
So, abstraction is about what details we choose to emphasize and what details
we choose to ignore. What we choose to emphasize is dictated by the
application. It simplifies the things that we look at in the real world. For
example, a chair can be made up of different kinds of material, height
adjusting knobs, reclining adjustment knobs etc. If every time we looked at
the chair, if we had to deal with what material it is made up of, how the
height adjustment knobs are designed and other irrelevant details related to
our purpose using a chair to sit, our brains will be exhausted. So, the
abstraction process simplifies things and allows us to manage complexity
during problem solving process.
Computer science is a science of abstraction — creating the right model
for thinking about a problem and devising the appropriate mechanizable
techniques to solve it. Every other science deals with the universe as it
is. The physicist’s job, for example, is to understand how the world
works, not to invent a world in which physical laws would be simpler or
more pleasant to follow. Computer scientists, on the other hand, must
create abstractions of real-world problems that can be understood by
computer users and, at the same time, that can be represented and
manipulated inside a computer.
Abstraction in the sense we use it implies simplification, the
replacement of a complex and detailed real-world situation by an
understandable model within which we can solve a problem. That is, we
“abstract away” the details whose effect on the solution to a problem is
minimal or nonexistent, thereby creating a model that lets us deal with
the essence of the problem.
-- From the book 'Foundations of Computer Science' by Alfred V Aho
and Jeffrey D Ullman
Why Abstraction?
Abstraction is crucial to produce clear, elegant designs and programs. It is
useful to manage complexity. We can diagnose components at the interfaces
rather than by exhaustively tracing functions of all components.
Advantages in Treating Systems by Levels of
Abstraction
 
Each level has its own definition and specification. So development can
proceed concurrently at each level.
We can allocate work according to strength.
A system can evolve by evolving components separately. It is not
necessary to re-implement the entire system when one component
changes. This avoids ==Second System Syndrome==.
Abstraction in Daily Life
You use abstraction in everyday things in your life. For instance you say : I
am going to a Rock Concert this weekend. You don't say: "I am going to a
musical performance characterized by electric guitar, electric bass guitar and
drums this weekend".
How to Abstract
In order to learn the process of abstraction you need to learn how to find the
essence of something. Oxford dictionary defines essence as :
The intrinsic nature or indispensable quality of something that
determines its character.
Let's now think about the following question to illustrate finding the essence.
 
What is the essence of a chair?
A chair is a thing and it has a form and function. If you assume sitting as the
function of a chair, then you have the attributes such as number of legs,
material of the chair, whether it has a support for the back and so on as the
variables that can be varied in the chair definition. The question now is what
is the least amount of these attributes we need but still retain the concept of
chair? Can a chair have no support for the back? Yes. So we can consider this
as irrelevant to the chair concept. We can continue this process for other
attributes to come up with the essence of a chair.
Leaky Abstraction
Abstraction that is leaky will force us to look at implementation to learn
about the usage of the API. This is like looking under the hood of your car
and understanding the working of the internals of the car engine in order to
learn how to drive the car.
Conclusion
In this article we discussed the what and the why of abstraction. Abstraction
is one of the most important concepts thatis taught in Computer Science. But
developers still find it difficult to apply it in software development.
Exercises
 
What is the essence of a pen?
What is the essence of a car?
References
 
[Abstraction in Computer Science & Software Engineering: A
Pedagogical Perspective]
(https://edu.technion.ac.il/Faculty/OritH/HomePage/FrontierColumns/OritHazzan_SystemDesigFrontier_Column5.pdf
'Abstraction in Computer Science')
[Is Abstraction the Key to Computing?]
(https://www.ics.uci.edu/~andre/informatics223s2007/kramer.pdf 'Is
Abstraction the Key to Computing')
Computational Thinking
https://edu.technion.ac.il/Faculty/OritH/HomePage/FrontierColumns/OritHazzan_SystemDesigFrontier_Column5.pdf
https://www.ics.uci.edu/~andre/informatics223s2007/kramer.pdf
https://www.cs.cmu.edu/~15110-s13/Wing06-ct.pdf
Single Purpose Principle
In this chapter you will about Single Purpose Principle.
A class should capture one and only one key abstraction
Object Oriented Design Heuristics by Arthur Riel
Where Did this Confusion Begin?
Robert C. Martin came up with the Single Responsibility Principle in 1995.
The problem is CRC card already had used the term Responsibility to mean
something different. CRC cards came way back in 1989. Also Robert Martin
redefined the term responsibility to mean a reason to change. He basically
took the concept of cohesion and added his own insights to come up with
Single Responsibility Principle.
The main point of this principle is that there should be only one reason to
change. I don't want to call it Single Responsibility Principle because of the
confusion it creates with the existing terminology. Since this principle says
that the set of responsibilities (as used by CRC cards) should be focused on
one purpose, I think it is appropriate to call it Single Purpose Principle.
Single Purpose
Unix is a perfect example for following the Single Purpose Principle. Each
command line utility does one thing really well. It proves that good design is
timeless.
How to Recognize Violation of Single
Purpose Principle?
Let's discuss the examples that are used in Robert C. Martins's SRP: The
Single Responsibility Principle.
Example 1 : Bowling
Game - Keep track of frames
Scorer - Calculate the score
Each of the above is an axis of change. What is an axis of change? How do
you recognize different axes of change? He does not explain in his paper. It is
confusing. If you ask yourself the questions:
 
1. Does the operations operate on the data most of time?
2. Is the data and operations on it related together?
3. Is this class highly cohesive?
then it will be easy to recognize that we need to separate the Game and
Scorer objects. In this example the abstractions are consistent and they are at
the application level.
Example 2 : Rectangle
Application 1 : Computational geometry
Application 2 : Graphical in nature
These are different abstractions. One is at the UI level and the other is at the
domain level. The abstractions must be consistent, you cannot mix different
levels of abstraction into one class.
He says :
In the context of the Single Responsibility Principle (SRP) we
define a responsibility to be a reason for change . If you can think of
more than one motive for changing a class, then that class has more
than one responsibility. This is sometimes hard to see.
Yes, it is hard to see because, you can always say that there is only one
reason to change when in reality there is more than one reason. This happens
when you mix different levels of abstraction.
For instance, I worked on a project where I had to upload files to Amazon S3.
The requirements demanded that I configure the number of threads that can
upload files at once to S3. I wrote threading library that did not have any
dependency on the S3 file uploader. The S3 file uploader was not aware that
it was used by multiple threads. The plumbing code was separated from the
actual task in the application. Later I can replace the threading library with a
library that uses Celluloid to abstract away the plumbing code. This change
will not impact the S3 file uploader.
Example 3 : Modem
This example also mixes different levels of abstraction.
Connection : Plumbing level abstraction (dial and hangup)
Data Channel : Application level abstraction (send and receive)
The plumbing and application level abstractions are not separated.
Example 4 : Employee
Employee class with calculate_pay , save , report_hours methods violates the
SRP. In this case, he argues that the people who request changes for the
business rules are different from those who request changes to the saving
functionality related to the database.
It is very clear in this example also that the Employee class mixes different
levels of abstraction. We need to separate persistence level abstraction from
application level abstraction. By applying Separation of Concerns we can easily
recognize that the class has more than one purpose.
His Conclusion
The SRP is one of the simplest of the principle, and one of the hardest to get
right. Conjoining responsibilities is something that we do naturally. Finding
and separating those responsibilities from one another is much of what
software design is really about.
My Thoughts
Another flaw in his reasoning can be illustrated by using the Amazon S3
uploader I mentioned earlier. In this case, the stake holders who would
demand changes due to threading is not different from file uploading aspect.
So since the stake holder is the same you would think it follows Single
Purpose Principle. But that is wrong. Because we know that we are mixing
different levels of abstraction and it can change due to two different reasons:
plumbing related code and the file uploading related code.
Focus on keeping abstractions consistent. Do not mix different levels of
abstraction. It will be easier to make better design decisions. You can read
Code Complete 2 by Steve McConnell for more detailed explanation.
Summary
In this chapter you learned about Single Purpose principle. If we apply Single
Purpose Principle throughout the system we will obey the Separation of
Concerns principle and the system will be organized into different layers.
Each layer will be focused on fulfilling one purpose such as Object
Relational Mapping layer, data conversion for external system and so on. For
a good discussion on this topic, read The Art of Separation of Concerns.
http://aspiringcraftsman.com/2008/01/03/art-of-separation-of-concerns/
Stepwise Refinement
In this chapter, you will learn about Stepwise Refinement.
Problem
I am the laziest professor ever. I've given a multiple choice test (A-D are
valid answers) but I lost my test key. I'll assume that whatever answer was
most popular for a question is the correct answer.
I've included a file that contains the students quiz answers. Each row
represents one student. Write code that figures out the most popular answer to
each question and then prints out the key.
Example
Given:
ABCCADCB
DDDCAACB
ABDDABCB
AADCAACC
BBDDAACB
ABDCCABB
ABDDCACB
Output:
ABDCAACB
Analysis
Question # : 1 2 3 4 5 6 7 8 Answers : ?
There are seven students. There are eight questions.
Steps
Step 1
 
1. Figure out the most popular answer to each question
2. Print out the key
Step 2
 
1. Figure out the most popular answer to each question
for each of the 8 questions
 Find the most popular answer
 Save the answer
end
 
1. Print out the key Print the answer for each question in the saved answer
key
Step 3
 
1. Figure out the most popular answer to each question
for each of the 8 questions
 Find the most popular answer
 Possible Answers are : A, B, C, D
 Initialize the number of answers to 0 for all the possible answers
 For each of the 7 students
 Check the answer by the student
 Increment the count for the answer
 end 
 Save the answer
end
 
1. Print out the key Print the answer for each question in the saved answerkey
This is the blueprint for our program. This blueprint can be used to code the
solution in any language.
Step 4
What is the structure of the input? Let's assume an array of answers arranged
by students:
['ABCCADCB','DDDCAACB', 'ABDDABCB', 'AADCAACC', 'BBDDAACB', 'ABDCCABB', 'ABDDCACB']
Step 5
@submissions = ['ABCCADCB','DDDCAACB', 'ABDDABCB', 'AADCAACC', 'BBDDAACB', 'ABDCCABB'
answer_key = []
def find_most_popular_answer_for(question)
 key = Hash.new(0) 
 for submission in @submissions
 key[submission[question]] += 1
 end
 key.max_by{|k,v| v}
end
for question in (0..7).to_a 
 answer_key << find_most_popular_answer_for(question)
end
puts answer_key
After I wrote the program, I had to add max_by to retain only the highest
scoring answer for the given question. You can always go back and refine the
step when required.
Summary
Stepwise Refinement is a useful technique for solving problems. We can start
with the 'What' and gradually move towards the 'How' and finally code the
solution for a given problem.
Resources
 
Lazy Professor
Program Development by Stepwise Refinement by Niklaus Wirth
https://github.com/SeaRbSg/braincandy/blob/master/lazy-professor/README.md
Dependency Inversion Principle
In this chapter, we will explore Dependency Inversion Principle by examples.
Steps
Step 1
Let's write a program to :
1. Read from keyboard.
2. Write to a terminal.
Here is the program:
def copy
 message = gets
 puts message 
end
copy
We can run this:
$ ruby copy.rb
Hello
Hello
Step 2
We now want to accommodate changes to our program that can:
1. Read from a tape reader
2. Write to a terminal
Here is the modified program:
def read_tape
 p 'Input from tape reader' 
end
def copy(tape_reader = false)
 if tape_reader
 message = read_tape
 else
 message = gets
 end
 puts message 
end
copy(true)
We can run it:
$ ruby copy.rb
Input from tape reader
Input from tape reader
Step 3
We now need to accommodate changes to our program that can:
1. Read from tape reader as before
2. Write to paper tape punch
Here is the modified program:
def read_tape
 p 'Input from tape reader' 
end
def write_to_paper_tape_punch(output)
 p 'Output to paper tape punch' 
end
def copy(tape_reader = false, paper_tape_punch = false)
 if tape_reader
 message = read_tape
 else
 message = gets
 end
 if paper_tape_punch
 write_to_paper_tape_punch(message)
 else
 puts message 
 end
end
copy(true, true)
The solution is becoming messy with lot of conditionals. When we add more
types of input and output coding down this path will lead to very difficult to
maintain codebase.
Applying Good Design Principles
We will apply the following three basic principles:
- Separate things that change from things that stays the same. Encapsulate what varies behind a well-
defined interface.
- Program to interfaces, not implementations. This exploits polymorphism.
- Depend on abstractions. Do not depend on concrete classes.
The input device and output device can change. We can encapsulate them
behind the interfaces named read and write. The code must use the newly
defined read and write interfaces. This results in the code depending on
abstractions.
class Copier
 def initialize(reader, writer)
 @reader, @writer = reader, writer
 end
 def copy
 message = @reader.read
 @writer.write(message)
 end
end
class KeyboardReader
 def read
 gets
 end
end
class PrinterWriter
 def write(output)
 p "Writing #{output} to printer"
 end
end
Copier class now depends on stable interface read and write. The
implementation of KeyboardReader and PrinterWriter is now hidden behind
the well defined read and write interface. We can use this new design to
copy from any input source to any output source without modifying the
Copier class.
reader = KeyboardReader.new
writer = PrinterWriter.new
copier = Copier.new(reader, writer)
copier.copy
We can run this program.
$ ruby copy.rb
This is a test
"Writing This is a test\n to printer"
Dependency Inversion Principle
It states:
- Details should depend on abstractions.
- Abstractions should not depend on details.
- High level modules should not depend upon low level modules. Both should depend upon abstractions.
Compare the dependency of:
Copy 
Read Keyboard WriterPrinter
vs
Copy
Reader : KeyboardReader
Writer : PrinterWriter
The interface in KeyboardReader and PrinterWriter is implicit in Ruby. We
conform to the read and write interface instead of explicitly saying we
implement those interfaces in the code. The copier class is reusable with
different input and output devices.
Code Breaker Game Example
Let's look at the code example for Codebreaker game from The Rspec Book.
The solution in the book does not apply the DIP.
Before
module Codebreaker
 class Game
 def initialize(output)
 @output = output
 end
 def start(secret)
 @secret = secret
 @output.puts 'Welcome to Codebreaker!'
 @output.puts 'Enter guess:'
 end
 end
end
g = Codebreaker::Game.new($stdout)
g.start('sekret')
This is a good example that shows using BDD or TDD does not make the
design emerge magically. You must apply good design principles consciously
and deliberately. Let's look at the solution after applying the DIP.
After
module Codebreaker
 class Game
 def initialize(writer)
 @writer = writer
 end
 def start(secret)
 @secret = secret
 @writer.write 'Welcome to Codebreaker!'
 @writer.write 'Enter guess:'
 end
 end
end
class StandardConsole
 def write(message)
 $stdout.puts(message)
 end 
end
writer = StandardConsole.new
g = Codebreaker::Game.new(writer)
g.start('sekret')
It's surprising to find coding solution from a published book that has gone
through technical review come up short in design.
Summary
In this chapter, we applied the three basic principles of good design:
1. Separate things that is likely to vary
2. Hide them behind a well defined interface
3. Write your program to use the stable interfaces instead of depending on concrete details.
to come up with a flexible design that is easy to maintain and extend.
The Three Basic Rules for a Good
Design
In this chapter, you will learn how to apply basic object oriented design
principles.
Problem
Write a program that:
 
Loads a set of employee records from a flat file.
Sends a greetings email to all employees whose birthday is today.
The flat file is a sequence of records, separated by newlines; these are the
first few lines:
last_name, first_name, date_of_birth, email
Doe, John, 1982/10/08, john.doe@foobar.com
Ann, Mary, 1975/09/11, mary.ann@foobar.com
The greetings email contains the following text:
Subject: Happy birthday!
Happy birthday, dear <first_name>!
where first_name is the place holder for first name.
Object Oriented Design Basic
Principles
We will apply the following three basic principles:
 
Separate things that change from things that stays the same. Encapsulate
what varies behind a well-defined interface.
Program to interfaces, not implementations. This exploits
polymorphism.
Depend on abstractions. Do not depend on concrete classes.
High Level Steps
 
1. Read data.txt file.
2. Check if date of birth is today.
3. Send greetings email if today is the person's birthday.
Step 1
Let's read a CSV file that contains data.
file_name = "data.txt"
text = open(file_name)
print text.read
Refine the Step 1
1. Read data.txt CSV file.
 Skip the header
Here is our simple program that we can start playing with:
require 'csv'
file_name = "#{Dir.pwd}/data.txt"
data = CSV.read(file_name, {headers: true}) 
data.each do |x|
 p x
end
Refine the Step 2
1. Read data.txt CSV file
 Skip the header
2. Check if date of birth is today
 Retrieve the third column
 Remove the spaces at the ends
 Check if month and date is the sameas today's month and date
 If yes, return the person's first name
3. Send greetings email if today is birthday
Refine the Step 3
1. Read data.txt CSV file
 Skip the header
2. Check if date of birth is today
 Retrieve the third column
 Remove the spaces at the ends
 Check if month and date is the same as today's month and date
 If yes, return the person's first name
3. Send greetings email
 Use Gmail, Sendgrid, Pony etc.
Before
The program written that does not apply the 3 design principles looks like
this:
require 'csv'
file_name = "#{Dir.pwd}/data.txt"
data = CSV.read(file_name, {headers: true}) 
data.each do |x|
 birth_date = x[2].strip!
 month = birth_date[5..6]
 day = birth_date[8..9] 
 if (month.to_i == Date.today.month) and (day.to_i == Date.today.day)
 p x[1].strip
 email = <<EMAIL_TEXT
Subject: Happy birthday!
 Happy birthday, dear #{x[1].strip}!
EMAIL_TEXT
 p email
 end
end
Analysis
Responsibilities
This program has the following responsibilities:
1. Parsing CSV file
2. Checking if today is the birthday of a person
3. Sending email
Things that can Change
Let's make a list of things that can change.
1. Input data source
2. How greetings is sent
Things that Stays the Same
Let's make a list of things that stays the same.
1. Logic to find if someone's birthday is today.
Redesign
Step 1
The PersonFileStore class will have records method that will return a list of
 Person objects. The code that applies what we did in analysis now looks like
this:
require 'csv'
# This is a domain object
# This object has no dependency on other objects. It is agnostic to storage mechanism
class Person 
 attr_reader :first_name
 def initialize(first_name, last_name, date_of_birth, email)
 @first_name = first_name
 @last_name = last_name
 @date_of_birth = date_of_birth
 @email = email
 end
 def birth_day
 @date_of_birth[8..9] 
 end
 def birth_month
 @date_of_birth[5..6]
 end
end 
# This class knows how to parse the CSV file to create Person objects
# The direction of dependency is from PersonFileStore to the domain object
class PersonFileStore
 def initialize(file)
 @file = file
 end
 def records
 result = []
 data = CSV.read(@file, {headers: true}) 
 data.each do |x|
 person = Person.new(x[1], x[0], x[2].strip!, x[3])
 result << person
 end 
 result
 end
end
# This section of the code is not yet cleaned up.
pfs = PersonFileStore.new("#{Dir.pwd}/data.txt")
records = pfs.records
records.each do |person|
 month = person.birth_month
 day = person.birth_day
 if (month.to_i == Date.today.month) and (day.to_i == Date.today.day)
 email = <<EMAIL_TEXT
Subject: Happy birthday!
 Happy birthday, dear #{person.first_name}!
EMAIL_TEXT
 p email
 end
end
Step 2
Let's extract the logic to find if anyone has a birthday today.
# Person and PersonFileStore classes is same as before.
# This class encapsulates the logic to find out if the birthday is today or not.
# It has no dependency on other objects
class BirthDay
 def initialize(month, day)
 @month = month
 @day = day
 end
 def today?
 (@month.to_i == Date.today.month) and (@day.to_i == Date.today.day)
 end
end
pfs = PersonFileStore.new("#{Dir.pwd}/data.txt")
records = pfs.records
records.each do |person|
 month = person.birth_month
 day = person.birth_day
 birth_day = BirthDay.new(month, day)
 if birth_day.today?
 email = <<EMAIL_TEXT
Subject: Happy birthday!
 Happy Birthday, Dear #{person.first_name}!
EMAIL_TEXT
 p email
 end
end
Step 3
Let's extract sending greeting.
# Person, PersonFileStore and Birthday classes is same as before.
# Sending email to the console output is encapsulated within the send interface
class GreetingConsole
 def initialize(message, email)
 @message = message
 @email = email
 end
 def send
 p "Sending email to : #{email}"
 p @message 
 end
end
# The following code is the client code
pfs = PersonFileStore.new("#{Dir.pwd}/data.txt")
records = pfs.records
records.each do |person|
 month = person.birth_month
 day = person.birth_day
 birth_day = BirthDay.new(month, day)
 if birth_day.today?
 message = <<EMAIL_TEXT
Subject: Happy Birthday!
 Happy Birthday, Dear #{person.first_name}!
EMAIL_TEXT
 # Client is tied to a specific implementation of sending an email message
 # This needs to change to GreetingEmail.new(message), greeting.send to send email greeting
 greeting = GreetingConsole.new(message)
 greeting.send
 end
end
Step 4
Let's add an in-memory data source and make it work.
class PersonMemoryStore
 def records
 result = []
 person = Person.new('Bugs', 'Bunny', '1982/10/06', 'bbunny@rubyplus.com')
 result << person
 person = Person.new('Daffy', 'Duck', '1975/09/11', 'dduck@rubyplus.com') 
 result << person
 result
 end
end
# The following code is the client code
pfs = PersonMemoryStore.new
records = pfs.records
records.each do |person|
 month = person.birth_month
 day = person.birth_day
 birth_day = BirthDay.new(month, day)
 if birth_day.today?
 message = <<EMAIL_TEXT
Subject: Happy Birthday!
 Happy Birthday, Dear #{person.first_name}!
EMAIL_TEXT
 # Client is tied to a specific implementation of sending an email message
 # This needs to change to GreetingEmail.new(message), greeting.send to send email greeting
 greeting = GreetingConsole.new(message)
 greeting.send
 end
end
Notice that the PersonMemoryStore has the same interface as the PersonFileStore 
class. In a real project, we could use SQLite in-memory database.
Step 5
Let's add a different way to send email by using Pony gem.
require 'pony'
# Sending a real email using Pony gem
class GreetingPony
 def initialize(message, email)
 @message = message
 @email = email
 end
 def send
 Pony.mail(:to => @email, :from => 'admin@rubyplus.com', :subject => 'Happy Birthday!'
 end
end
After Redesign
The channel folder has greeting_console.rb and greeting_pony.rb classes. The
 GreetingConsole class looks like this:
# Sending email to the console output is encapsulated within the send interface
class GreetingConsole
 def initialize(message, email)
 @message = message
 @email = email
 end
 def send
 p "Sending email to : #{@email}"
 p "Subject : Happy Birthday!"
 p @message 
 end
end
Here is the GreetingPony class:
require 'pony'
# Sending a real email using Pony gem
class GreetingPony
 def initialize(message, email)
 @message = message
 @email = email
 end
 def send
 Pony.mail(:to => @email, :from => 'admin@rubyplus.com', :subject => 'Happy Birthday!'
 end
end
The domain folder contains the BirthDay and Person classes. Here is the
 BirthDay class:
require 'date'
# This class encapsulates the logic to find out if the birthday is today or not.
# It has no dependency on other objects
class BirthDay
 def initialize(month, day)
 @month = month
 @day = day
 end
 def today?
 (@month.to_i == Date.today.month) and (@day.to_i == Date.today.day)
 end
end
Here is the Person class:
# This is a domain object
# This object has no dependency on other objects. It is agnostic to storage mechanism
class Person 
 attr_reader :first_name, :email
 def initialize(first_name, last_name, date_of_birth, email)
 @first_name = first_name
 @last_name = last_name
 @date_of_birth = date_of_birth
 @email = email
 end
 def birth_day
 @date_of_birth[8..9] 
 end
 def birth_month
 @date_of_birth[5..6]
 end
end
The source folder contains person_file_store.rb and person_memory_store.rb .
require 'csv'
require_relative '../domain/person'
# This class knows how to parse the CSV file to create Person objects
# The direction of dependency is from PersonFileStoreto the domain object
class PersonFileStore
 def initialize(file)
 @file = file
 end
 def records
 result = []
 data = CSV.read(@file, {headers: true}) 
 data.each do |x|
 person = Person.new(x[1], x[0], x[2].strip!, x[3])
 result << person
 end 
 result
 end
end
You can see that we need the require_relative statement, since it has
dependency on the Person domain object. The PersonMemoryStore class
looks like this:
require_relative '../domain/person'
# This class provides in-memory implementation of the data source interface
# Useful in writing tests
class PersonMemoryStore
 def records
 result = []
 person = Person.new('Bugs', 'Bunny', '1982/10/08', 'bbunny@rubyplus.com')
 result << person
 person = Person.new('Daffy', 'Duck', '1975/09/11', 'dduck@rubyplus.com') 
 result << person
 result
 end
end
Visual Representation
You can download the final refactored code that has a better design here:
[Ruby Greeter] (https://bitbucket.org/bparanj/greeter 'Ruby Greeter')
https://bitbucket.org/bparanj/greeter
Summary
We separated the input data source that can change into it's own source
folder. We encapsulated it behind a well-defined interface. We did the same
for different ways to send birthday greetings by moving all the relevant
classes to the channel folder. We depend on the send method for greeting
delivery and records method for data source, so we program to the interface.
The glue code in main.rb that uses the classes in the channel, domain and
source folders depends on concrete classes. You can use dependency
injection and vary the input source and the channel to make them depend on
abstractions instead of concrete classes.
Reference
The birthday greetings kata
http://matteo.vaccari.name/blog/archives/154
The Art of Uniform Interface
Context
After reading how the first, second, third and so on methods were added in
the Rails doctrine by DHH, I started to look for ways on how I would
actually go about doing something better. Obviously the drawback of
defining methods like that is that they are not scalable. But, I see his point of
not indexing into an array to get data for some common use cases.
Challenge
How can we come up with a uniform interface that does not result in
explosion of method and at the same time achieves ignorance of indexing
into an array? Ruby, has values_at method for an array.
> a = [1,2]
 => [1, 2] 
> a.values_at(1)
 => [2] 
> a.values_at(0)
 => [1]
This exposes the knowledge of the indexing of an array. It also knows that is
an array. Ruby also has values_at method for hash.
 h = { "cat" => "feline", "dog" => "canine", "cow" => "bovine" }
 h.values_at("cow", "cat") 
 #=> ["bovine", "feline"]
This exposes the knowledge that is an hash. You need to know the key in the
hash to get the corresponding value.
Well Defined Interface
I don't care about the type of data structure, just give me the element in the
given position.
class Array
 def element(position)
 self[position - 1]
 end
end
p [4,5,6].element(3)
You can just provide the position as the parameter to element method instead
of going through the mental mapping of position and index of an array.
Similarly, we can define a element method for Hash.
class Hash
 def element(position)
 a = self.to_a[position-1]
 {a[0] => a[1]}
 end
end
h = {a: 1, b: 2, c: 3, d: 4}
p h.element(2)
This returns :
{b: 2}
Both implementation are data structure agnostic. It does not force the
developers to map the position of an element and the index in array or
knowing the key of a hash.
Principle of Least Surprise
I was surprised when I tried to call element method on Array and Hash and I
got errors. Ruby has failed in this case. It only has first and last method
defined.
Summary
In this chapter, we saw how we can define a uniform interface that hides the
data structure and knowledge required to pass in parameters to a method.
This solution eliminates parametric coupling.
Localized Change vs Additive Change
To learn how to eliminate conditionals and come up with a good object
oriented design.
Transformer
Let's write a program to transform a json string or a binary format string.
Steps
Step 1
The transformer that uses conditional:
require 'json'
class Transformer
 def initialize(string)
 @string = string
 end
 def transformed_string(type)
 if type == :json
 JSON.parse(@string)
 elsif type == :binary
 @string.unpack('B*').first
 end
 end
end
t = Transformer.new('Hello')
x = t.transformed_string(:binary)
p x
This prints:
0100100001100101011011000110110001101111
Step 2
Let's eliminate the conditional in transformed_string method:
require 'json'
class Transformer
 def initialize(string)
 @string = string
 end
 def transformed_string(transformation)
 transformation.transform(@string)
 end
end
class JSONTransformation
 def self.transform(string)
 JSON.parse(string)
 end
end
y = Transformer.new('{"foo": "bar"}').transformed_string(JSONTransformation)
p x
This prints : { "foo" => "bar" }
Step 3
Let's now implement the binary transformation:
require 'json'
class Transformer
 same as before
end
class BinaryTransformation
 def self.transform(string)
 string.unpack('B*').first
 end
end
x = Transformer.new('Hello').transformed_string(BinaryTransformation)
p x
This prints:
0100100001100101011011000110110001101111
Step 4
Let's change the setter dependency injection to constructor dependency
injection and cleanup the code a bit:
require 'json'
class Transformer
 def initialize(type)
 @type = type
 end
 def transform(string)
 @type.transform(string)
 end
end
class BinaryTransformation
 def transform(string)
 string.unpack('B*').first
 end
end
transformer = Transformer.new(BinaryTransformation.new)
x = transformer.transform('Hello')
p x
This prints:
0100100001100101011011000110110001101111
Step 5
We can implement the JSON tranformation:
class JSONTransformation
 def transform(string)
 JSON.parse(string)
 end
end
t = Transformer.new(JSONTransformation.new)
y = t.transform('{"foo": "bar"}')
p y
Step 6
Let's implement MD5 for fun:
require 'digest'
class MD5Transformation
 def transform(string)
 Digest::MD5.hexdigest string
 end
end
s = Transformer.new(MD5Transformation.new)
z = s.transform('Hello')
p z
we eliminated conditionals and replaced it with well-defined abstraction. We
went from isolated changes to additive changes to implement new
functionality. In this case trasform method. This is the main theme of
Design Patterns: Elements of Reusable Object-Oriented Software book.
Step 7
We can do even better by replacing these anemic classes with blocks.
require 'json'
class Transformer
 def transform(string)
 yield(string)
 end
end
binary_transformer = ->(x) { x.unpack('B*').first } 
transformer = Transformer.new
a = transformer.transform('Hello', &binary_transformer)
p a
json_transformer = ->(y) { JSON.parse(y) }
b = transformer.transform('{"foo": "bar"}', &json_transformer)
p b
require 'digest'
md5transformer = ->(z) { Digest::MD5.hexdigest(z) }
c = transformer.transform('Hello', &md5transformer)
p c
Summary
In this chapter, we used dependency injection to vary the implementation for
transforming a given string. We improved the design by going from
Localized Change to Additive Change. The examples used in this article is
based on SOLID Review: Dependency Inversion Principle.
http://www.runtime-era.com/2015/04/solid-review-dependency-inversion.html
Coupling Basics : Dependency
Direction
Inversion of Control Principle
The Hollywood Principle is another name for Inversion of Control Principle.
Hollywood Principle:
Don't call us, we'll call you.
Martin Fowler says:
IoC is about who initiates the call. If your code initiates a call, it is not
IoC, if the container/system/library calls back into codethat you
provided it, it is IoC.
Example #1 for Hollywood Principle:
class Car
 def initialize(name)
 @name = name
 end
 def to_s
 "My name is #{@name}"
 end
end
c = Car.new('Tesla')
print c
This prints:
My name is Tesla
We did not explicitly call to_s in our code. The to_s method is called by
print.
Example #2 for Hollywood Principle:
In a Rails app:
def new
 @user = User.new
end
You implement the action in the controller, the framework calls this method.
You as a programmer never call the new action in your code.
Why Apply Inversion of Control?
Inversion of Control principle helps us to achieve loose coupling thereby
allowing us to achieve re-use in our projects. It is one of the ways to achieve
Context Independence.
Summary
In this article you learned about the Inversion of Control Principle and
examples illustrating their use. The most important take away is that
Inversion of Control is about DIRECTION of messages and it can be used to
achieve Context Independence.
Concrete Class vs Abstract Messages
In this chapter, you will learn about depending on abstract messages instead
of concrete classes to write re-usable code.
Steps
Step 1
Create copy.rb :
while line = gets
 puts line
end
Step 2
Run it.
$ruby copy.rb
Here is a sample run:
Hi
Hi
Hello
Hello
Press Ctrl+D to quit the program. This reads from keyboard and writes to the
console.
Step 3
Create a readme.txt file:
This is the first line
This is the second line
Now run the program as follows:
$ruby copy.rb readme.txt
This reads the readme.txt from the file and writes it to the console.
Step 4
Create file_copy.rb :
File.open('./readme.txt', 'r') do |file|
 while line = file.gets
 puts line
 end
end
Run it.
$ruby file_copy.rb
This reads the readme.txt and writes it to the console.
Step 5
Create abstract.rb with:
require 'stringio'
ip = StringIO.new('This is a test')
op = StringIO.new('', 'w')
ip.each_line do |line|
 op.puts line
end
print op.string
This program uses StringIO a fake file system to read and write. This is good
for testing. The dependency is on the message: each_line and puts and not
on any concrete class. If there is a name of the class in the code, it creates
tight coupling in the code and makes it difficult to reuse. As long as the ip
and op variables can respond to the each_line and puts messages. This
program will work.
Depend on messages that capture abstractions which can be varied by
having different implementations.
Summary
In this chapter, we saw examples for:
Standard In --> Standard Out
Keyboard --> Console
File --> Console
Fake File --> Fake Console
You learned that depending on a concrete class creates tight coupling and
depending on messages that can be implemented by different
implementations in different class can lead to re-usable code.
Flexible Design
Software does not exist in a vacuum. It interacts with environment and the
environment interacts with it. The environment is market forces, users,
external systems, operating systems, competing software, changes in law etc.
It evolves, either it improves or decays over time. The only thing that is
constant is change demanded by the environment.
The Law of Change: The longer your program exists, the more
probable it is that any piece of it will have to change. Max Kanat-
Alexander in Code Simplicity book
You need to work on a existing code base in order to :
 
1. Improve Performance
2. Improve the Design
3. Fix Bugs
4. Enhance existing features
5. Upgrade to newer software it depends on
6. Add features
7. Remove features
How do we evaluate a design that will make the software easy to do all of the
above? If we order from the most to least desirable ways to achieve quality,
they are:
 
1. Data driven or meta-programming
2. Additive change
3. Localized modification.
Just because meta-programming is on the top of the list, I am not advocating
that is the first choice for every design problem. As long as meta-
programming is used to achieve a good design that obeys good design
principles, it is ok. Remember the two golden rules from Code Simplicity
book:
 
It is more important to reduce the effort of maintenance than it is to
reduce the effort of implementation.
The effort of maintenance is proportional to the complexity of the
system.
Localized Modification
According to the dictionary, localized means restrict something to a particular
area. We change only one specific location in our existing code base to
implement a new feature. The changed code must be deployed. Before you
can run the example code, you need to install highline and clipboard gems.
$gem install highline
$gem install clipboard
Here is the localized modification version of the password recall script:
#!/usr/local/bin/ruby
require 'digest/sha1'
require 'highline/import'
require 'clipboard'
def unlock_password(account, domain)
 salt = ask("Enter your secret key : ") do |q|
 q.echo = false
 q.verify_match = true
 q.gather = {"Enter your secret key" => '', "" => ''}
 end
 password = Digest::SHA1.hexdigest(domain + account + salt)
 Clipboard.copy(password) 
end
choose do |menu|
 domain = ask("Enter the website : ")
 menu.prompt = "Please make a selection : "
 menu.choice :yahoo do 
 unlock_password('email', domain)
 say("Yahoo password copied.") 
 end
 menu.choice :google do 
 unlock_password('email', domain)
 say("Gmail password copied.") 
 end
 menu.choice :microsoft do 
 unlock_password('email', domain)
 say("Live password copied.") 
 end
end
Here we modify just one file and we add a new site to menu.choice call.
Additive
We add new code to the existing system without modifying the existing code
to implement a new feature. Risk of introducing bugs to existing code is very
low. New code must be deployed. This will use polymorphism so that the
new object introduced will have the same interface that the existing code
uses.
Here is an example from Rails Antipatterns by Chad Pytel and Tammer
Saleh book that I have improved the design by moving it from localized
modification to additive change.
Before (Solution in the Book)
class OrderConverter
 def initialize(order)
 @order = order
 end
 def to_xml
 end
 def to_json
 end
 def to_csv
 end
 def to_pdf
 end
end
oc = OrderConverter.new(order)
oc.to_xml
This solution needs localized changes to add a new conversion format for the
order.
After (My Improved Solution)
Here is my solution that allows additive changes:
class Order
 attr_reader :amount, :number
 def initialize(amount, number)
 @amount = amount
 @number = number
 end
end
class OrderXmlConverter
 def initialize(order)
 @order = order
 end
 def convert
 "<order><amount>#{@order.amount}</amount><number>#{@order.number}</number>
</order>"
 end
end
Instead of hard-coding class name, you can use const_get to dynamically
instantiate a class:
order = Order.new(19, 2)
format = 'Xml'
class_name = Object.const_get("Order#{format}Converter")
converter = class_name.new(order)
puts converter.convert
In rails, you can use constantize method:
class_name = "Order#{format}Converter".constantize
By following a convention in naming the converter class, we eliminate
dependency on a specific class name. In order to add a new format, for
instance json, we add a new class OrderJsonConverter which has the same
interface convert that returns JSON representation. Uniform interface allows
additive change. We end up with small classes that is focused on doing one
thing really well, they all have the same interface, convert in our example.
Data Driven
New data is added to make the system implement a new feature. This is the
most flexible design. Probably this design will demand the highest effort of
implementation. No code deployment necessary. The example below requires
reading the value of the array items from an external configurationfile.
#!/usr/local/bin/ruby
require 'digest/sha1'
require 'highline/import'
require 'clipboard'
def unlock_password(account, domain)
 salt = ask("Enter your secret key : ") do |q|
 q.echo = false
 q.verify_match = true
 q.gather = {"Enter your secret key" => '', "" => ''}
 end
 password = Digest::SHA1.hexdigest(domain + account.to_s + salt)
 Clipboard.copy(password) 
end
choose do |menu|
 domain = ask("Enter the website : ")
 menu.prompt = "Please make a selection : "
 # This is hard-
coded. You must read the values of the list from an external configuration file to make it data driven that does not require source code changes to add a new site.
 items = [:yahoo, :google, :microsoft]
 items.each do |item|
 menu.choice item do 
 unlock_password(item, domain)
 say("#{item.to_s} password copied to clipboard.") 
 end
 end
end
Design Techniques
What are the design techniques to achieve these three kinds of design?
 
1. Localized changes are better than changes that ripple across your code
base.
2. Additive changes use polymorphism, meta-programming etc. It obeys
Open Closed Principle if properly designed. New code is added with no
modification to existing code.
3. Data driven technique obeys Open Closed Principle. No change is made
in existing code. No new code is added.
Summary
In this chapter we saw three different kinds of design that gives different
levels of flexibility. Sometimes you have to make a trade off between
complexity and flexibility. You can recognize these different types of
flexibility in your code and make decisions based on your current
requirements.
Reference
Rails Antipatterns by Chad Pytel and Tammer Saleh
Open Closed Principle
In this chapter, you will learn about Open Closed principle. Let's consider the
FizzBuzz problem to learn how to apply the Open Closed Principle. FizzBuzz
requirements:
- For multiples of 3, print Fizz
- For multiples of 5, print Buzz
- For multiples of 3 and 5, print FizzBuzz
Steps
Step 1
Define classes to implement the above requirements:
class Fizz 
 def value(n)
 if n % 3 == 0
 'Fizz'
 end
 end
end
class Buzz
 def value(n)
 if n % 5 == 0
 'Buzz'
 end
 end 
end
class FizzBuzz
 def value(n) 
 if n % 15 == 0
 'FizzBuzz'
 end
 end
end
Step 2
One of the requirement is implicit, because numbers that is not multiple of 3,
5 or 15 should not be transformed. So we need a NoOp class:
class NoFizzBuzz
 def value(n)
 n
 end
end
So far, we have the concrete classes that implement the FizzBuzz logic.
Notice that we have a uniform interface value(n) that allows clients to
program to an interface and not to an implementation. You will see this in
action in upcoming steps.
Step 3
Define FizzBuzzGenerator class that will delegate the FizzBuzz generation
to the concrete classes.
class FizzBuzzGenerator
 def initialize(objects, list)
 @list = list
 @objects = objects
 end
 def generate
 result = []
 @list.each do |num| 
 @objects.each do |l|
 v = l.value(num) 
 unless v.nil?
 result << v
 break
 end
 end
 end
 result
 end
end
Notice that the dependency is on the message value(num). There is no
dependency on the name of a class. So we don't have any references to Fizz,
Buzz, FizzBuzz or NoFizzBuzz classes. This class is open for extension and
closed for modification. This means we can add more concrete classes such
as Fazz that returns multiples of 7 as Fazz, if such a new requirement arises
without modifying this class and extend the functionality.
Step 4
Finally, here is the test run:
objects = [FizzBuzz.new, Fizz.new, Buzz.new, NoFizzBuzz.new]
g = FizzBuzzGenerator.new(objects, (1..20).to_a)
r = g.generate
puts r
Discussion
The list of concrete classes (objects), needs to change only when new
concrete classes are added. Deploying new feature requires additive changes.
This means we add new concrete classes and an instance of that object to the
objects array. The generator class does not require any modification to the
existing code. This results in a flexible and easy to maintain code base. In our
solution, notice that we don't have any if-else-elsif statements. If your
solution used if-else-elsif then it would require Localized Changes and it
would not be Additive Change.
There is a subtle dependency between the FizzBuzzGenerator class and the
order of the objects in the test run code. The correct generation of the
FizzBuzz sequence depends on the order of objects. This is a quick-and-dirty
implementation of Chain of Responsibility pattern. However this example
was chosen to illustrate the Open Closed Principle. If the concrete classes
have business logic that can be implemented by passing through a chain of
handlers independent of the order in which they are executed, this solution
would shine. Because, in that case, there would be no dependency on the
order of the handlers in the objects array.
Exercise
In order to understand the concepts explained in this article, implement the
feature where you must print Fuzz for multiples of 7. What are the changes
required to satisfy the requirement?
Summary
In this chapter, you learned about the Open Closed Principle and how to
apply it by working through FizzBuzz example.
Table of Contents
Introduction
Basics of Abstraction
Single Purpose Principle
Stepwise Refinement
Dependency Inversion Principle
Basic Three Rules of Design
The Art of Uniform Interface
Localized Change vs Additive Change
Coupling Basics : Dependency Direction
Concrete Class vs Abstract Messages
Flexible Design
Open Closed Principle
	Introduction
	Basics of Abstraction
	Single Purpose Principle
	Stepwise Refinement
	Dependency Inversion Principle
	Basic Three Rules of Design
	The Art of Uniform Interface
	Localized Change vs Additive Change
	Coupling Basics : Dependency Direction
	Concrete Class vs Abstract Messages
	Flexible Design
	Open Closed Principle

Outros materiais