Статьи

.NET для Ruby: классы

В предыдущем посте из этой серии .NET для Ruby: среда Ruby мы рассмотрели основные инструменты, составляющие Ruby. Если вы помните, мы говорили о запуске Ruby из командной строки, используя IRB, мы написали нашу первую программу Hello World и выяснили, что происходит под крышкой.

В этом посте мы рассмотрим сравнение фундаментальной объектно-ориентированной функции: классы. Да это все. Этот пост начинался как классы, методы и переменные, но он стал слишком большим — есть много чего рассказать при обсуждении классов! Итак, давайте погрузимся.

Классы

Классы существуют в Ruby, как и .NET, и их определение остается прежним. В обоих

Классы Ruby и .NET используются для инкапсуляции (или группировки) частей связанных данных и методов в сжатый объект

В обоих случаях мы можем создать экземпляр класса и получить его экземпляр. Ruby и .NET используют ключевое слово new для создания экземпляров объектов, но синтаксис немного отличается:

// In C#
var baz = new FooBar();
 # In Ruby
baz = FooBar.new

Чтобы фактически определить класс, в C # мы сделали бы это так:

 public class FooBar {
    // Class implementation
}

И в Ruby класс выглядит так:

 class FooBar
  # Class implementation
end

Действительно просто и понятно, разница в том, что в определении класса в Ruby отсутствует область видимости. А область видимости классов — это та область, в которой Ruby значительно отличается от .NET, поскольку в Ruby не допускаются закрытые, защищенные или внутренние классы. Все классы в Ruby являются публичными .

Почему классы Ruby все публичные?

В Ruby есть открытые классы, потому что в основном все классы Ruby являются открытыми классами . Это означает, что я могу взломать класс String (или любой другой) и добавить к нему функциональность. Вот пример:

 class String
  def remove_vowels
    self.gsub(/[aeiou]/, '')
  end
end

puts "this is a sentence".remove_vowels #=> ths s  sntnc

Теперь, прежде чем кто-то подпрыгнет на Ruby за то, что он допустил такое злодеяние, давайте отметим, что в .NET предусмотрена та же функциональность в форме методов расширения . Давайте напишем тот же код в .NET:

 static class StringExtensions
{
    public static string ReplaceVowels(this string value)
    {
        return new Regex("[aeiou]").Replace(value, string.Empty);
    }
}

Единственное место, где отличаются открытые классы Ruby и функциональные возможности расширения .NET — это то, что в Ruby вы можете открыть класс и определить методы класса, тогда как в .NET вы не можете определить расширение метода класса.

Поскольку в этой серии статей о переходе с .NET на Ruby речь идет не о начале пламенных войн, я оставляю вам самим определить, стоят ли Open Classes и Extensions нарушения инкапсуляции классов, которое они вводят (оставьте свое мнение в комментариях, если ты особенно смелый.)

Для тех из вас, кто хочет взглянуть на примеры хорошо применяемых открытых классов, заглянуть в GitHub и взглянуть на Rails core_ext .

Наследование

Наследование в Ruby похоже на .NET. С его помощью мы можем определить суперкласс, а затем наследовать от суперкласса для создания подклассов. Подклассы наследуют все данные и методы суперкласса, как и следовало ожидать. В C # мы бы использовали наследование следующим образом:

 public class Mammal {
    // Methods & data
}	
		
public class Human : Mammal {
}

В Ruby наследование выглядит так:

 class Mammal
end
	
class Dog < Mammal
end

В Ruby символ «меньше» используется для обозначения наследования.

Интерфейсы

Давайте поговорим об интерфейсах. Интерфейсы — это еще одна особенность, которую Ruby не сравнивает с .NET, и это потому, что Ruby имеет утку. Базовое определение типирования утки:

Если он ходит, как утка, плавает, как утка, и говорит, как утка, то это должна быть утка.

Теперь, если вы не знаете, что такое утка, это только усугубляет ситуацию. По сути, утка позволяет вам вызывать метод для любого экземпляра объекта независимо от его типа, и если этот экземпляр отвечает на метод, экземпляр выполнит его. Давайте посмотрим на пример:

 class Duck
  def talk
    puts "quack"
  end
end

class Dog
  def talk
    puts "woof"
  end
end

