Методы частного модуля в Ruby

108

У меня вопрос из двух частей

Лучшая практика

  • У меня есть алгоритм, который выполняет некоторую операцию над структурой данных с помощью общедоступного интерфейса.
  • В настоящее время это модуль с множеством статических методов, все частные, за исключением одного метода открытого интерфейса.
  • Есть одна переменная экземпляра, которая должна использоваться всеми методами.

Вот варианты, которые я вижу, и какой из них лучший ?:

  • Модуль со статическими ('модуль' в ruby) методами
  • Класс со статическими методами
  • Модуль Mixin для включения в структуру данных
  • Выполните рефакторинг той части алгоритма, которая изменяет эту структуру данных (очень маленькую), и сделайте ее миксином, который вызывает статические методы модуля алгоритма.

Техническая часть

Есть ли способ сделать частный метод модуля ?

module Thing
  def self.pub; puts "Public method"; end
  private
  def self.priv; puts "Private method"; end
end

privateТам , кажется, не имеют никакого эффекта , я все еще могу назвать Thing.privбез проблем.

Дэниел Бердсли
источник
5
К вашему сведению, в Ruby нет такой вещи, как «статический» метод, они называются методами экземпляра класса
Брэд,
31
Старый комментарий, но поскольку он получил четыре голоса за, я должен указать, что не существует такой вещи, как «метод экземпляра класса». «Метод класса» - правильный термин.
micapam
5
privateвлияет только на методы экземпляра, но не на методы класса. используйте private_class_methodвместо этого:module Thing; def self.pub; end; private_class_method :pub; end
apeiros
1
@micapam Методы экземпляра класса существуют в Ruby, и они отличаются от методов класса.
Marnen Laibow-Koser,

Ответы:

88

Я думаю, что лучший способ (и в основном то, как пишутся существующие библиотеки) сделать это - создать класс внутри модуля, который имеет дело со всей логикой, а модуль просто предоставляет удобный метод, например

module GTranslate
  class Translator
    def perform( text ); translate( text ); end

    private

    def translate( text )
      # do some private stuff here
    end
  end

  def self.translate( text )
    t = Translator.new
    t.perform( text )
  end
end
ucron
источник
14
Ruby newb здесь. В этом примере представлен ли класс Translator как часть открытого интерфейса модуля? Можно ли ограничить доступ к методу выполнения в GTranslate?
rshepherd
2
@rshepherd Это performне тот метод, который должен быть здесь частным, частный метод - это частный метод в Translatorклассе (в примере @ ucron его нет, что очень прискорбно). GTranslate.translateэто всего лишь удобный метод GTranslate::Translator#perform, нет никакой реальной выгоды, скрывающей его, если бы это было вообще возможно.
michelpm
28
Я не уверен, чего можно достичь, проведя здесь урок. Если цель состоит в том, чтобы иметь метод частного модуля, то это не соответствует цели. Потому что вы можете получить доступ к методу «выполнить» извне модуля, вызвав GTranslate :: Translator.new.perform. Другими словами, это не личное.
Zack Xu
1
@jschorr Я думаю, что Op и этот ответ предназначены для создания частного класса или метода модуля, а не метода экземпляра. Кроме того, это не сделает какой-либо метод экземпляра закрытым, поскольку self.translateобъявляет метод класса / модуля.
konsolebox
5
GTranslate::Translator.new.perform(text)- запутанно, но не приватно!
abhillman
80

Есть также Module.private_class_method, что, возможно, выражает большее намерение.

module Foo
  def self.included(base)
    base.instance_eval do
      def method_name
        # ...
      end
      private_class_method :method_name
    end
  end
end

Для кода в вопросе:

module Thing
  def self.pub; puts "Public method"; end
  def self.priv; puts "Private method"; end
  private_class_method :priv
end

Ruby 2.1 или новее:

module Thing
  def self.pub; puts "Public method"; end
  private_class_method def self.priv; puts "Private method"; end
end
консольбокс
источник
Я не знал об этом. Будет ли работать и до определения метода, вроде private?
Marnen Laibow-Koser
7
Этот ответ вместе с ответом @JCooper - настоящее решение. @ MarnenLaibow-Koser Это не так. Вы можете рассмотреть другой ответ за счет большего количества группировок и отступов. На самом деле это может быть предпочтительным решением для некоторых. (Ответ только для справки.)
konsolebox
58
module Writer
  class << self
    def output(s)
      puts upcase(s)
    end

    private

    def upcase(s)
      s.upcase
    end
  end
end

Writer.output "Hello World"
# -> HELLO WORLD

Writer.upcase "Hello World"
# -> so.rb:16:in `<main>': private method `upcase' called for Writer:Module (NoMethodError)
cdrev
источник
4
Это должен быть принятый ответ. На мой взгляд, чисто и идиоматично.
Мартин Няга,
@MartinNyaga у этого есть недостаток в том, что у него нет include Writerвыбора!
Ulysse BN
28

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

module Foo
  def self.included(base)
    class << base 
      def public_method
        puts "public method"
      end
      def call_private
        private_method
      end
      private
      def private_method
        puts "private"
      end
    end
  end
