CSC/ECE 517 Fall 2011/ch1 2b bs: Difference between revisions
No edit summary |
No edit summary |
||
Line 210: | Line 210: | ||
The Comparable mixin is used by classes whose objects may be ordered. The class must define the <=> operator, which compares the receiver against another object, returning -1, 0, or +1 depending on whether the receiver is less than, equal to, or greater than the other object. Comparable uses <=> to implement the conventional comparison operators (<, <=, ==, >=, and >) and the method between?. | The Comparable mixin is used by classes whose objects may be ordered. The class must define the <=> operator, which compares the receiver against another object, returning -1, 0, or +1 depending on whether the receiver is less than, equal to, or greater than the other object. Comparable uses <=> to implement the conventional comparison operators (<, <=, ==, >=, and >) and the method between?. | ||
class SizeMatters | class SizeMatters | ||
include Comparable | include Comparable | ||
attr :str | attr :str |
Revision as of 05:14, 19 September 2011
Introduction:
An interface in the Java programming language is an abstract type that is used to specify an interface that classes must implement. Interface may only contain method signature and constant declarations. An interface may never contain method definitions. Interfaces cannot be instantiated. A class that implements an interface must implement all of the methods described in the interface, or be an abstract class. Object references in Java may be specified to be of an interface type; in which case, they must either be null, or be bound to an object that implements the interface. In object-oriented programming languages, a mixin is a class that provides a certain functionality to be inherited or just reused by a subclass. Some of the functionality of mixins is provided by interfaces in popular languages like Java and C#. However, an interface only specifies what the class must support and cannot provide an implementation. Another class, providing an implementation and dependent with the interface, is needed for refactoring common behavior into a single place. Interfaces combined with aspect-oriented programming can produce full-fledged mixins in languages that support such features, such as C# or Java. One benefit of using interfaces is that they simulate multiple inheritance. All classes in Java other than java.lang.Object, must have exactly one base class; multiple inheritance of classes is not allowed.
Overview
Mixins in Ruby
In Java you just have classes (both abstract and concrete) and interfaces. The Ruby language provides classes, modules, and a mix of both. Inheriting from a mixin is not a form of specialization but is rather a means of collecting functionality. A class may inherit most or all of its functionality from one or more mixins through multiple inheritance. A mixin can also be viewed as an interface with implemented methods. When a class includes a mixin, the class implements the interface and includes, rather than inherits, all the mixins’ attributes (fields, properties) and methods. They become part of the class during compilation. A mixin can defer definition and binding of methods until runtime, though attributes and instantiation parameters are still defined at compile time. In the Ruby language a mixin is a class that is mixed with a module.
Module
A module is a degenerate abstract class. A module can’t be instantiated and no class can directly extend it but it can fully implement methods. A class can leverage the implementation of a module by including the module’s methods. A module can define methods that can be shared in different and separate classes either at the class or instance level.
Example of Module:
- Convert a integer value to English.
module Stringify
# Requires an instance variable @value def stringify if @value == 1 "One" elsif @value == 2 "Two" elsif @value == 3 "Three" end end
end
The Stringify module above makes use of a @value instance variable. The class that will be mixed with this module needs to define and set a @value instance variable since the Stringify module uses it but does not define it. In addition to instance variables a module could invoke methods not defined in the module but in the class that it will be mixed with.
Self Contained Module
- A Math module akin to Java Math class.
module Math
# Could be called as a class, static, method def add(val_one, val_two) BigInteger.new(val_one + val_two) end
end
The methods in the Math module are intended to be invoked like class methods, also known as static methods. The add method in the Math module accepts two integer values and returns an instance of BigInteger.
Using Mixins:
Implementation of the class and module are joined, intertwined, combined, etc. A mixin is a different mechanism to the extend construct used to add concrete implementation to a class. With a mixin you can extend from a module instead of a class.
This is the implementation of BigInteger class. The BigInteger class defines one constructor and directly inherits one method from the Number base class. To mix in the methods implemented in the Stringify and Math modules with the BigInteger class you will note the usage of the include and extend methods, respectively.
- Base Number class
class Number
def intValue @value end
end
- BigInteger extends Number
class BigInteger < Number
# Add instance methods from Stringify include Stringify # Add class methods from Math extend Math # Add a constructor with one parameter def initialize(value) @value = value end
end
Using the class:
- Create a new object
bigint1 = BigInteger.new(10)
- Call a method inherited from the base class
puts bigint1.intValue # --> 10
The extend method will mix a module’s methods at the class level. The method defined in the Math module can be used as a class/static method.
- Call class method extended from Math
bigint2 = BigInteger.add(-2, 4) puts bigint2.intValue # --> 2
The include method will mix a module’s methods at the instance level, meaning that the methods will become instance methods. The method defined in the Stringify module can be used as an instance method.
- Call a method included from Stringify
puts bigint2.stringify # --> 'Two'
There is another use of the extend method. You can enhance an object instance by mixing it with a module at run time. This ‘CurrencyFormatter’ is used to extend an object, changing it’s responsibilities at runtime.
- Format a numeric value as a currency
module CurrencyFormatter
def format "$#{@value}" end
end
To mix an object instance with a module you can do the following:
- Add the module methods to
- this object instance, only!
bigint2.extend CurrencyFormatter puts bigint2.format # --> '$2'
Calling the extend method on an instance will only extend that one object; objects of the same class will not be extended with the new functionality.
puts bigint1.format # will generate an error
Modules that will be mixed with a class via the include or extend method could define something like a contructor or initializer method to the module. The module initializer method will be invoked at the time the module is mixed with a class. When a class extends a module the module’s self.extended method will be invoked:
module Math
def self.extended(base) # Initialize module. end
end
The self prefix indicates that the method is a static module level method. The base parameter in the static extended method will be either an instance object or class object of the class that extended the module depending whether you extend a object or class, respectively. When a class includes a module the module’s self.included method will be invoked.
module Stringify
def self.included(base) # Initialize module. end
end
The base parameter will be a class object for the class that includes the module. It is important to note that inside the included and extended initializer methods you can include and extend other modules.
module Stringify
def self.included(base) base.extend SomeOtherModule end
end
Interfaces in Java
Interfaces define a standardized set of commands that a class will obey. The commands are a set of methods that a class implements. The interface definition states the names of the methods and their return types and argument signatures. There is no executable body for any method. Example: interface Bicycle {
void changeCadence(int newValue); // wheel revolutions per minute void changeGear(int newValue); void speedUp(int increment); void applyBrakes(int decrement);
}
Implementation is independent to each class that implements the interface. Once a class implements an interface, the Java compiler knows that an instance of the class will contain the specified set of methods. Therefore, it will allow you to call those methods for an object referenced by a variable whose type is the interface. Implementing an interface enables a class to be "plugged in" in any situation that requires a specific behavior (manifested through the set of methods). class ACMEBicycle implements Bicycle {
//implementation specific to this class int cadence = 0;
int speed = 0; int gear = 1;
void changeCadence(int newValue) { cadence = newValue; } void changeGear(int newValue) { gear = newValue; } void speedUp(int increment) { speed = speed + increment; } void applyBrakes(int decrement) { speed = speed - decrement; } void printStates() { System.out.println("cadence:"+cadence+" speed:"+speed+" gear:"+gear); }
}
class MOUNTAINBicycle implements Bicycle { int cadence = 2;
int speed = 3; int gear = 5;
void changeCadence(int newValue) { cadence = newValue + 10; } void changeGear(int newValue) { gear = newValue + 4; } void speedUp(int increment) { speed = speed + increment + 10; } void applyBrakes(int decrement) { speed = speed – decrement – 10; } void printStates() { System.out.println("cadence:"+cadence+" speed:"+speed+" gear:"+gear); }
}
Using an interface rather than inheritance to specify a certain set of methods allows a class to inherit from some other class. In other words, if a class needs two different sets of methods, so it can behave like two different types of things, it could inherit one set from class A, and use an interface B to specify the other. You could then reference one of these objects with either an A reference or a B reference. Implementing an interface allows a class to become more formal about the behavior it promises to provide. Interfaces form a contract between the class and the outside world, and this contract is enforced at build time by the compiler. If your class claims to implement an interface, all methods defined by that interface must appear in its source code before the class will successfully compile.
Mixins vs Interfaces
Because of this mixin feature, a developer can add arbitrary methods and modify behavior of core classes at runtime. This is amazingly powerful if you are trying to write plugins and extensions to the framework. Because you can add functionality to existing objects, users can install your plugin and start taking advantage of new functionality without having to make changes to the objects that they are instantiating in their application.
In frameworks written in other languages, such as Java, plugging in new functionality means that you need to change how your objects are instantiated. This will require code changes and/or potentially configuration changes which makes it hard to develop, hard to maintain, and a pain for plugin developers to support. But because of the mixin feature, Rails plugin developers can customize the base objects and the users of the plugins do not have to change any of their code or configuration logic.
- 1.Comparable Functionality
- a.Ruby
The Comparable mixin is used by classes whose objects may be ordered. The class must define the <=> operator, which compares the receiver against another object, returning -1, 0, or +1 depending on whether the receiver is less than, equal to, or greater than the other object. Comparable uses <=> to implement the conventional comparison operators (<, <=, ==, >=, and >) and the method between?.
class SizeMatters include Comparable attr :str def <=>(anOther) str.size <=> anOther.str.size end def initialize(str) @str = str end def inspect @str end end
s1 = SizeMatters.new("Z") s2 = SizeMatters.new("YY") s3 = SizeMatters.new("XXX") s4 = SizeMatters.new("WWWW") s5 = SizeMatters.new("VVVVV")
s1 < s2 #=> true s4.between?(s1, s3) #=> false s4.between?(s3, s5) #=> true [ s3, s2, s5, s4, s1 ].sort #=> [Z, YY, XXX, WWWW, VVVVV]