<<

CSC 517 1

Access Control O-o languages have different levels of access control, e.g., private and public. Ruby provides three levels of access control –  Public methods can be called by anyone—no access control is enforced. Methods are public by default (except for , which is always private).  Protected methods can be invoked only by objects of the defining class and its subclasses. Access is kept within the family.  Private methods cannot be called with an explicit receiver—the receiver is always self. This means that private methods can be called only in the context of the current object; you can’t invoke another object’s private methods.

If a method is private, it may be called only within the context of the calling object—it is never possible to access another object’s private methods directly, even if the object is of the same class as the caller.

By contrast, if a method is protected, it may be called by any instance of the defining class or its subclasses. Can you think of a case where a private method would need to be called by another object of the same class?

class MyClass def method1 # default is “public” #... end protected #subsequent methods will be “protected” def method2 # will be “protected” #... end private # subsequent methods will be “private” def method3 # will be “private”

Week 2 Object-Oriented Design and Development 1 #... end public # subsequent methods will be “public” def method4 # and this will be “public” #... end end

Alternatively, you can set access levels of named methods by listing them as arguments to the access-control functions. class MyClass def method1 end # ... and so on public :method1, :method4 protected :method2 private :method3 end

Unbounded vs. subtype polymorphism In a statically typed o-o language like Java or C++, you can declare a variable in a superclass, then assign a subclass object to that type: public class Bicycle { protected int gear; public void setGear(int nextGear) { gear = nextGear; } } public class MountainBike extends Bicycle { protected int seatHeight; public void setHeight(int newSeatHeight) { seatHeight = newSeatHeight; } }

CSC/ECE 517 Lecture Notes © 2017 Edward F. Gehringer 2 CSC 517 3

public class BikeSim { public static void main() { ... Bicycle myBike = new MountainBike(); ... myBike.setGear(3); myBike.setHeight(5); } } Which statement is illegal in the code above? Why?

In most dynamically typed o-o languages, including Ruby, that statement would be legal. In Ruby, if a method is defined on an object, the object can respond to the message.

It doesn’t matter what class the object is declared as … in fact, the object isn’t declared!

This is called unbounded polymorphism—the polymporphism is not limited by the declared class of the object.

In contrast, statically typed o-o languages usually have subtype polymorphism—the compiler checks that the invoked method is defined in the type that the object is declared as.

Unbounded polymorphism is related to duck typing, which is discussed in next week’s online lectures. Abstract Methods Ruby does not have abstract methods like Java or pure virtual functions like C++. However the same functionality can be simulated as follows (similarly to subclassResponsibility in ): class Shape def draw

Week 2 Object-Oriented Design and Development 3 raise NotImplementedError.new( "Method not implemented") end end What will happen if we execute … s = Shape.new.draw

Subclasses, then, have to provide an implementation for the draw method. Why do you think that Ruby and other dynamic o-o languages don’t have an official “abstract method” construct?

Modules and

Modules in Ruby are a way to group together methods, classes and constants. They are similar to namespaces in languages such as C++.

Of course, whenever you mix namespaces, you have the possibility of name clashes.

Say you have a graphics library that contains classes for Window and (window.rb) and Border (border.rb).1

Both window.rb and border.rb have a top method, which gives the position of the top of the Window and the top of the Border, respectively.

An application programmer wants to lay out windows with borders, and thus loads both window.rb and border.rb into the program.

1 A considerably cornier example can be found in the Module chapter of Programming Ruby.

CSC/ECE 517 Lecture Notes © 2017 Edward F. Gehringer 4 CSC 517 5

What’s the problem here?

Ruby’s answer is the module mechanism.

Modules define a namespace. What’s a namespace?

The window functions can go into one module module Window def Window.top # .. end def Window.bottom # .. end end and the border functions can go into another module Border def Border.top # ... end def Border.width # .. end end

Look at the names of module methods. Given their syntax, what do they remind you of?

When a program needs to use these modules, it can simply load the two files using the Ruby require statement, and reference the qualified names. require 'window' require 'border'

Week 2 Object-Oriented Design and Development 5 trueTop = Window.top + Border.top

