Metaprogramación

+ Pricipios SOLID

Metaprogramación

  • Métodos dinámicos
  • Missing method y missing constant
  • Instance eval, class eval

Principios SOLID

Metaprogramación

Inspeccionando una clase

class User
  attr_accessor :name, :role

  def initialize(name, role)
    @name = name
    @role = role
  end

  def say_hi
    puts "Hello!"
  end
end

Inspeccionando una clase

user = User.new('joaquin', :admin)
=> #<User:0x00007fbefe0798f0 @name="joaquin", @role=:admin>

user.instance_variables
=> [:@name, :@role]

user.methods
=> [:say_hi, :name, :role, :name=, :role=, :instance_variable_defined?, :remove_instance_variable, :instance_of?, ...

User.ancestors
=> [User, Object, Kernel, BasicObject]

Métodos de clase #1

# Método de clase dentro de la definición de la clase
class MyClass
  def self.capitalize_name
    name.upcase
  end
end
puts MyClass.capitalize_name # => MYCLASS

Métodos de clase #2

# Método de clase fuera de la definición de clase
class MyClass;end

def MyClass.capitalize_name
  name.upcase
end

Métodos de clase #3

# Usar una instancia de MyClass
MyClass = Class.new
# add the capitalize_name to MyClass
def MyClass.capitalize_name
  name.upcase
end

Modificando objetos (singleton)

class User; end

user = User.new
def user.hello
  puts "Hello World"
end

user.hello
# => Hello World

user2 = User.new
user2.hello
# => NoMethodError (undefined method `hello' for #<User:0x00007f92f381adc0>)

Enviando mensajes

"Hola Mundo".upcase

"Hola Mundo".send(:upcase)

method = :downcase
"Hola Mundo".send(method)

Generando nuevos métodos

class User
  define_method :say_bye, -> { puts 'Bye!' }
end

User.new.say_bye
# => Bye!

Código que escribe código

class CarModel
  def engine_info=(info)
    @engine_info = info
  end
  def engine_info
    @engine_info
  end
  def engine_price=(price)
    @engine_price = price
  end
  def engine_price
    @engine_price
  end
  # etc ...

Código que escribe código

class CarModel
  FEATURES = ["engine", "wheel", "airbag", "alarm", "stereo"]

  FEATURES.each do |feature|
    define_method("#{feature}_info=") do |info|
      instance_variable_set("@#{feature}_info", info)
    end

    define_method("#{feature}_info") do
      instance_variable_get("@#{feature}_info")
    end

    define_method "feature_price=" do |price|
      instance_variable_set("@#{feature}_price", price)
    end

    define_method("#{feature}_price") do
      instance_variable_get("@#{feature}_price")
    end
  end
end

Method Missing

class MyGhostClass
  def method_missing(name, *args)
    puts "#{name} was called with arguments: #{args.join(',')}"
  end
end

m = MyGhostClass.new
m.awesome_method("one", "two") # => awesome_method was called with arguments: one,two
m.another_method("three", "four") # => another_method was called with arguments: three,four

Métodos dinámicos

User.find_by_email(email)

class User
  def method_missing(name, *args)
    if name =~ /find_by_/
      # ...
    end
  end
end

instance_eval vs class_eval

  • ClassName.instance_eval define un método de clase (asociado a la clase, pero no visible a las instancias).
  • ClassName.class_eval define un método de instancia (aplica a todas las instancias de ClassName).

class_eval

MyClass.class_eval do
  def num
    @num
  end
end

se comporta exactamente igual que el siguiente código:

class MyClass
  def num
    @num
  end
end

Lambda vs Proc

lambda se comporta como un método, mientras que Proc se comporta como un bloque.

Argumentos

lambda = -> (name) { puts "Hola #{name}" }
lambda.call 'Juan'
# => 'Hola Juan'

proc = Proc.new { |name| puts "Hola #{name}" }
proc.call 'Juan'
# => 'Hola Juan'
lambda.call()  
# => ArgumentError: wrong number of arguments (0 for 1)

proc.call()  
# => Hola  

Return statement

def print
  # esto funciona como esperamos
  lambda = lambda { return 'lambda' }
  puts "lambda = #{lambda.call}"

  # este `return` va a interrumpir el método `print` y devolver 'proc'
  proc = Proc.new { return 'proc' }
  # esta línea no se va a imprimir por pantalla
  puts "proc = #{proc.call}"
end

Principios SOLID

Principios SOLID

  • Single responsibility principle
  • Open/closed principle
  • Liskov substitution principle
  • Interface segregation principle
  • Dependency inversion principle

Single responsibility principle

Una clase debe tener solo una razón para cambiar

Ventajas

Codigo más fácil de entender y mantener.
Aumenta la posibilidad de extender el código.

Desventajas

Mayor cantidad de clases.
Funcionalidad distribuida en distintos lugares.

Open/closed principle

Una entidad de software debe quedar abierta para su extensión, pero cerrada para su modificación

Se debe poder cambiar el comportamiento de una clase sin necesidad de modificarla

Ventajas

Diseño claro gracias al polimorfismo.
Código limpio, evitando condiciones innecesarias.
Permite que nuestro código descanse en políticas de alto nivel, dejando los detalles para implementaciones de bajo nivel.

Desventajas

Demanda mayor esfuerzo de previsión, diseño e implementación.
Cuando no disponemos de la suficiente información sobre los requerimientos podemos implementarlo en casos que no lo requerían en un principio.

Liskov substitution principle

Cada clase que hereda de otra puede usarse como su padre sin necesidad de conocer las diferencias entre ellas

Problema:

¿Es un Cuadrado una subclase de Rectángulo?

Soluciones

  1. Push down method / Push down field

  2. Reemplazar herencia con delegación

Interface segregation principle

Muchas interfaces específicas son mejores que una interfaz de propósito general

Los clientes no deberían verse forzados a depender de interfaces que no usan

Interface segregation principle

Dependency inversion principle

El código debe depender de abstracciones, no depender de implementaciones.

  • Los módulos de alto nivel no deben depender de los módulos de bajo nivel. Ambos deben depender de abstracciones (ej: interfaces)
  • Las abstracciones no deben depender de detalles. Los detalles deben depender de las abstracciones.

Antipatterns

  • Duplicated code: varias líneas de código que se repiten a lo largo de diversos métodos en nuestro software.
  • Feature envy: enviar varios mensajes al mismo objeto desde un mismo método.
  • Compare to null: comparar variables contra null es un smell de que no estamos manejando objetos sino la posibilidad de la ausencia de los mismos.

Antipatterns

  • instanceof in conditionals: al preguntar de qué tipo es un objeto dentro de una condición, estamos desperdiciando el polimorfismo.
  • God class: una clase que tiene demasiado código, por la que pasa gran parte de la lógica de nuestro software.
  • Too many parameters: la utilización de excesivos parámetros en un mensaje puede indicar la necesidad de crear una nueva clase que agrupe algunos de ellos.