Structuring a Class

In the previous section, and in Organizing Code in Classes and Modules, you saw a simple Mineral class. Here’s the class code by itself without any additional logic:

 class​ Mineral
  getter name : String
  getter hardness : Float64
  getter crystal_struct : String
 
 def​ ​initialize​(@name, @hardness, @crystal_struct) ​# constructor
 end
 end

This class has three read-only instance variables: name, hardness, and crystal_struct. Giving them a type is imposed by the Crystal compiler. But you can also do this in the initialize method:

 class​ Mineral
  getter name, hardness, crystal_struct
 
 def​ ​initialize​(@name : String,
  @hardness : Float64,
  @crystal_struct : String)
 end
 end

Default values can be assigned like this:

 def​ ​initialize​(@name : String = ​"unknown"​, ...)
 end

Some people use symbols like :hardness for the property name, but it isn’t required. A property without a type must have a default value. Or you could give it a value in initialize (try it!). You don’t need to define variables at the start of the class.

The new method creates a Mineral object:

 min1 = Mineral.​new​(​"gold"​, 1.0, ​"cubic"​)
 min1 ​# => #<Mineral:0x271cf00 @crystal_struct="cubic",
 # => @hardness=1.0, @name="gold">
 min1.​object_id​ ​# => 41012992 == 0x271cf00
 typeof(min1) ​# => Mineral # compile-time type
 min1.​class​ ​# => Mineral # run-time type
 Mineral.​class​ ​# => Class # all classes have type Class

new is a class method that’s created automatically for every class. It allocates memory, calls initialize, and then returns the newly created object. An object is created on the heap and it has an object_id: its memory address. When it gets a new name or when it’s passed to a method, only the reference is passed. This means the object is changed when it’s changed in the method.

When you’re not sure which types your initialize method will accept, you can also use generic types like T, as in this class Mineralg:

 class​ Mineralg(T)
  getter name
 
 def​ ​initialize​(@name : T)
 end
 end
 
 min = Mineralg.​new​(​"gold"​)
 min2 = Mineralg.​new​(42)
 min3 = Mineralg(String).​new​(42)
 
 # => Error: no overload matches 'Mineralg(String).new' with type Int32

When naming instance variables, prefix them with @. For class variables, use @@, like the @@planet our mineral species comes from. All objects built using this class will share this variable, and its value will be the same to all of them. (However, subclasses, which you’ll see in the next section, all get their own copy with the value shared across the subclass.)

To name properties that can change, such as quantity in the code that follows, prefix them with property. For write-only properties that can’t be read, use the prefix setter, like id in the following code. Trying to show them is an error:

 class​ Mineral
  @@planet = ​"Earth"
 
  getter name, hardness, crystal_struct
  setter id
  property quantity : Float32
 
 def​ ​initialize​(@id : Int32, @name : String, @hardness : Float64,
  @crystal_struct : String)
  @quantity = 0f32
 end
 
 def​ self.​planet
  @@planet
 end
 end
 
 min1 = Mineral.​new​(101, ​"gold"​, 1.0, ​"cubic"​)
 min1.​quantity​ = 453.0f32 ​# => 453.0
 min1.​id​ ​# => Error: undefined method 'id' for Mineral
 Mineral.​planet​ ​# => "Earth"
 
 min2 = min1.​dup
 min1 == min2 ​# => false

You must make sure that properties are always initialized, either in the initialize method or when calling new. The names of methods called on the class itself are prefixed with self., like the planet method.

Use the dup method to create a “shallow” copy of the object: the copy min2 is a different object, but if the original contains fields that are objects themselves, these are not copied. If you need a “deep” copy, you have to define a clone method.

You can also optionally write a finalize method for a class, which is automatically invoked when an object is garbage collected:

 def​ ​finalize
  puts ​"Bye bye from this ​​#{​self​}​​!"
 end

But this creates a burden for the garbage collection process. You should use it only if you want to free resources taken by external libraries that the Crystal garbage collector won’t free for you. Add this code snippet to see finalization at work, but be warned: you’ll exhaust your machine’s memory by digging so much gold. So save anything you need before running it.

 loop​ ​do
  Mineral.​new​(101, ​"gold"​, 1.0, ​"cubic"​)
 end

As in Ruby or C#, you can reopen a class, which means making additional definitions of a class: they’re all combined into a single class. This even works for built-in classes. How cool is it to define your own new methods on existing classes, such as String or Array? (Yes, this is sometimes derisively called “monkey patching,” and it’s not always a good idea.)

Your Turn 1

a. Employee: Create a class Employee with a getter name and a property age. Make an Employee object and try to change its name.

b. Increment: Create a class Increment with a property amount and two versions of a method increment: one that adds 1 to amount, and another that adds a value, inc_amount.

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

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