Ruby’s require, include, and load statements have similar functionality.  include makes features available, but does not execute the code.  require loads and executes the code one time (somewhat like a C #include).  load loads and executes the code every time it is encountered.

To understand their behavior, it’s important to realize that Ruby is a dynamic language—it supports not only dynamic typing, but metaprogramming: code can be written at run time (as we will see in Lecture 10).

Any idea how a C #include differs from a Ruby require?

Any idea how a C #include differs from a Ruby require?

Now, why do you think you might want to load code instead of require it?

Mixins The most interesting use of modules is to define mixins.

When you include a module within a class, all its functionality becomes available to the class.

The methods of the module become instance methods in the class that includes it.

CSC/ECE 517 Lecture Notes © 2017 Edward F. Gehringer 6 CSC 517 7

Let’s contrast this with #include in (what languages?) How are mixins different?

Let’s contrast this with in (what languages?). How are mixins different?

So, instead of implementing interfaces as in Java, one uses mixins in Ruby when one wants to .

Consider the following code: module Introspect def kind puts "This object is a #{self.class.name}" end end class Animal include Introspect def initialize(name) @name = name end end class Car include Introspect def initialize(model) @model = model end end = Animal.new("Cat") c = Car.new("Ferrari") d.kind # kind method is available through …

Week 2 Object-Oriented Design and Development 7 c.kind # .. the Introspect

>>This object is a Animal >>This object is a Car

Exercise: Take a look at the TeachingAssistant example defined here and answer here.  What happens if you remove the different to_s methods?  What does this tell you about the way that Ruby resolves conflicts among names inherited from mixins?  Can you affect this by using qualified names, i.e., by prefixing the method name by the class name, e.g., ed.Employee::to_s?

Comparable A good example of the power of modules is Comparable, from the Ruby library.

To use comparable in a class, the class needs to define a method called <=> (sometimes called “rocket”). Once we define this method, we get a lot of useful comparison functions, such as <, >, <=, >=, == and the method between? for free.

What is this like in Java? In Java, similar functionality is achieved through the use of the Comparable .

Here is an example. Suppose that we have a Line class: class Line def initialize(x1, y1, x2, y2) @x1, @y1, @x2, @y2 = x1, y1, x2, y2 end end

CSC/ECE 517 Lecture Notes © 2017 Edward F. Gehringer 8 CSC 517 9

We compare two lines on the basis of their lengths.

We add the Comparable mixin as follows: class Line include Comparable def length_squared (@x2-@x1) * (@x2-@x1) + (@y2-@y1) * (@y2-@y1) end def <=>(other) self.length_squared <=> other.length_squared end end

<=> returns 1,0, or –1, depending on whether the receiver is greater than, equal to, or less than the argument.

We delegate the call to <=> of the Fixnum class, which compares the squares of the lengths.

Now we can use the Comparable methods on Line objects: l1 = Line.new(1, 0, 4, 3) l2 = Line.new(0, 0, 10, 10) puts l1.length_squared if l1 < l2 puts "Line 1 is shorter than Line 2" else if l1 > l2 puts "Line 1 is longer than Line 2" else puts "Line 1 is just as long as Line 2" end end

>>Line 1 is shorter than Line 2

Week 2 Object-Oriented Design and Development 9 Composing Modules Enumerable [SAAS §3.7] is a standard mixin, which can be included in any class. It has a very useful method inject, which can be used to repeatedly apply an operation to adjacent elements in a set: [1, 2, 3, 4, 5].inject {|v, n| v+n } >>15

Many built-in classes include Enumerable, including Array and Range. ('a' .. 'z').inject {|v, n| v+n }

Exercise: Use inject to define a factorial- method.

Let’s define a VowelFinder class that includes Enumerable. It will have an each method for returning successive vowels from a string. This method yields each time it encounters a vowel. class VowelFinder include Enumerable def initialize(string) @string = string end

def each @string.scan(/[aeiou]/) do |vowel| yield vowel end end end

CSC/ECE 517 Lecture Notes © 2017 Edward F. Gehringer 10 CSC 517 11

Here’s an example of its use: VowelFinder.new("abacadabra").inject {|v, n| v+n} >> However, would be nice not to have to type out the “inject” each time, if we just want to work on a different collection. “+” does different things for different classes.  When used with numbers, it .  When used with strings, it . Let’s define a module that encapsulates the call to inject and +. module Reducible def sum_reduce inject {|v, n| v+n} end end class Array include Reducible end class Range include Reducible end class VowelFinder include Reducible end

[1, 2, 3, 4, 5].sum_reduce ('a' .. 'z').sum_reduce vf = VowelFinder.new ("The quick brown fox jumped over the lazy dog.") puts vf.sum_reduce

Week 2 Object-Oriented Design and Development 11

Exercise: Define a method that returns a string containing the vowels, so that the above can be done with only one method call.

Simulating Multiple Inheritance Modules can be used to simulate multiple inheritance in Ruby. Suppose one wants to add tags to Strings. Then one can define a Taggable module and include it into the class. require 'set' # A collection of unordered values with no duplicates

# Include this module to make your class taggable. The names of the # instance variable and the setup method are prefixed with "taggable_" # to reduce the risk of namespace collision. You must call # taggable_setup before you can use any of this module's methods. module Taggable attr_accessor :tags

def taggable_setup @tags = Set.new end

def add_tag(tag) @tags << tag end

def remove_tag(tag) @tags.delete(tag) end end

CSC/ECE 517 Lecture Notes © 2017 Edward F. Gehringer 12 CSC 517 13

class TaggableString < String include Taggable def initialize(arg) super taggable_setup end end s = TaggableString.new('It was the best of times, it was the worst of times.') s.add_tag 'dickens' s.add_tag 'quotation' s.tags # => #

What happens if we execute puts s ?

Exercise: Add a to_s method to the taggable-string example. Then submit it here.

Is Multiple Inheritance Good? Multiple inheritance is generally used in one of four ways.

• Multiple independent protocols. A class is created by combining completely different superclasses. For example, in Eiffel, the library class WINDOW is a subclass of SCREENMAN, RECTANGLE, and TWO_WAY_TREE.

Week 2 Object-Oriented Design and Development 13 • Submodularity. While creating a situation, modularity of subparts is noticed and factored out. For example, in a class representing mortgages, one might factor out FIXED_RATE and ADJUSTABLE mortgages.

• Mix and match. Several classes are created specially for subsequent combination. Mixins are an example of this.

• Separation of interface and implementation. An abstract class is created to define the interface. A second class is created to encapsulate the implementation details. E.g., a Stack class could be created as a subclass of StackInterface and StackImplementation.

Problems posed by multiple inheritance: Multiple inheritance can increase reusability and consistency in a system.

However, with its power also comes complexity and ambiguity. The following problems arise:

• Name collision. Two features with the same name are inherited from different superclasses. The parent classes may be correct and consistent. But there is a conflict when a descendant class inherits a method with the same name (e.g., initialize) from each.

• Repeated inheritance. Because there may be more than one path from a class to its ancestor classes, a class can inherit from the same superclass more than once. We will consider this problem shortly.

• Method combination. An object may need to execute methods with the same name defined in different superclasses (e.g., initialize).

CSC/ECE 517 Lecture Notes © 2017 Edward F. Gehringer 14 CSC 517 15

• Implementation difficulties. Because classes may be combined in various ways, it is difficult to represent objects and find methods without a lot of search or indirection. • Misuse. Because a class has the ability to inherit from as many other classes as necessary, there is a tendency to use inheritance more often than it should be used. Consider declaring an ApplePie class to inherit from Apple and Cinnamon. Why is this not good? ° °

ApplePie should simply be a client of Apple and Cinnamon

Resolution of naming conflicts: The Eiffel language has a novel solution for the problem of name collision.

In Eiffel, no name conflicts are permitted between inherited features.

There is a rename clause to remove them:

class C inherit A rename x as x1, y as y1; B rename x as x2, y as y2; feature …

This inherit clause would be illegal without renaming.

Another advantage of renaming: Sometimes it can be used to give more appropriate names to features.

The class defines a routine called insert_subtree.

For class WINDOW, this is not an appropriate name; add_subwindow is much better.

Renaming allows the better name to be used.

Week 2 Object-Oriented Design and Development 15 Extending Specific Objects In most o-o languages we are familiar with, all objects are grouped into classes.

All objects of a class have the same behavior.

This is called the set-based way of defining objects.

In Ruby, there’s also another way: prototype based.

Set-based language Prototype-based language We create an object that is a We describe a class that abstracts concrete representation of the object what we know to be true of the we are attempting to specify, called object we are attempting to specify. a prototype. We create instances of the object We copy, or clone, the prototype to from the class to perform the actual obtain multiple instances of the work. object. Each instance holds only what is Each object instance is unique and unique to itself and a reference to its holds all its own internal values. prototype, called an extension. There is no true distinction between We can create more specific an instance and a prototype. Any subclasses of a given class that instance can be cloned, becoming either modify the properties of the the prototypical object for its parent or add additional properties. duplicates. When an object receives a message When an object receives a message it does not understand, it delegates it does not understand, it consults its the message to its prototypes, ancestors, asking each of them to asking each of them to grant it the handle the message. ability to handle the message.

Exercise: Take the above table, and give names (e.g., “Object creation”) to each of the rows. We can quickly see the philosophical differences between sets and prototypes.

CSC/ECE 517 Lecture Notes © 2017 Edward F. Gehringer 16 CSC 517 17

In a set-based object system, we define objects top down by specifying the abstract and using that specification to create the specific.

For example, to describe a Cat object we collect properties that we believe to be common to all Cats and create a Cat class. Then we use that class to create unique Cats. In a prototype object system, we define objects bottom up by specifying a specific instance and modifying it as necessary to represent other instances.

For example, to describe a Cat Escher we create an object that contains her properties (female, black fur, annoying).

If we later discover a Cat Berlioz, we specify what we know about Berlioz (male, orange fur) and assume that the remainder is identical to the prototypical Cat Escher.

This system naturally evolves the assumption that all Cats are annoying. Some researchers believe that prototype object systems make better usage of the human ability to generalize from their experience with concrete situations.

How does this apply to Ruby?

Ruby can act as a set-based language: Using include augments a class definition, and hence adds functionality to all objects of a class.

But it can also act as a prototype-based language: There is a way to add functionality to just specific instances of a class, by using the Object#extend method.

Let us suppose that there is a mild-mannered Person class. class Person attr_reader :name, :age, :occupation

def initialize(name, age, occupation) @name, @age, @occupation = name, age, occupation

Week 2 Object-Oriented Design and Development 17 end

def mild_mannered? true end end jimmy = Person.new('Jimmy Olsen', 21, 'cub reporter') clark = Person.new('Clark Kent', 35, 'reporter') jimmy.mild_mannered? # => true clark.mild_mannered? # => true

But it happens that some Person objects are not as mild-mannered as they might appear. Some of them have super powers. module SuperPowers def fly 'Flying!' endIntro

def leap(what) "Leaping #{what} in a single bound!" end

def mild_mannered? false end

def superhero_name 'Superman' end end

If we use include to mix the SuperPowers module into the Person class, it will give every person super powers. Some people are bound to

CSC/ECE 517 Lecture Notes © 2017 Edward F. Gehringer 18 CSC 517 19

misuse such power. Instead, we'll use extend to give super powers only to certain people: clark.extend(SuperPowers) puts clark.superhero_name puts clark.fly puts clark.mild_mannered? puts jimmy.mild_mannered?

>> >> >> >>

Not only can we extend objects; we can also extend classes. In this case, the methods of the extended class become class methods. So, what’s the difference between including a module and extending it?

Reflection

We’ve already seen a few examples of where Ruby programs can discover things about themselves at run time.

For example, we have seen calls like 3.14159.methods Why do we call this “discovering things about [3.14159] at run time”?

Reflection allows program entities to discover things about themselves through introspection.

For example, an object can ask what its methods are, and a class can tell what its ancestors are.

Week 2 Object-Oriented Design and Development 19 While Java also provides reflection, it does so much more verbosely than Ruby.

The related technique of metaprogramming allows one to create new program entities, such as methods or classes, at run time. Why is this called metaprogramming? puts 1.class # print the class of 1 >> Fixnum puts 123456789012345.class >> Bignum puts 123456789012345.kind_of? Integer >> true puts 123456789012345.instance_of? Integer >> false puts 123456789012345.instance_of? Bignum >> true

Exercise: What’s the largest Fixnum? What’s the smallest Fixnum? How do you explain this?

Why is there a difference between a Fixnum and a Bignum?

puts [1, 2, 3, 4, 5].length >> 5 puts "Hey".class >> String puts "John".class.superclass # print the superclass of a String >> Object puts String.ancestors #print the hierarchy >> String

CSC/ECE 517 Lecture Notes © 2017 Edward F. Gehringer 20 CSC 517 21

Enumerable Comparable Object Kernel

For String and Fixnum, which of these ancestors are classes, and which are mixins? Explain.

Note: While it may be useful, in debugging, to print out the class of an object, it is almost always a mistake to test the class of an object: if s.kind_of? Integer then this_method else that_method end

Why?

Week 2 Object-Oriented Design and Development 21