Object-Oriented Warmups II

A series of exercises designed to get your object-oriented muscles working properly.

Scroll down...

Content

Resources

Comments

Before we get into the heavy lifting of building "real" projects with OOP, let's make sure you've picked up a few of the key concepts by building a few warmups.

Getting Started

This assignment will be stored on Github. To get started, follow the same steps as before:

  1. Fork this project's repo on Github
  2. Add your name to the README, commit, and push to your fork.

Warmup 1: Interfaces

The "Interface" of a class is that portion of the class which is publicly exposed. That's because this is the only way that other classes can interact with this particular class.

Good engineering principles seek to make classes as independent from each other (loosly coupled) as possible. A good way to enforce this is by making sure your classes have "narrow" interfaces, meaning that very little is actually exposed to the outside world.

To get some quick practice thinking about this concept:

  1. Create a new file called "ttt_interfaces" in the current project.
  2. Go back to the Tic Tac Toe demo and write down the interface for each of the classes it uses. This means indicating which methods and variables are publicly available and how to call them. Remember that an attr_reader exposes a different method from an attr_writer, while attr_accessor exposes both.

For example:

# The class definition
class Foo

    attr_reader :bar
    attr_accessor :bim

    def initialize
        @bar = "something"
        @zip = 123
        @thing = Thing.new
    end

    def do_something(some_input)
        ...
        do_private_things
        ...
    end

    private

    # NOT part of the interface
    def do_private_things
        ...
    end
end


# One way to write the interface
# (the `new` is implied)
Class: Foo
    bar                         # from the attr_reader
    bim                         # from the attr_accessor
    bim=(new_value)             # from the attr_accessor
    do_something(some_input) 

Warmup 2: Deep Dup

Pass-by-reference is an important concept. Create a method deep_dup which returns a completely new object on a 2-or-more dimensional array. Ruby's simple dup method will be useful but not the only thing you use.

For example:

arr = [ [1, 2], [3, 4] ]

# Verify their object_ids
arr.object_id
#=> 70349854069800
arr[0].object_id
#=> 70349854069960
arr[1].object_id
#=> 70349854069840

duper = arr.deep_dup

# Verify they are different ids
duper.object_id
#=> 70349854047610
duper[0].object_id
#=> 70349854041100
duper[1].object_id
#=> 70342030583020

deeper_arr = [ 
                [ 1, [ 2, 3 ] ], 
                [ 4, 5, 6], 
                [ [ 7, 8, [ 9, 10 ]], [11, 12] ], 
            ]

# ... make that work too!

Warmup 3: Including Enumerable

Imagine that you are building an application that needs to keep track of "Tweets" which will later be sent by using the Twitter API. To do so in an object-oriented fashion, it makes sense to make a special Tweeter class which contains all of the Tweet-related functionality.

Because that class will handle lots of individual tweets, we can assume that an instance of Tweeter is really just a wrapper around a collection of these tweets. Our rough class might look something like this:

class Tweeter
    def initialize
        @tweets = []
    end

    def tweet(message)
        # Your TODO: fill this in. 
        # This should add the first 144 characters
        # of any message to the @tweets array
    end
end

Instead of making an accessor for @tweets and accessing it directly from outside of the Tweeter instance, we can treat the Tweeter instance as a collection itself.

To do so, your task is:

  1. Define an each instance method on Tweeter which iterates through all the tweets inside its @tweets collection.
  2. Include the Enumerable module into the Tweeter class.

You should now have access to all the Enumerable methods directly on any instance of Tweeter. Specifically, you should now be able to do the following:

t = Tweeter.new

# Add some tweets to our @tweets array
t.tweet("first message")
t.tweet("second message")

# Use Enumerable methods on the Tweeter instance
t.each{|msg| puts msg}
#=> first message
#=> second message

# Note: You should NOT have defined this method explicitly!
#   ...it should come from Enumerable by default.
t.map{|msg| msg.upcase}
#=> ["FIRST MESSAGE", "SECOND MESSAGE"]

This is a common thing! Often when you have a collection that isn't quite as simple as a standard Array, you'll still want to be able to iterate over it using all your trusty Enumerable methods. That might be true if your object is a collection of Tweets like in this example but it's just as applicable if you've designed more complex User objects and you want to be able to easily iterate over collections of them.

Finishing Up

  1. When you're finished with all tasks, push your changes up to your fork (aka $ git push origin master).
  2. To submit your assignment, create a pull request from your fork to the main upstream repository.
Pull request 300 Octocat 300


Sign up to track your progress for free

There are ( ) additional resources for this lesson. Check them out!

There are no additional resources for this lesson just yet!

Sorry, comments aren't active just yet!

Next Lesson: Serialization