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”
20
Embed
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
This document is posted to help you gain knowledge. Please leave a comment to let me know what you think about it! Share it to your friends and learn new things together.
Transcript
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”
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
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
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
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
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.
• 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).
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.
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.
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
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
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