extend self in ruby
Volgende code werd getest met ruby 1.9.3 .
Klasse is bedoeld voor zowel data als gedrag
Laten we eens kijken naar deze ruby code.
1class Util2 def self.double(i)3 i*24 end5end67Util.double(4) #=> 8
Hier hebben we een Util
klasse. Maar merk op dat alle methodes van deze klasse klasse methodes zijn. Deze klasse heeft geen instantie variabelen. Gewoonlijk wordt een klasse gebruikt om zowel gegevens als gedrag te dragen en in dit geval heeft de klasse Util alleen gedrag en geen gegevens.
Gelijkaardige hulpprogramma’s in robijn
Nu, om wat perspectief op deze discussie te krijgen, laten we eens kijken naar enkele robijn-methoden die gelijkaardige dingen doen. Hier zijn er een paar.
1require 'base64'2Base64.encode64('hello world') #=>"aGVsbG8gd29ybGQ=\n "34require 'benchmark'5Benchmark.measure { 10*2000 }67require 'fileutils'8FileUtils.chmod 0644, 'test.rb'910Math.sqrt(4) #=> 2
In alle bovenstaande gevallen wordt de klassemethode aangeroepen zonder eerst een instantie aan te maken. Dit lijkt dus op de manier waarop ik Util.double
heb gebruikt.
Maar laten we eens kijken wat de klasse is van al deze objecten.
1Base64.class #=> Module2Benchmark.class #=> Module3FileUtils.class #=> Module4Math.class #=> Module
Dit zijn dus geen klassen, maar modules. Dat roept de vraag op waarom de slimme jongens bij ruby-core ze als modules hebben geïmplementeerd in plaats van een klasse te maken zoals ik heb gedaan voor Util.
De reden is dat Class te zwaar is om alleen methoden te maken zoals double
. Zoals we eerder hebben besproken moet een klasse zowel gegevens als gedrag hebben. Als het enige waar je om geeft gedrag is, dan stelt Ruby voor om het te implementeren als een module.
extend self is het antwoord
Voordat ik verder ga om extend self
te bespreken, hier is hoe mijn Util
klasse eruit zal zien na het verplaatsen van Class
naar Module
.
1module Util2 extend self34 def double(i)5 i * 26 end7end89puts Util.double(4) #=> 8
Dus hoe werkt extend self
Laten we eerst eens kijken wat extend doet.
1module M2 def double(i)3 i * 24 end5end67class Calculator8 extend M9end10puts Calculator.double(4)
In het bovenstaande geval breidt Calculator
module M
uit en dus zijn alle instance-methoden van module M
direct beschikbaar voor Calculator
.
In dit geval is Calculator
een klasse die de module M
heeft uitgebreid. Calculator
hoeft echter geen klasse te zijn om een module uit te breiden.
Nu volgt een variatie waarbij Calculator
een module is.
1module M2 def double(i)3 i * 24 end5end67module Calculator8 extend M9end10puts Calculator.double(4) #=> 8
Hier is Calculator een module die een andere module uitbreidt.
Nu we begrijpen dat een module een andere module kan uitbreiden, kijken we naar de bovenstaande code en vragen we ons af waarom module M
überhaupt nodig is. Waarom kunnen we de methode double
niet direct naar module Calculator verplaatsen. Laten we dat eens proberen.
1module Calculator2 extend Calculator34 def double(i)5 i * 26 end7end8puts Calculator.double(4) #=> 8
Ik ben van module M
af en heb de methode double
naar module Calculator
verplaatst. Omdat module M
weg is heb ik extend M
veranderd in extend Calculator
.
Een laatste fix.
Binnen de module Calculator wat is self
. self
is de module Calculator
zelf. Het is dus niet nodig Calculator
twee keer te herhalen. Hier is de uiteindelijke versie
1module Calculator2 extend self34 def double(i)5 i * 26 end7end8puts Calculator.double(4) #=> 8
Converting A Class into a Module
Iedere keer als ik code als extend self
tegenkom, staan mijn hersenen even stil. Dan zoek ik het op google. Er over lezen. Drie maanden later herhaalde ik het hele proces.
De beste manier om het te leren is het te gebruiken. Dus ging ik op zoek naar een geval om extend self
te gebruiken. Het is geen goed gebruik om op jacht te gaan naar code om een idee toe te passen dat je in je hoofd hebt, maar hier probeerde ik te leren.
Hier is een voor momentopname van methoden uit Util
klasse die ik in een project heb gebruikt.
1class Util2 def self.config2hash(bestand); end3 def self.in_cents(bedrag); end4 def self.localhost2public_url(url, protocol); end5end
Na gebruik van extend self
code werd
1module Util2 extend self34 def config2hash(bestand); end5 def in_cents(bedrag); end6 def localhost2public_url(url, protocol); end7end
Veel beter. Het maakt de bedoeling duidelijk en het is, denk ik, in lijn met de manier waarop Ruby verwacht dat we gebruiken.
Een ander gebruik in lijn met hoe Rails extend self gebruikt
Hier ben ik een e-commerce applicatie aan het bouwen en elke nieuwe bestelling moet een nieuw bestelnummer krijgen van een verkoopapplicatie van derden. De code zou er als volgt uit kunnen zien. Ik heb de implementatie van de methoden weggelaten omdat deze niet relevant zijn voor deze discussie.
1class Order2 def amount; end3 def buyer; end4 def shipped_at; end5 def number6 @number || self.class.next_order_number7 end89 def self.next_order_number; 'A100'; end10end1112puts Order.new.number #=> A100
Hier zou de methode next_order_number
een gecompliceerde oproep naar een ander verkoopsysteem kunnen doen. Idealiter zou de klasse Order
methode next_order_number
niet moeten blootstellen. Dus kunnen we deze methode private
maken, maar dat lost het probleem niet op. Het probleem is dat model Order
niet mag weten hoe het nieuwe ordernummer wordt gegenereerd. We kunnen de methode next_order_number
verplaatsen naar een andere Util
klasse, maar dat zou te veel afstand scheppen.
Hier is een oplossing met extend self
.
1module Checkout2 extend self34 def next_order_number; 'A100'; end56klasse Order7 def amount; end8 def buyer; end9 def shipped_at; end10 def number11 @number || Checkout.next_order_number12 end13 end14end1516puts Checkout::Order.new.number #=> A100
Moveel beter. De klasse Order stelt methode next_order_number
niet bloot en deze methode staat daar in hetzelfde bestand. Je hoeft de Util
klasse niet te openen.