Сравнительный взгляд на некторые идиомы Ruby

В этой статье мы рассмотрим некоторые хорошо известные модели программирования (patterns), их реализацию в популярных языках программирования и сравним эквиваленты написанные на Ruby. Материал будет полезен в первую очередь тем кто только присматривается к Ruby. Целью данной статьи не является стремление показать насколько Ruby хорош перед остальными языками, а продемонстрировать как на Ruby реализуются общие задачи, такие как конструирование объектов, итерирование по наборам данных и т.д. Статья не претендует на исчерпывающее представление всех особенностей языка, мы рассмотрим лишь малую долю тех возможностей, которые нам дает замечательный язык программирования Ruby.

Конструирование объектов


Главной целью конструктора является корректная инициализация объекта.

Вот как выглядит типичный конструктор в языке Java:

 
class MyCounter
{
  MyCounter(int i) {
    _i = i;
  }

  private int _i; // instance variable
}

MyCounter m = new MyCounter(9);

Теперь посмотрите как аналогичная конструкция записывается на языке программирования Ruby:

class MyCounter
  def initialize(i)
    @i = i
  end
end

m = MyCounter.new(9)


Accessors


Accessors - это методы используемые для доступа к переменным объекта, которые позволяют добиться лучшей инкапсуляции.

Аксессоры в Java:

class MyCounter {
  private int counter;
 
  int getCounter() { return counter; }
  void setCounter(int n) { counter = n; }
}

MyCounter m = new MyCounter();
m.setCounter(9);
int n = m.getCounter();

Вот как это записать на ruby:

class MyCounter
  def counter
    @counter
  end

  def counter=(n)
    @counter = n
  end
end

m = MyCounter.new
m.counter = 9
n = m.counter

Ruby так же позволяет записать тоже самое в более короткой форме:

class MyCounter
  attr_accessor :counter
end

m = MyCounter.new
m.counter = 9
n = m.counter

Пример выше показывает, что манипуляция с членом объекта происходит при помощи естественных операций присвоения, на самом деле происходит вызов метода. Данный принцип называется -- Однородный Принцип Доступа (Uniform Access Principle) и был введен впервые в языке Eiffel.


Указатель на функцию (Function pointer)

"Function pointer" это идиома языка C/C++. Я использую этот термин чтобы описать каким образом можно передать блок кода для последующего использования в другой части программы.

Указатель на метод в языке C++:

class MyClass {
public:
  int myMethod(int i, int j) { return i + j; }
}

// Сохраняем указатель на метод
int (MyClass::*fp)(int, int) = &MyClass::myMethod;

// Вызываем метод через указатель
int n = (m.*fp)(1, 2); // -> 3

C++ язык со строгой типизацией и поэтому код выглядит весьма громоздка.

Теперь посмотрим как выглядит указатель на функцию в Perl:

sub f { $_[0] + $_[1] }
$fr = \&f;
$n = &$fr(1, 2); # -> 3

Указатель на функцию в Ruby:

p = proc { |i, j| i + j }
p.call(1, 2)

Указатель на метод в Ruby:

class MyClass
  def myMethod(i, j)
    i + j
  end
end
m = MyClass.new
fp = m.method(:myMethod)
fp.call(1, 2) # -> 3


Итераторы

Итераторы служат для последовательного перебора набора данных.

Итераторы в Java (старая техника):

char[] data = { 'a', 'b', 'c', 'd', 'e', 'f' };

for (int i = 0; i < data.length; i++) {
  System.out.println( data[i] );
}

Новая техника (доступна начиная с Java 1.5):

Character[] data = { 'a', 'b', 'c', 'd', 'e', 'f' };
List<Character> ldata = Arrays.asList(data);

for (char c : ldata) {
  System.out.println( c );
}

Итератор в Ruby:

data = 'a'..'f'
data.each { |c| print c, "\n" }

Как видите решение на Ruby гораздо более компактно, но не менее содержательно. Особенно обратите внимание на то как объявлен массив data, эта техника пришла в Ruby из Perl.


Переменное количество аргументов


Бывают ситуации когда методу заранее не известно какое количество параметров будет передано при вызове.

Решение на C:

#include <stdarg.h>
#include <stdio.h>

void print_nums(int count, ...) {
  va_list ap;
  int i;

  va_start(ap, count);
  for (i = 0; i < count; i++) {
    int num = va_arg(ap, int);
    printf("%d\n", num);
  }
  va_end(ap);
}

main() {
  print_nums(3, 102, 5, 689);
}

И вот как коротко выглядит аналогичный код в Ruby:

def print_nums(*numbers)
  numbers.each { |i| puts i }
end

print_nums(3, 102, 5, 689)


Исключения

Исключения служат для обработки ошибок, в Ruby так же поддержан этот механизм.

Пример обработки исключений в Java:

FileInputStream f = null;

try {
  f = new FileInputStream("z.txt");
  throw new IOException("Oops; we goofed");
} catch (FileNotFoundException fnfe) {
  System.out.println("The file was not found!");
} catch (IOException ioe) {
  throw ioe; // rethrow
} finally {
  if (f != null) f.close();
}

Обработка исключений в Ruby:

begin
  f = File.open("z.txt")
  # делаем что то с файлом 
  # ...
  # обнаружили ошибку 
  raise "Oops, we goofed"
rescue Errno::ENOENT
  print "The file was not found: ", $!, "\n"
rescue # catch all
  raise $! # rethrow
ensure
  f.close if f
end

В ruby так же можно в одном блоке rascue можно обрабатывать исключения разных заданных типов.

Приведенный выше пример демонстрировал принципы обработки исключений, однако более корректным способом работы с файлом в Ruby является такая форма:

File.open("file.ext") { |f|
  f.readlines.each { |l| print l }
}

Даже если внутри блока произойдет исключение, файл будет автоматически закрыт, всегда используйте такую форму.


Пространство имен


Пространство имен в ruby обеспечивается размещением кода в пределах модуля.


Множественное наследование


Среди языков затронутых в этой статье, только C++ поддерживает истинное множественное наследование. В языке Java множественное наследование моделируется за счет использования интерфейсов - абстрактные классы без каких либо данных. Ruby использует другой подход - общий код размещается внутри модуля, затем модуль можно включить в другой класс, например:

module Foo
  def foo; puts :foo; end
end

module Bar
  def bar; puts :bat; end
end

class A
  include Foo
  include Bar
end

a = A.new
a.foo #-> foo
a.bar #-> bar


Регулярные выражения


Синтаксис регулярных выражений в Ruby позаимствован из Perl:

data = "Programming in Ruby"
if data =~ /^P/ || data =~ /ming$/
  data.gsub!(/[pr]/i, 'X') # Для замены символов в строке используется метод gsub
  print "#{data}\n" # -> XXogXamming in Xuby
end

---
Автор: Renaud Waldura
Перевод: Антон Кириллов

Оригинал статьи -- http://renaud.waldura.com/doc/ruby/idioms.shtml

Обсуждение

Re: Сравнительный взгляд на некторые идиомы Ruby

подправил, спасибо

Вход для пользователей