duck = Duck.new 
duck.talk         #=> quack

dog = Dog.new
dog.talk          #=> woof

В этом примере мы используем экземпляр Duck, экземпляр Dog и вызываем метод talk Поскольку оба экземпляра будут отвечать на talk Но что, если экземпляр не имеет метода, который вызывается на нем?

 class Truck
end

truck = Truck.new
truck.talk        
   #=> NoMethodError: undefined method `talk' for #<Truck:0x1011d1620>

Как видно из примера, NoMethodErrortalktruck Это типизированная утка: независимо от базового типа, если экземпляр может ответить на метод, он это сделает, если он не может, он выдаст ошибку . Так почему утка позволяет Ruby отказываться от интерфейсов? Потому что Руби не волнует типы.

В .NET интерфейсы используются для того, чтобы мы могли передавать экземпляры объектов различных типов в метод, и компилятор проверяет, соответствует ли этот тип экземпляра интерфейсу. Это позволяет нам определить наши ожидания для экземпляра, не говоря, как эти ожидания должны быть удовлетворены. Но поскольку Ruby имеет утку, каждый экземпляр может быть передан в этот метод — вы рискуете получить NoMethodError

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

Не поймите меня неправильно, интерфейсы являются важной концепцией, и они были изобретены по какой-то причине. Ruby использует другой подход в том, что интерфейсы неформально определяются в форме документации. Rack — это один из примеров кода, который формально определяет интерфейс (они называют его протоколом), которому должен соответствовать любой код, использующий Rack.

Вы видите Ruby, и Ruby-программисты любят принцип «Не повторяй себя» . Так зачем вам писать интерфейс в коде, а затем записывать его в свою документацию? Это дублированное усилие. Вместо этого напишите действительно хорошую документацию для своего кода, объясните свои интерфейсы и их рациональное использование и предоставьте потребителю кода для реализации интерфейс.

С учетом всего сказанного, если вы действительно хотите, вы можете определить интерфейс Ruby внутри кода . Это не то, что делает большинство Rubyists, так как это противоречит соглашениям Ruby, но вы можете сделать это.

И последнее, что нужно сделать при наборе Duck, — это то, что он имеет много преимуществ. Мы рассмотрим два из этих преимуществ позже, когда коснемся написания макетов для модульного тестирования и при выполнении метапрограммирования.

Абстрактные классы

Переходя к абстрактным классам, давайте наметим базовое определение:

Класс, помеченный как Abstract, диктует, что суперкласс не может быть создан, и что он должен быть создан как подкласс

Последствия этого определения состоят в том, что функциональность, объявленная в суперклассе, передается подклассу. Это определение важно, потому что в Ruby нет концепции абстрактных классов, как в .NET.

Вместо этого Ruby реализует то, что называется Mixins . Миксины очень похожи на абстрактные классы в том смысле, что их можно использовать для определения методов и данных, которые можно добавить в класс. Они не могут быть созданы непосредственно, но вместо этого включены в существующий класс. Вот краткий пример:

 module Cheerful
    def greeting
      puts "It's nice to meet you!"
    end
  end

  class Man
    include Cheerful
  end

  bill = Man.new
  bill.greeting     #=> It's nice to meet you!

Для этого кода мы объявляем moduleCheerful Модули также используются для пространств имен, о которых я расскажу в другом посте. Затем мы используем метод includeCheerfulMan Смешивая CheerfulManCheerful

Теперь это действительно базовый пример, но сила Mixins заключается в нашей способности смешивать любое количество модулей, которые мы хотим. Эффективно миксины позволяют вам писать общий код один раз — как код для перечисления по коллекциям — и применять его к широкому кругу классов (если вы читаете документацию по Enumerable, там тоже есть определение интерфейса!)

Поэтому, хотя Ruby не использует абстрактные классы, он вместо этого предоставляет миксины, которые можно использовать для достижения тех же целей, что и абстрактные классы.

И с этим этот пост на занятиях завершен. В этом посте было много информации. Мы затронули реализацию классов, открытые классы, наследование, интерфейсы, Duck Typing, абстрактные классы и миксины. В следующем посте «Переход с .NET на Ruby» мы рассмотрим, как методы и переменные работают в Ruby.

Как всегда комментарии и обсуждения приветствуются и приветствуются!