20
Week 2 Object-Oriented Design and Development 1 1 CSC 517 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 anyoneno 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 receiverthe 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 objectit 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”

Modules and Mixins - csc2.ncsu.eduSimulating Multiple Inheritance Modules can be used to simulate multiple inheritance in Ruby. Suppose one wants to add tags to Strings. Then one can

  • Upload
    others

  • View
    2

  • Download
    0

Embed Size (px)

Citation preview

Page 1: Modules and Mixins - csc2.ncsu.eduSimulating Multiple Inheritance Modules can be used to simulate multiple inheritance in Ruby. Suppose one wants to add tags to Strings. Then one can

Week 2 Object-Oriented Design and Development 1

1 CSC 517

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”

Page 2: Modules and Mixins - csc2.ncsu.eduSimulating Multiple Inheritance Modules can be used to simulate multiple inheritance in Ruby. Suppose one wants to add tags to Strings. Then one can

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

#... 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;

}

}

Page 3: Modules and Mixins - csc2.ncsu.eduSimulating Multiple Inheritance Modules can be used to simulate multiple inheritance in Ruby. Suppose one wants to add tags to Strings. Then one can

Week 2 Object-Oriented Design and Development 3

3 CSC 517

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 Smalltalk):

class Shape def draw

Page 4: Modules and Mixins - csc2.ncsu.eduSimulating Multiple Inheritance Modules can be used to simulate multiple inheritance in Ruby. Suppose one wants to add tags to Strings. Then one can

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

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 Mixins

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.

Page 5: Modules and Mixins - csc2.ncsu.eduSimulating Multiple Inheritance Modules can be used to simulate multiple inheritance in Ruby. Suppose one wants to add tags to Strings. Then one can

Week 2 Object-Oriented Design and Development 5

5 CSC 517

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'

Page 6: Modules and Mixins - csc2.ncsu.eduSimulating Multiple Inheritance Modules can be used to simulate multiple inheritance in Ruby. Suppose one wants to add tags to Strings. Then one can

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

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.

Page 7: Modules and Mixins - csc2.ncsu.eduSimulating Multiple Inheritance Modules can be used to simulate multiple inheritance in Ruby. Suppose one wants to add tags to Strings. Then one can

Week 2 Object-Oriented Design and Development 7

7 CSC 517

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

mixins different?

Let’s contrast this with multiple inheritance 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

d = Animal.new("Cat") c = Car.new("Ferrari") d.kind # kind method is available through …

Page 8: Modules and Mixins - csc2.ncsu.eduSimulating Multiple Inheritance Modules can be used to simulate multiple inheritance in Ruby. Suppose one wants to add tags to Strings. Then one can

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

c.kind # .. the mixin 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 interface.

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

Page 9: Modules and Mixins - csc2.ncsu.eduSimulating Multiple Inheritance Modules can be used to simulate multiple inheritance in Ruby. Suppose one wants to add tags to Strings. Then one can

Week 2 Object-Oriented Design and Development 9

9 CSC 517

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

Page 10: Modules and Mixins - csc2.ncsu.eduSimulating Multiple Inheritance Modules can be used to simulate multiple inheritance in Ruby. Suppose one wants to add tags to Strings. Then one can

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

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

Page 11: Modules and Mixins - csc2.ncsu.eduSimulating Multiple Inheritance Modules can be used to simulate multiple inheritance in Ruby. Suppose one wants to add tags to Strings. Then one can

Week 2 Object-Oriented Design and Development 11

11 CSC 517

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

Page 12: Modules and Mixins - csc2.ncsu.eduSimulating Multiple Inheritance Modules can be used to simulate multiple inheritance in Ruby. Suppose one wants to add tags to Strings. Then one can

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

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

Page 13: Modules and Mixins - csc2.ncsu.eduSimulating Multiple Inheritance Modules can be used to simulate multiple inheritance in Ruby. Suppose one wants to add tags to Strings. Then one can

Week 2 Object-Oriented Design and Development 13

13 CSC 517

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 # => #<Set: {"dickens", "quotation"}> 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.

Page 14: Modules and Mixins - csc2.ncsu.eduSimulating Multiple Inheritance Modules can be used to simulate multiple inheritance in Ruby. Suppose one wants to add tags to Strings. Then one can

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

• 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).

Page 15: Modules and Mixins - csc2.ncsu.eduSimulating Multiple Inheritance Modules can be used to simulate multiple inheritance in Ruby. Suppose one wants to add tags to Strings. Then one can

Week 2 Object-Oriented Design and Development 15

15 CSC 517

• 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 TREE 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.

Page 16: Modules and Mixins - csc2.ncsu.eduSimulating Multiple Inheritance Modules can be used to simulate multiple inheritance in Ruby. Suppose one wants to add tags to Strings. Then one can

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

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 describe a class that abstracts what we know to be true of the object we are attempting to specify.

We create an object that is a concrete representation of the object we are attempting to specify, called a prototype.

We create instances of the object from the class to perform the actual work.

We copy, or clone, the prototype to obtain multiple instances of the object.

Each object instance is unique and holds all its own internal values.

Each instance holds only what is unique to itself and a reference to its prototype, called an extension.

We can create more specific subclasses of a given class that either modify the properties of the parent or add additional properties.

There is no true distinction between an instance and a prototype. Any instance can be cloned, becoming the prototypical object for its duplicates.

When an object receives a message it does not understand, it consults its ancestors, asking each of them to handle the message.

When an object receives a message it does not understand, it delegates the message to its prototypes, asking each of them to grant it the 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.

Page 17: Modules and Mixins - csc2.ncsu.eduSimulating Multiple Inheritance Modules can be used to simulate multiple inheritance in Ruby. Suppose one wants to add tags to Strings. Then one can

Week 2 Object-Oriented Design and Development 17

17 CSC 517

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

In a prototype object system, we define objects bottom up by specifying a specific instance and modifying it as necessary to represent other instances.

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 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

Page 18: Modules and Mixins - csc2.ncsu.eduSimulating Multiple Inheritance Modules can be used to simulate multiple inheritance in Ruby. Suppose one wants to add tags to Strings. Then one can

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

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

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?

>> >> >> >>

Page 19: Modules and Mixins - csc2.ncsu.eduSimulating Multiple Inheritance Modules can be used to simulate multiple inheritance in Ruby. Suppose one wants to add tags to Strings. Then one can

Week 2 Object-Oriented Design and Development 19

19 CSC 517

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.

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

Page 20: Modules and Mixins - csc2.ncsu.eduSimulating Multiple Inheritance Modules can be used to simulate multiple inheritance in Ruby. Suppose one wants to add tags to Strings. Then one can

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

>> 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 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?