Você está na página 1de 8

!"##$%& (#)*#"+ ,$#- ./01 2% .

)$34
5-)6#"* 7 89"*:$4" 7
; <$##3" =*):#$:" ,$#- ./01
("##$%& #-" (:"%"
We will uevelop the book stock example fiom the lectuies a little bit fuithei. As
mentioneu theie, this example is taken fiom: !"#$"%&&'($ *+,- ./0 1 2/3, by
Bave Thomas, publisheu by Piagmatic Piogiammeis. I have slightly mouifieu
anu extenueu it, but still stiongly iecommenu using the book itself foi fuithei
stuuy of Ruby:
http:piagpiog.combookiuby4piogiamming-iuby-1-9-2-u

; *">$%+"* 2? 2/* 4#)*#$%& 62$%#
In the lectuies we hau got as fai as a basic uefinition of a BookInStock class:
class BookInStock
attr_reader(:isbn)
attr_accessor(:price)

def initialize(isbn, price)
@isbn = isbn
@price = Float(price)
end

def to_s
"ISBN: #{@isbn}, price: #{@price}"
end

end

The fiist thing to uo is to use whichevei text euitoi you piefei using to save the
above coue in a foluei calleu "book_in_stock.ib".
0pen a commanu piompt oi teiminal winuow (uepenuing on which opeiating
system you aie using) anu change uiiectoiy to the foluei wheie the above file
has been saveu. Then open up Inteiactive Ruby (you shoulu iemembei how to uo
this fiom the lectuie).
Then loau the file you just cieateu. You can uo this fiom the iib by typing at the
piompt:
> load book_in_stock.rb

Now cieate two oi thiee new instances of BookInStock anu then piint theii
"inteinal states" out at the console (use "puts" to uo this).
Again, iefei to the lectuie if you aie unsuie about this.
A Little Piactice with Ruby
The piice look a bit funny as theie is no cuiiency. Bepenuing on youi piefeience,
euit the above file so that a "$" oi a "" sign is in fiont of the piice when it is
piinteu out. Now foi something cool. Reloau youi "book_in_stock.ib" file (simply
entei the above commanu again - you can uo this by using the "up aiiow" key to
iecall the commanu anu then hit entei). If you piint out the inteinal states of
youi book instances, you will see youi euit is ieflecteu in the iesult.
@A$*#/)3B ;##*$0/#"4
In the lectuies I intiouuceu the notion of an instance vaiiable (@isbn anu @price,
foi example). These captuie the '(45"(%6 74%45 of an object. Bowevei (in Ruby)
these instance vaiiables aie piivate to an object. Anu that is piivate in the veiy
stiict sense of Ruby - the only way anothei object (even one of the same class)
can ieau oi wiite to an instance vaiiable is thiough an accessoi methou. We can
wiite these methous explicitly if we wish, oi we can ueclaie them using
attr_reader oi attr_accessible as above.
What we have now is a way in which the exteinal woilu can access anu
manipulate the state of an object. Because of the naming conventions useu
within Ruby, the names of the exteinally visible entities aie the same as those of
the instance vaiiables in this case:
my_book.isbn
my_book.price = new_price

To make this inteinalexteinal uistinction cleai, the exteinally visible entities
aie iefeiieu to as the "attiibutes" of an object. It happens that each attiibute of a
BookInStock maps onto an inteinal vaiiable (with the same name). Bowevei,
this neeu not necessaiily be the case, as we will now see.
Suppose now that I want to iefine my mouel so that it is possible to ietain a
iecoiu of the oiiginal publishei's piice of a book, but allow the shelf piice to be
uiscounteu by a ceitain peicentage. To uo this, mouify the coue so that it looks
like this (changes in !"#$):
class BookInStock
attr_reader(:isbn)
attr_accessor(:discount)

def initialize(isbn, price)
@isbn = isbn
@price = Float(price)
@discount = 1.0
end

def shelf_price
@price * @discount
end

def to_s
"ISBN: #{@isbn}, price: #{@price}"
end

end

A Little Piactice with Ruby
Now, although piice is still an instance vaiiable, it is no longei an %44"',+45 of a
BookInStock object. In contiast, shelf_piice is an attiibute of BookInStock but it
uoes not uiiectly map onto an instance vaiiable; in that sense it coulu be iefeiieu
to as a 8'"4+%6 attiibute.
0nce you have maue the above changes, it is safest to quit the iib anu then
ieopen it. Then loau BookInStock's file again.
Cieate a new instance of BookInStock.
Now, see if you still have access to piice as an attiibute. Entei:
> my_book.price

This will geneiate an eiioi. But you uo have access to shelf_piice:
> my_book.shelf_price

You can even assign values to viitual attiibutes. Nake the following mouification
(in bolu) anu loau the file again:
class BookInStock
attr_reader(:isbn)
attr_accessor(:discount)

def initialize(isbn, price)
@isbn = isbn
@price = Float(price)
@discount = 1.0
end

def shelf_price
@price * @discount
end

def shelf_price=(price)
@discount = price/@price
end

def to_s
"ISBN: #{@isbn}, price: #{@price}, discount: #{@discount}"
end

end

Now you can assign a new value to the shelf_price, anu the value of the
@discount instance vaiiable will be automatically upuateu to ensuie that the
inteinal state is consistent with the exteinal iepiesentation of the object's state.
Nake suie you tiy this out, anu piint the inteinal state of the object out to check
this!

As always, uo note that nothing magic evei happens - it is youi iesponsibility as
a piogiammei to make suie that the inteinal anu exteinal iepiesentations aie
consistent.

A Little Piactice with Ruby
This hiuing of the uiffeience between instance vaiiables anu calculateu values is
an impoitant piinciple of object-oiienteu softwaie engineeiing. As a
piogiammei, you aie fiee to change the way an object's inteinal state is
iepiesenteu as much as you like so long as the exteinally visible attiibutes aie
not changeu.
89"*:$4" 7
Nouify the to_s methou so that it piints out the shelf_price insteau of the
piice.
.")+$%& #-" C)#)
The next thing we want to uo is to be able to ieau in all the uata about oui books
fiom a file. We will use CSv files foi oui uata. Foitunately, Ruby pioviues suppoit
foi CSv with a veiy useful libiaiy anu we will use that to builu oui CsvReauei
class.
We will builu this class up inciementally. The fiist thing we want to uo is to open
a file anu then use each iow aftei the heauei to instantiate a new instance of
BookInStock. We will collect all this instances into an instance vaiiable calleu
BooksinStock (iemembei what I saiu about collections being conventionally
nameu as pluialisations of the name of theii instances). Cieate a new file calleu
"csv_ieauei.ib" in the same foluei as "book_in_stock.ib" anu entei the following
coue:
require 'csv'
require_relative 'book_in_stock.rb'

class CsvReader

def initialize
@books_in_stock = []
end

def read_in_csv_data(csv_file_name)
CSV.foreach(csv_file_name, headers: true) do |row|
@books_in_stock << BookInStock.new(row["ISBN"], row["Price"])
end
end
end

Fiist, we neeu to iequiie the csv libiaiy. We also neeu to iequiie
"book_in_stock.ib", anu I have useu require_relative on the assumption that
this file will always be in the same foluei as my new file.
The initialisation methou shoulu be cleai to you now, so I will not explain that.
The uefinition of read_in_csv_data takes a file name as paiametei (you will
neeu to pioviue the full path fiom the cuiient woiking uiiectoiy - so putting all
the csv files in the cuiient woiking uiiectoiy is simplest!). It then uses the CSv
class fiom oui libiaiy to ieau in successive iows fiom that file, with the
exception of the fiist iow which it uses as a heauei to set up the key:value hashes
that aie the inteinal iepiesentation of each iow.
A Little Piactice with Ruby
0nce a iow has been ieau, it is useu to instantiate a new instance of BookInStock
anu appenu that to the instance vaiiable (using anothei nice intuitive piece of
Ruby syntax).
!"#$%&'
We aie not going to uo any foimal testing foi a couple of weeks. But at this stage
it is woith infoimally (unit) testing youi class uefinition to make suie it uoes
what it says. To save you a bit of typing you can uownloau a ieauy piepaieu csv
file fiom heie:
https:ul.uiopboxuseicontent.comu2S49uuSSfile1.csv
Save this into youi cuiient woiking foluei.
Then in the iib type:
> load 'csv_reader.rb'
> csv = CsvReader.new
> csv.read_in_csv_data('file1.csv')

That shoulu woik without failuie if you have followeu the above caiefully. Soit
out the eiiois if theie aie any (ask if you aie stuck!). The tiouble is, you will not
know foi suie if it has actually built up the @books_in_stock instance vaiiable
coiiectly.
Exeicise:
The ieason foi not being able to easily access this instance vaiiable is that we
have not uefineu a coiiesponuing exteinally visible attiibute. 0thei than foi
testing this is not pait of the iequiieu public inteiface foi CsvReauei. A way to
hanule this is to oveiiiue the uefault to_s methou in oiuei to have it summaiise
the inteinal state of a CsvReauei object. Tiy anu uo this now. Then ieloau the
class anu check it has inueeu upuateu the inteinal state coiiectly.
(/>>$%& #-" 4#2:D E)3/"
Now we can auu the attiibute that we actually iequiie. This is
total_value_of_stock. Note that we will implement this as a calculateu (oi
viitual) attiibute, but that uoes not mattei to any client of the class. Foi
peifoimance ieasons if we aie going to access this attiibute many times with the
stock unchangeu then we might ueciue to change the implementation so that its
value is cacheu as an instance vaiiable. We can uo this without bieaking any
coue exteinal to the class. But foi oui puiposes the following coue is satisfactoiy.
0puate youi implementation by auuing the following methou anu ieloau the
class (auuitions in bolu):
def total_value_in_stock
sum = 0.0
@books_in_stock.each {|book| sum += book.shelf_price}
sum
end

Remembei that Ruby implicitly ietuins the evaluation of the last statement in a
methou uefinition. Bence we simply neeu to evaluate the sum befoie closing the
methou.
Exeicise:
A Little Piactice with Ruby
Ruby has a iathei nice, although obscuiely nameu, methou calleu inject that can
be useu to accumulate a value acioss membeis of a collection. The geneial
pattein is:
collection_instance.inject(init) {|acc,element| acc opp element2 }

The value init is useu to initialise the accumulatoi acc. The opeiatoi opp is
typically one of { +, *, << }. element2 coulu eithei be element itself oi an
attiibute of element.
See if you can use inject to ieuuce the above methou uefinition uown fiom thiee
lines to one.
52/%#$%& 022D :26$"4F 89"*:$4"
It woulu be useful to implement a seconu viitual attiibute - a iecoiu of the
numbei of instances in stock of books with a specific isbn.
def number_of_each_isbn
#...
end

I'm going to leave this foi you to uo as an exeicise, but heie aie a couple of hints.
A hash is a goou stiuctuie foi uoing this. In this case, we woulu use the isbn as a
key anu the numbei of books with that isbn as the coiiesponuing value. You will
neeu to iteiate thiough books_in_stock in a similai way to the pieceuing example
(but (#4 using inject) anu each time you finu a book of a specific isbn you shoulu
inciement its associateu value by 1.
Theie is a tiick you neeu to know, though. You will iemembei fiom the lectuies
that if you tiy to access the value of a key that has not so fai been explicitly
iecoiueu in a hash, Ruby will ietuin nil. This is not the behavioui we want in
this case as we neeu the staiting point foi the book counteis to be 0, not nil ("+"
is not uefineu as a methou on nil). Feai not, Ruby can soit this foi you. You can
specify the uefault value foi a value as follows:
book_counts = Hash.new(0)

Now tiy anu implement this methou youiself. It is not too uifficult, but I will
pioviue an answei in a sepaiate hanuout.
Nake suie you test that this is woiking coiiectly befoie moving on to the final
step.
C"?$%$%& #-" )663$:)#$2% ?$3"
No ieal thinking is neeueu now, pioviueu that eveiything you have uone so fai is
woiking. You can exit the iib anu cieate the main application class. What we
want to uo now is to be able to take a list of csv files fiom the commanu line anu
piocess them to piint out the total value of the books in stock. This shoulu all be
quite stiaightfoiwaiu. 0nly one thing neeus explanation. The ARuv vaiiable in
the coue below accesses the commanu line aiguments to cieate a list of csv files
that aie then piocesseu sequentially.
Cieate a file calleu "stock_stats.ib" in the same foluei as all the othei files anu
then euit it so that it contains the following contents:
A Little Piactice with Ruby
require_relative 'csv_reader'

reader = CsvReader.new

ARGV.each do |csv_file_name|
STDERR.puts "Processing #{csv_file_name}"
reader.read_in_csv_data(csv_file_name)
end

puts "Total value = #{reader.total_value_in_stock}"

You can copy uown a couple moie csv files if you like:
https:ul.uiopboxuseicontent.comu2S49uuSSfile2.csv
https:ul.uiopboxuseicontent.comu2S49uuSSfileS.csv

Now entei this at the commanu line:
$ ruby stock_stats.rb file1.csv file2.csv file3.csv

You shoulu see the piocessing message piinteu out foi each file, followeu by a
statement of the total value of the stock.
; 3$##3" +$4:/44$2%
Apait fiom a slight vaiiation in the BookInStock class, the content of all the files
(apait fiom the csv ones) is taken fiom Bave Thomas' book !"#$"%&&'($ *+,-
./0 1 2/3/
As mentioneu, please iefei to this book foi fuithei insights. Bowevei, theie is
one point I woulu like to uiscuss. Befoie intiouucing the uefinition of the
CsvReauei class, Bave Thomas states that whilst in 00 uesign the classes
noimally map onto exteinal entities, theie is "anothei souice of classes in youi
uesigns. These aie the classes that coiiesponu to the things insiue youi coue
itself." Be then intiouuces CsvReauei. Bowevei, I woulu asseit that this uoes
inueeu map onto an exteinal entity. Although I have ietaineu the same name in
the above in oiuei to make it veiy cleai that I have ieuseu his example, I
peisonally believe that this class is in fact a iepiesentation of the BookShop. It
has an inteinal instance vaiiable that maintains a catalogue of the books in stock,
anu two attiibutes that captuie impoitant aspects of the exteinal state of a shop:
the total value of books in stock, anu the numbeis of copies of books foi each
isbn in stock. It uoes not iepiesent anything about a Csv ieauei - the
read_in_csv methou is meiely a utility methou foi initialising the state of a
BookShop using a set of csv files. This issue will become cleaiei when we move
to Rails wheie we can uispense with the csv files anu builu a mouel that peisists
in a uatabase. Then we will see that a "BookShop has many BooksInStock" anu
we can maintain a clean iepiesentation of the inteinal anu exteinal views of the
state of a BookShop without neeuing the ieau_in_csv methou; Rails' Active
Recoiu will populate the mouels with uata in the backgiounu.
This is a fine point anu uo not woiiy about it too much at this stage. We will,
howevei, talk a lot moie about mouelling latei in the couise.

A Little Piactice with Ruby
Paul J Krause
Professor of Software Engineering
University of Surrey
12
th
October 2013

Você também pode gostar