I am still trying in wrap my head around OOP in Ruby. Let's say I'm trying to create a simple Hangman game and I want to select a random word from a text file. So far I have 2 examples in the codeblock. The first example shows a Word and Game class, where the Word class generates a random word and the Game class calls the Word class in the initialize method. The second example has only a Game class where the Game class itself generates the random word. My question is, is it the Game classes responsibility to generate the random word or use the Word class?
# First Example
module Hangman
class Word
def self.words
File.readlines("../words.txt")
end
def self.random
words.select { |word| word.length > 4 && word.length < 13 }.sample
end
end
class Game
attr_reader :random_word
def initialize
#random_word = Hangman::Word.random
end
end
end
# Second Example
module Hangman
class Game
attr_reader :words, :random_word
def initialize
#words = File.readlines("../words.txt")
#random_word = #words.select { |word| word.length > 4 && word.length < 13 }.sample
end
end
end
Sandi Metz has a great example of how to answer this question in Practical Object Oriented Design in Ruby. Sadly, since it is copyrighted I can't link directly to the passage.
In her example, although a bicycle seems like a good candidate for a class due to its obvious existence in the problem domain, at the point in the development of her application that she needs to compute a gear ratio, she realizes that the inputs to that calculation are all related only to gears: the number of teeth on each of two instances of Gear, and thus places the functionality on Gear, deferring the creation of the Bicycle class until a later time.
So the general answer is: look to the input values for the required computation and place that computation's definition on the class with the greatest number of those input values as fields already.
In your specific case:
is it the Game classes responsibility to generate the random word or use the Word class?
Well first off, it seems like your Word class is more of a WordList class, although depending on your future direction, it could remain a Word class but in embodiment of the composite pattern . If you do keep it as a WordList class, it has no instance methods, so discussing responsibilities of the class becomes very difficult. Effectively the class itself has methods, but the class is always expected to be at "singleton instantiation scope" or a constant. Ruby class names are constants, so defining methods only at the level of constants is effectively procedural, not object-oriented, code.
To make WordList object-oriented, you can pass an IO instance (File is a subclass, but why depend on a subclass whose additionally defined methods are not needed by your code?) to WordList#initialize , potentially providing singleton access with a
def self.singleton_instance
#singleton_instance ||= new(File.open("../words.txt"))
end
This allows other clients to reuse the WordList class in other contexts by providing any kind of IO, including a StringIO, and separates and makes explicit that loading the default, singleton WordList is only one way this class expects to be used, requires the constant-scope level file from the parent directory, and allows the instance-level behavior of a WordList to be defined.
So far it looks like that instance-level behavior you need is a random selection from all the words. Getting back to Sandi Metz's advice, WordList does seem like a good place to put the computation of the random selection, because WordList will have a field:
attr_reader :words
def initialize(io)
#words = io.readlines
end
and it's exactly the words field that the filtration is to be performed upon, so this class is a good candidate for that functionality:
def random # notice no self. prefix
words.select { |word| word.length > 4 && word.length < 13 }.sample
end
and later, to actually-use,
#random_word = Hangman::WordList.singleton_instance.random
This also gives you a place to swap-out the singleton instance for a different one if you need to later, without changing the WordList class. That should score points for complying with the Open Closed Principle too.
(An aside: it seems that "random" may be a poor choice for method name -- it's not just random, but also constrained to a length of between 4 and 13 exclusive. Perhaps "random_suitable_length_word"?)
In general it depends.
For this specific case I think most people would agree that splitting the structure between Word and the Game is a good idea.
Word is a nice small testable piece, and so it does deserve its own class.
It also could be reusable in a number of games that need a random word.
I think this becomes clearer if you rewrite word so it has an initialize method. Then the game is simply calling Word.new(...) to get a new random word.
Imagine if there was a gem called "words" that already did all this. You would be happy add the gem and say done deal. Well that is an easy way to tell you have made a good division of labor, even no such gem exists.
By the way once you think this should be a separate class, you might want to check to see if somebody already did it for you. In this case there is a gem random-word.
What would the parameters be to words initialize? Well the length of the word, skill level, etc etc.
class Word
def self.words
#words ||= File.readlines("../words.txt")
end
def initialize(min_length, max_length)
Word.words.select do |word|
word.length > length && word.length < max_length
end.sample
end
end
Related
Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 8 years ago.
Improve this question
I'm aware that ruby class files should generally be quite small, but sometimes I find logic in classes that (a) don't really warrant their own file, but (b) would be more readable if I could group them somehow.
For example, in certain classes I may have (let's say) 10 methods which determine paths. At the moment, I tend to just throw some a few lines of decorative "### PATHS ###" type comments around them to distinguish them from the rest of the group. I'm wondering if theres a better way(?)
Am I right in thinking implementing subclasses/modules within a class just to improve readability is a little excessive, or do other folks do this? Any other thoughts appreciated.
Maybe they do warrant their own file - Think single responsibility principle. Since you haven't shown us any code, here's an idea for a separate class that handles the paths for an object:
class FooPaths
def initialize(foo)
#foo = foo
end
def path_one
# code to calculate path using #foo
end
def path_two
#code to calculate another path using #foo
end
end
The idea is that your class Foo is probably doing too many things. Create smaller classes which accept #foo in the initializer, and then that smaller class can handle a smaller subset of operations involving Foo. It's also a lot easier to test.
When I have a bunch of similar helper methods, that means one of two things.
I have a business concept (read: object) that needs to be defined
I have a bunch of business concepts that act in a similar way.
For the first case, very often, the first argument passed into the method is the name of the business concept
class User < ActiveRecord::Base
# active record attributes :first_name, :last_name
def self.full_name(first_name, last_name)
[first_name, last_name].compact.join(" ")
end
def self.extract_first_name(full_name)
full_name.split.first
end
end
So I would create a concept for full_name:
class User << ActiveRecord::Base
# active record attributes :first_name, :last_name
def full_name
FullName.from_parts(first_name, last_name)
end
def full_name=(val)
if val
self.first_name, self_last_name = val.partsfirst_name, val.last_name
else
self.first_name = self.last_name = nil
end
end
end
class FullName
attr_accessor :first_name, :last_name
def to_s
parts.join(" ")
end
def parts
[first_name, last_name].compact
end
def eq(other)
to_s.eq(other)
end
def self.from_parts(first_name, last_name)
# [...]
end
def self.from_full_name(full_name)
# [...]
end
end
I know the example is contrived, but I've found that a lot of view logic and helpers groups around this concept. So if you end up finding more concepts that can be brought into this, or you notice that you often pass these around in groups, then it is a great candidate. Also, if you have had a number of conversations with the business users around the full name of a user, then that is really saying that you want a business concept here.
For the second case, it is sometimes the case that you want to add "the same" attribute onto a bunch of disparate business objects. They just happen to be calculated in different ways. The path for a chapter book and a dictionary are different. Introduce the concept of a chapter book and a dictionary, both having the path parameter. As stated above, if you find that you are not using these business concepts often with your users, chances are, this is not the right approach here.
I'm learning Ruby through 'Learn to Program' by Chris Pine. I'm trying to cope without global variables. I have a sloppy looking command prompt blackjack program (can't split yet) My first three methods illustrate the problem I'm having. In Pascal I would have used global variables, and used methods to manipulate those global variables. It would look something like this (using ruby pseudo language)
$deck_of_cards = []
$player_hand = []
$dealer_hand = []
def create_deck
$deck_of_cards = #numbers 1-52
end
def shuffle_deck
$deck_of_cards = #shuffled up deck of cards
end
def opening_deal
2.times do
$player_hand.push $deck_of_cards.pop
$dealer_hand.push $deck_of_cards.pop
end
end
create_deck
shuffle_deck
opening_deal
and I would end up with a shuffled deck, a player hand, and a dealer hand.
Taking away global variables, the first two methods now look like this:
def create_deck deck_of_cards
deck_of_cards = #numbers 1-52
return deck_of_cards
end
def shuffle_deck deck_of_cards
deck_of_cards = #shuffled up deck of cards
return deck_of_cards
end
deck = create_deck([])
deck = shuffle_deck(deck)
which feels a little odd but I could get used to it.
But I'm completely stuck on the last method, which would need to return two variables, player_hand and dealer_hand. I could push those two arrays into another array and return that, but that doesn't seem like it's simplifying anything.
Is there a general strategy for dealing with this, or does each situation have its own unique solution?
Another situation that comes to mind is a chess program. Why would it not make sense to make the chess board a global variable since almost all methods in a chess program would need to use it's value?
The short answer: Objects. Object oriented programming allows you to encapsulate data so that it doesn't pollute the global namespace. As your projects grow larger, it becomes unmaintainable to have all your data in the global namespace. Objects allow you to group data and methods into meaningful units, which can then be re-used and maintained more easily.
So for instance with the example you provided we could make deck and hand objects. This then allows us to create multiple deck and hands easily because these objects encapsulate and manage their data.
As a rough outline of how you might organize your classes / methods:
class Deck
def shuffle
end
def draw_hand
end
end
class Hand
end
class Card
end
In general, using object oriented programming techniques you would define classes and create objects of those classes and then pass around those objects. You can think of objects as sort of like arrays in that they can contain lots of related data, but better than arrays in that the data elements can have names and can be any type and do not need to be the same type and they come with named functions for operating on the data. (Actually, in Ruby, Array elements can be any type and do not need to be the same type, and the same is true of Hash elements plus Hash elements have names, but Ruby is special that way. Few other languages allow that.)
Keeping it simple, though, you can continue your program like this:
def opening_deal deck_of_cards
player_hand = []
dealer_hand = []
2.times do
player_hand.push deck_of_cards.pop
dealer_hand.push deck_of_cards.pop
end
return player_hand, dealer_hand
end
player_hand, dealer_hand = opening_deal deck
It is a bad habit to have global variables, but it is not bad to have global constants, or in other words, constants defined in the main name space. When in need of a global variable, you can always define a constant in the main name space. Mutable objects like string, hash, array can be modified even if it is a constant. If that is not enough and you need more complicated things, then you can define a module (which is a special case of constants), and define methods on that module to have access to the things you want to do, and encapsulate the complicated information inside that module using instance variables.
When you need to perform several methods on an object, which solution would you prefer?
The code should speak for itself:
class Foo
def self.build
foo = new
foo.first
foo.second
foo
end
def first
end
def second
end
end
class Bar
def self.build
new.first.second
end
def first
self
end
def second
self
end
end
I think it depends on how many times you expect to be calling these methods. Will they be called together quite often or just once here? If it's just once I'd go with the 2-line approach because it's not needlessly passing self in the return. If it's happening often then make another method which calls them both in succession.
It is a tradeoff. The answer depends on (1) the importance and (2) the complexity of the surrounding code. You are the one who, as a programmer, has to make the decision. The piped form is shorter, saves vertical space. The sparse form is easier to debug. Depending on how you choose your method keywords, inline form may read like a human sentence, but in other cases, sparse form can be more human readable. If your methods are under heavy development, use the sparse way of writing. For stabilized code, piped form is O.K. For less important code, such as tests, I like to use the piped form. Actually, I like it so much that I even omit the dots, so my FizzBuzz program looks like this, using my custom library that allows dense dotless one-character method double-barell piping:
(1..100).τᴍ⁒i⁇χ( 3, 0, "fizz", nil ).πᴍ⁒i⁇χ( 5, 0, "buzz", nil ).πγZᴍjτ
So remember, one line piping is nothing, programs like the one above are what they mean when they say "really bad style" in Ruby :)))
It is possible to define a prototype of a function or in some way indicate to Ruby that a function exists even though it may not be defined yet?
I have lots of classes like this:
class Program
FIRST = Block.FIRST
FOLLOW = Set.new['$']
end
class Block
FIRST = Declaration.FIRST
FOLLOW = Set.new['.']
end
class Declaration
FIRST = ConstDecl.FIRST + VarDecl.FIRST + ProcDecl.FIRST
end
class ConstDecl
FIRST = Set.new['const'] + EMPTY_SET
end
Which as you can see reference fields from classes that are defined below them, Is there a way to indicate to Ruby that these classes exist, and ask Ruby to look for them?
The simplest way I can think of is something like this:
class Program
def self.first; Block.first end
def self.follow; Set.new['$'] end
end
class Block
def self.first; Declaration.first end
def self.follow; Set.new['.'] end
end
class Declaration
def self.first; ConstDecl.first + VarDecl.first + ProcDecl.first end
end
class ConstDecl
def self.first; Set.new['const'] + EMPTY_SET end
end
This doesn't seem like good design to me, though, I'd probably make those objects instead of classes and use a proper type hierarchy.
A major difference between Ruby and other languages which you may be accustomed to (like C/C++) is that before execution, a C/C++ program is processed by a compiler which matches up uses of variables/functions to their definitions. Ruby programs are simply executed from top to bottom, one statement at a time. So when a line which references Block.FIRST is executed, the Ruby interpreter can't "look forward" in the program code and see what value will be assigned to Block.FIRST later. It knows nothing about what will come later; it only knows what it has executed so far.
Perhaps one of the strongest characteristics of Ruby is that almost everything is dynamic and can be changed at run-time. If you are coming from a C/C++ background, this is the first thing you need to get your head around to understand Ruby. For example, constants in Ruby can be assigned conditionally:
class Block
if rand % 2 == 0
FIRST = '.'
else
FIRST = '$'
end
end
If the Ruby interpreter was required to "look forward" to see what the value of Block.FIRST should be, what should it predict in the above case?
This is a conceptual shift from what you are used to, and it will require you to structure your programs in a different way, and think about your programs in a different way. If you try to write C/C++/Java in Ruby, you will be fighting all the way.
In this case, I recommend you simply reverse the order of your definitions and go "bottom-up". There are other ways to achieve the same effect, but that is the simplest one.
you can try defined?(function_name)
I created a program that tracks car mileage and service history in order to update the user for upcoming service needs for the car.
I have three classes: Car, CarHistory, and CarServiceHistoryEntry. The third one is straightforward; it holds all the attributes associated with a service: date, mileage, service performed, etc. The CarHistory class is as follows:
require_relative 'car_service_history_entry'
class CarHistory
attr_reader :entries
def initialize (*entry)
if entry.size > 1
#entries = []
else
#entries = entry
end
end
def add_service_entry entry
#entries << entry
end
def to_s
entries_string = ""
#entries.each {|entry| entries_string << "#{entry.to_s}\n"}
entries_string
end
end
In initialize, should the class of entry be checked?
In add_service_entry, adopting duck typing (as in Andy Thomas's argument in "Programming Ruby"), would I even test if a CarServiceHistoryEntry could be added? Couldn't I just pass a String instead of setting up and then adding CarServiceHistoryEntry in my unit testing?
Since the only necessary attributes of a CarHistory are the entries array and the to_s method, should I just scrap this class all together and put it into the car class?
For 1 and 2, you need to release your tight grip on "strict-typing" when you move to a loose-typed language like Ruby.
Should you check your input arguments ? The traditional answer would be yes. An alternative way would be to have good names and unit tests that document and specify how the type is supposed to work. If it works with other types, fine.. that's an added bonus. So if you pass in an incompatible type, it would blow up with an exception, which is good enough in most-cases. Try it out and see how it feels (possible outcomes: Liberating / "Retreat!". But give it a fair try.). Exceptions would be if you're designing public APIs for shared libraries - in which the rules are different. You need to fail fast and informatively for bad-input.
As for clubbing car_history into car - I'd ask what the responsibilities of your Car class are. If maintaining its own history is one of them, you could club them. In the future, if you find a lot of methods creeping in related to car history, you could again reverse this decision and extract the CarHistory type again. Use the SingleResponsibilityPrinciple to make an informed decision. This is just OOP - Ruby doesn't degrade object design.
Code Snippet: the code can be more concise
# just for simplicity, I'm making HistoryEntry a string, it could be a custom type too
class CarServiceHistoryEntry << String
end
class CarHistory
attr_reader :entries
def initialize(*history_entries)
#entries = history_entries
end
def add_service_entry(entry)
#entries << entry
end
def to_s
#entries.join("\n")
end
end
irb>x = CarHistory.new("May 01 Overhaul", "May 30 minor repairs")
irb>x.add_service_entry("June 12 Cracked windshield")
irb>x.to_s
=> "May 01 Overhaul\nMay 30 minor repairs\nJune 12 Cracked windshield"
It's hard to comment on the relationship of the CarHistory class to your others, but I'm sure it will become clear to you as you go along.
A couple of your methods could be simplified, although I must say I didn't understand the if in initialize, perhaps it was just backwards and should have been > 0.
def initialize *entry
#entries = entry # if not specified it will be [] anyway
end
def to_s
#entries.join "\n"
end
And yes, Ruby should be simple. You don't need to litter your code with runtime type checks. If the code runs your unit tests then you can just declare victory. The zillions of explicit conversions tend to patch up type errors anyway.
Ruby is going to check your types at run-time anyway. It's perfectly reasonable to leave the type checking to the interpreter and put your effort into functional tests.
I'll skip the first two questions and answer the third. If the only attribute of a CarServiceHistoryEntry is a string, then yes, scrap CarHistory (as well as CarServiceHistoryEntry) and add a service_history attribute to Car which would just be an array of strings. Until proven otherwise, simpler is better.
As to duck typing, you would never want to test if something 'is a' only see if it 'responds to' (at most).
Finally, to answer question #1, no its supposed to be even simpler :)
Hope this helps,
Brian