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.

Articles

Geef een antwoord

Het e-mailadres wordt niet gepubliceerd.