Applying Inheritance

As in all object-oriented languages and much the same as in Ruby, Crystal provides for single inheritance, indicated by: subclass < superclass. Putting properties and methods common to several classes into a superclass lets them all share functionality. That way, you can use all instance variables and all methods of the superclass in the subclass, including the constructors. You can see this in the following example where PDFDocument inherits initialize, name, and print from Document:

 class​ Document
  property name
 
 def​ ​initialize​(@name : String)
 end
 
 def​ ​print
  puts ​"Hi, I'm printing ​​#{​@name​}​​"
 end
 end
 
 class​ PDFDocument < Document
 end
 
 doc = PDFDocument.​new​(​"Salary Report Q4 2018"​)
 doc.​print​ ​# => Hi, I'm printing Salary Report Q4 2018

You can also override any inherited method in the subclass. If the subclass defines its own initialize method(s), they aren’t inherited anymore. If you want to use the superclass functionality after you overrode it, you can call any method of the superclass with super:

 class​ PDFDocument < Document
 def​ ​initialize​(@name : String, @company : String)
 end
 
 def​ ​print
 super
  puts ​"From company ​​#{​@company​}​​"
 end
 end
 
 # doc = PDFDocument.new("Salary Report Q4 2018")
 # => Error: wrong number of arguments for 'PDFDocument.new' (given 1,
 # => expected 2)
 doc = PDFDocument.​new​(​"Salary Report Q4 2018"​, ​"ACME"​)
 doc.​print
 
 # => Hi, I'm printing Salary Report Q4 2018
 # From company ACME

Crystal’s type system gives you more options here. Instead of overriding, you can define specialized methods by using type restrictions, such as print in PDFDocument:

 class​ PDFDocument < Document
 def​ ​initialize​(@name : String, @company : String)
 end
 
 def​ ​print​(date : Time)
  puts ​"Printing ​​#{​@name​}​​"
  puts ​"From company ​​#{​@company​}​​ at date ​​#{​date​}​​"
 end
 end
 
 doc = PDFDocument.​new​(​"Salary Report Q4 2018"​, ​"ACME"​)
 doc.​print​(Time.​now​)
 
 # => Printing Salary Report Q4 2018
 # From company ACME at date 2017-05-25 12:12:45 +0200

Using Abstract Classes and Virtual Types

Ruby doesn’t have native support for interfaces and abstract classes like Java or C#. In both Ruby and Crystal, the concept of an interface is implemented through modules, as you’ll see in the next chapter. But Crystal also knows the concept of an abstract class, so if you’re a Rubyist, the following will be new to you.

Not all classes are destined to produce objects, and abstract classes are a good example. These serve instead as a blueprint for subclasses to implement their methods. Here you see a class, Rect (describing rectangles), forced to implement all abstract methods from class Shape:

 abstract ​class​ Shape
  abstract ​def​ ​area
  abstract ​def​ ​perim
 end
 
 class​ Rect < Shape
 def​ ​initialize​(@width : Int32, @height : Int32)
 end
 
 def​ ​area
  @width * @height
 end
 
 def​ ​perim
  2 * (@width + @height)
 end
 end
 
 s = Shape.​new​ ​# => can't instantiate abstract class Shape
 Rect.​new​(3, 6).​area​ ​# => 18

If one of the methods (say perim) isn’t implemented, the compiler issues an error like the following:

 error: "abstract `def Shape#perim()` must be implemented by Rect"

This lets you create class hierarchies where you can be confident that all necessary methods are implemented.

You can create more intricate structures as well. In the following example, class Document is called a virtual type because it combines different types from the same type hierarchy—in this case, different documents:

 class​ Document
 end
 
 class​ PDFDocument < Document
 def​ ​print
  puts ​"PDF header"
 end
 end
 
 class​ XMLDocument < Document
 def​ ​print
  puts ​"XML header"
 end
 end
 class​ Report
  getter doc
 
 def​ ​initialize​(@name : String, @doc : Document)
 end
 end
 
 salq4 = Report.​new​ ​"Salary Report Q4"​, PDFDocument.​new
 taxQ1 = Report.​new​ ​"Tax Report Q1"​, XMLDocument.​new

This virtual type is indicated by the compiler as type Document+, meaning that all types inherit from Document, including Document itself. It comes into play in situations like the one that follows where you’d expect d to be of a union type (PDFDocument | XMLDocument):

 if​ 4 < 5
  d = PDFDocument.​new
 else
  d = XMLDocument.​new
 end
 typeof(d) ​# => Document

Instead, d is of type Document. Internally the compiler uses this as a virtual type Document+ instead of the union type (PDFDocument | XMLDocument), because union types quickly become very complex in class-hierarchies.

If you call a method in a subclass of Document, you get an error:

 salq4.​doc​.​print​ ​# => Error: undefined method 'print' for Document

To remove this error, simply make the class Document abstract.

 abstract ​class​ Document
 end
 
 salq4.​doc​.​print​ ​# => PDF header

Your Turn 2

Shape: Subclass Shape with classes Square and Circle. (Hint: Use PI from the Math module with: include Math.)

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.141.7.240