end

class Bar
  include Foo
end

Bar.public_method

begin
  Bar.private_method
rescue
  puts "couldn't call private method"
end

Bar.call_private
Кэмерон Прайс
источник
5
Это умно. Так что это возможно, но, вероятно, того не стоит.
Дэниел Бердсли
хорошо работает. Я использовал included do |base| [...] endвместо def
Crystark 01
5
@Crystark: Этот синтаксис существует только в модулях, расширяющих ActiveSupport :: Concern, если я не ошибаюсь. т.е. это рельсы.
Vaz
11

К сожалению, privateотносится только к методам экземпляра. Общий способ получить частные «статические» методы в классе - это сделать что-то вроде:

class << self
  private

  def foo()
   ....
  end
end

По общему признанию, я не играл с этим в модулях.

Дж. Купер
источник
7
Это неправда. У вас могут быть методы частного класса и методы частного модуля.
mikeycgto
У вас могут быть методы частного класса, но простое выполнение этого не приведет к .fooсозданию метода частного класса: "private; def self.foo ()"
Ари
@mikeycgto Не хотите ли подробно описать разницу между методами частного класса и методами частного модуля? Потому что я думаю, что они такие же. Обратите внимание, что оба privateи private_class_methodпринадлежат Modulenot Class. Кстати, этот код работает и является альтернативой использованию private_class_method.
konsolebox
3

Хороший способ такой

module MyModule
  class << self
    def public_method
      # you may call the private method here
      tmp = private_method
      :public
    end

    private def private_method
      :private
    end
  end
end

# calling from outside the module
puts MyModule::public_method
Таллак Твейде
источник
1

Как насчет хранения методов в виде лямбда-выражений в переменных / константах класса?

module MyModule
  @@my_secret_method = lambda {
    # ...
  }
  # ...
end

Для теста:
UPD: огромное обновление этого кода через 6 лет показывает более чистый способ объявления частного методаd

module A
  @@L = lambda{ "@@L" }
  def self.a ; @@L[] ; end
  def self.b ; a ; end

  class << self
    def c ; @@L[] ; end
    private
    def d ; @@L[] ; end
  end
  def self.e ; c ; end
  def self.f ; self.c ; end
  def self.g ; d ; end
  def self.h ; self.d ; end

  private
  def self.i ; @@L[] ; end
  class << self
    def j ; @@L[] ; end
  end

  public
  def self.k ; i ; end
  def self.l ; self.i ; end
  def self.m ; j ; end
  def self.n ; self.j ; end
end

for expr in %w{ A.a A.b A.c A.d A.e A.f A.g A.h A.i A.j A.k A.l A.m A.n }
  puts "#{expr} => #{begin ; eval expr ; rescue => e ; e ; end}"
end

Здесь мы видим, что:

A.a => @@L
A.b => @@L
A.c => @@L
A.d => private method `d' called for A:Module
A.e => @@L
A.f => @@L
A.g => @@L
A.h => private method `d' called for A:Module
A.i => @@L
A.j => @@L
A.k => @@L
A.l => @@L
A.m => @@L
A.n => @@L

1) @@Lне может быть доступен извне, но доступен практически отовсюду
2) class << self ; private ; defуспешно делает метод dнедоступным извне и изнутри с ним, self.но не без него - это странно
3) private ; self.и private ; class << selfне делает методы закрытыми - они доступны как с и безself.

Накилон
источник
лямбды - это совсем не то же самое, что методы. лямбды относятся к типу Proc, а методы - к типу Method.
Майкл Дорст
1
глобальные переменные плохие
чемпион 07
@achempion, где ты их видишь?
Накилон 07
@Nakilon, мои извинения, отредактируйте свой ответ, если хотите, чтобы я отменил свой голос
чемпион
0

Сделайте приватный модуль или класс

Константы никогда не бывают закрытыми. Однако можно создать модуль или класс, не назначая его константе.

Таким образом, альтернативой :private_class_methodявляется создание частного модуля или класса и определение на нем общедоступных методов.

module PublicModule
  def self.do_stuff(input)
    @private_implementation.do_stuff(input)
  end

  @private_implementation = Module.new do
    def self.do_stuff(input)
      input.upcase # or call other methods on module
    end
  end
end

Использование:

PublicModule.do_stuff("whatever") # => "WHATEVER"

См. Документацию для Module.new и Class.new .

Натан Лонг
источник
Мне очень нравится этот метод. Но не представляется возможным удалить .selfв определениях методов, включить их в другой класс и использовать их как instance_methods включающего класса. Вы знаете, есть ли способ заставить его работать?
Шиясон
0

Этот метод не позволит обмениваться данными с частными методами, если вы явно не передадите данные с помощью параметров метода.

module Thing
  extend self

  def pub
    puts priv(123)
  end

  private
  
  def priv(value)
    puts "Private method with value #{value}"
  end
end

Thing.pub
# "Private method with value 123"

Thing.priv
# NoMethodError (private method `priv' called for Thing:Module)
Джерри Шоу
источник