Шаблон разработки Ruby: Как сделать расширяемый класс фабрики?

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

X509Store store = new X509Store(StoreName.My, StoreLocation.LocalMachine);
store.Open(OpenFlags.ReadOnly);
X509Certificate2 cert = null;

foreach (X509Certificate2 certificate in store.Certificates)
{
    if (!string.IsNullOrWhiteSpace(certificate?.SubjectName?.Name) && certificate.SubjectName.Name.StartsWith("CN=*.mysite.com"))
    {
        cert = certificate;
        break;
    }
}
50
задан Instance Hunter 13 April 2009 в 16:12
поделиться

2 ответа

Вам не нужна фабрика LogFileReaderFactory; просто научите свой класс LogFileReader, как инстанцировать свои подклассы:

class LogFileReader
  def self.create type
    case type 
    when :git
      GitLogFileReader.new
    when :bzr
      BzrLogFileReader.new
    else
      raise "Bad log file type: #{type}"
    end
  end
end

class GitLogFileReader < LogFileReader
  def display
    puts "I'm a git log file reader!"
  end
end

class BzrLogFileReader < LogFileReader
  def display
    puts "A bzr log file reader..."
  end
end

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

class LogFileReader
  @@subclasses = { }
  def self.create type
    c = @@subclasses[type]
    if c
      c.new
    else
      raise "Bad log file type: #{type}"
    end
  end
  def self.register_reader name
    @@subclasses[name] = self
  end
end

class GitLogFileReader < LogFileReader
  def display
    puts "I'm a git log file reader!"
  end
  register_reader :git
end

class BzrLogFileReader < LogFileReader
  def display
    puts "A bzr log file reader..."
  end
  register_reader :bzr
end

LogFileReader.create(:git).display
LogFileReader.create(:bzr).display

class SvnLogFileReader < LogFileReader
  def display
    puts "Subersion reader, at your service."
  end
  register_reader :svn
end

LogFileReader.create(:svn).display

Вот и все. Просто разделите это на несколько файлов и требуйте их соответствующим образом.

Вам следует прочитать книгу Питера Норвига Design Patterns in Dynamic Languages, если вы интересуетесь подобными вещами. Он показывает, что многие паттерны проектирования на самом деле являются обходом ограничений или недостатков вашего языка программирования; и если у вас достаточно мощный и гибкий язык, вам не нужен паттерн проектирования, вы просто реализуете то, что хотите сделать. В качестве примеров он использует Dylan и Common Lisp, но многие из его положений актуальны и для Ruby.

Возможно, вы также захотите взглянуть на Why's Poignant Guide to Ruby, особенно на главы 5 и 6, но только если вы можете справиться с сюрреалистическим техническим письмом.

edit: Сейчас я отталкиваюсь от ответа Йорга; мне нравится сокращать количество повторений, и поэтому я не повторяю название системы контроля версий ни в классе, ни в регистрации. Добавив к моему второму примеру следующее, вы сможете писать гораздо более простые определения классов, оставаясь при этом довольно простыми и понятными.

def log_file_reader name, superclass=LogFileReader, &block
  Class.new(superclass, &block).register_reader(name)
end

log_file_reader :git do
  def display
    puts "I'm a git log file reader!"
  end
end

log_file_reader :bzr do
  def display
    puts "A bzr log file reader..."
  end
end

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

def log_file_reader name, superclass=LogFileReader, &block
  c = Class.new(superclass, &block)
  c.register_reader(name)
  Object.const_set("#{name.to_s.capitalize}LogFileReader", c)
end
95
ответ дан Brian Campbell 7 November 2019 в 10:38
поделиться

Это на самом деле просто подрывает решение Брайана Кэмпбелла. Если вам это нравится, , пожалуйста, upvote , его ответ : он сделал всю работу.

#!/usr/bin/env ruby

class Object; def eigenclass; class << self; self end end end

module LogFileReader
  class LogFileReaderNotFoundError < NameError; end
  class << self
    def create type
      (self[type] ||= const_get("#{type.to_s.capitalize}LogFileReader")).new
    rescue NameError => e
      raise LogFileReaderNotFoundError, "Bad log file type: #{type}" if e.class == NameError && e.message =~ /[^: ]LogFileReader/
      raise
    end

    def []=(type, klass)
      @readers ||= {type => klass}
      def []=(type, klass)
        @readers[type] = klass
      end
      klass
    end

    def [](type)
      @readers ||= {}
      def [](type)
        @readers[type]
      end
      nil
    end

    def included klass
      self[klass.name[/[[:upper:]][[:lower:]]*/].downcase.to_sym] = klass if klass.is_a? Class
    end
  end
end

def LogFileReader type

Здесь мы создаем глобальный метод (более похожий на процедуру, на самом деле), называемый LogFileReader , то же имя, что и у нашего модуля LogFileReader . Это законно в Ruby. Неоднозначность разрешается следующим образом: модуль всегда будет предпочтительным, кроме случаев, когда это, очевидно, вызов метода, т.е. вы либо ставите круглые скобки в конце ( Foo () ), либо передаете аргумент ( Foo : bar ).

Это трюк, который используется в нескольких местах в stdlib, а также в Camping и других фреймворках. Потому что такие вещи, как включают или , расширяют aren ' Фактически, ключевые слова, но обычные методы, которые принимают обычные параметры, вам не нужно передавать им фактический модуль в качестве аргумента, вы также можете передать все, что оценивает , в . ] Модуль . На самом деле, это даже работает для наследования, совершенно законно написать класс Foo .

С помощью этого трюка вы можете сделать так, как будто вы наследуете от универсальный класс, хотя в Ruby нет универсальных. Это используется, например, в библиотеке делегирования, где вы делаете что-то вроде класса MyFoo , и в результате происходит то, что метод SimpleDelegator динамически создает и возвращает анонимный подкласс класса SimpleDelegator , который делегирует все вызовы метода экземпляру класса Foo .

Здесь мы используем похожую хитрость: мы собираемся динамически создать модуль , который при смешивании в класс, автоматически зарегистрирует этот класс в реестре LogFileReader .

  LogFileReader.const_set type.to_s.capitalize, Module.new {

В этой строке происходит многое. Давайте начнем справа: Module.new создает новый анонимный модуль. Блок, переданный ему, становится телом модуля - это в основном то же самое, что и использование ключевого слова module .

Теперь перейдем к const_set . Это метод установки константы. Таким образом, это то же самое, что сказать FOO =: bar , за исключением того, что мы можем передать имя константы в качестве параметра, вместо того, чтобы знать это заранее. Так как мы вызываем метод в модуле LogFileReader , константа будет определена внутри этого пространства имен, поэтому она будет называться LogFileReader :: Something .

Итак, что имя константы? Ну, это аргумент типа типа , переданный в метод с заглавной буквы. Итак, когда я передаю в : cvs , полученная константа будет LogFileParser :: Cvs .

И для чего мы устанавливаем константу? Нашему недавно созданному анонимному модулю, который теперь больше не является анонимным!

Все это на самом деле является просто длинным способом сказать module LogFileReader :: Cvs , за исключением того, что мы не знали "Cvs" "заранее, и поэтому не мог написать это так.

    eigenclass.send :define_method, :included do |klass|

Это тело нашего модуля. Здесь мы используем define_method для динамического определения метода, называемого , включенного . И мы фактически не определяем метод на самом модуле, а на собственном классе модуля (с помощью небольшого вспомогательного метода, который мы определили выше), что означает, что метод не станет методом экземпляра, но скорее «статический» метод (в терминах Java / .NET).

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

      LogFileReader[type] = klass

И вот что делает наш метод ловушки: он регистрирует класс, который передается методу hook, в реестр LogFileReader . И ключ, под которым он его регистрирует, это аргумент type из метода LogFileReader , описанного выше, который благодаря магии замыканий фактически доступен внутри включенного Метод .

    end
    include LogFileReader

И наконец, мы включаем модуль LogFileReader в анонимный модуль. [Примечание: я забыл эту строку в исходном примере.]

  }
end

class GitLogFileReader
  def display
    puts "I'm a git log file reader!"
  end
end

class BzrFrobnicator
  include LogFileReader
  def display
    puts "A bzr log file reader..."
  end
end

LogFileReader.create(:git).display
LogFileReader.create(:bzr).display

class NameThatDoesntFitThePattern
  include LogFileReader(:darcs)
  def display
    puts "Darcs reader, lazily evaluating your pure functions."
  end
end

LogFileReader.create(:darcs).display

puts 'Here you can see, how the LogFileReader::Darcs module ended up in the inheritance chain:'
p LogFileReader.create(:darcs).class.ancestors

puts 'Here you can see, how all the lookups ended up getting cached in the registry:'
p LogFileReader.send :instance_variable_get, :@readers

puts 'And this is what happens, when you try instantiating a non-existent reader:'
LogFileReader.create(:gobbledigook)

Эта новая расширенная версия позволяет три различных способа определения LogFileReader s:

  1. Все классы, чье имя соответствует шаблону <Имя > LogFileReader будет автоматически найден и зарегистрирован как LogFileReader для : имя (см .: GitLogFileReader ),
  2. Все классы, которые смешиваются в модуле LogFileReader и чье имя соответствует шаблону <Имя> Что бы было зарегистрировано для : name обработчик (см .: BzrFrobnicator ) и
  3. Все классы, которые смешиваются в модуле LogFileReader (: name) , будут зарегистрированы для : name Обработчик , независимо от их имени (см .: NameThatDoesntFitThePattern ).

Обратите внимание, что это очень надуманная демонстрация. Это, например, определенно не поточно-ориентированный. Это также может привести к утечке памяти. Используйте с осторожностью!

Все, что
будет зарегистрировано для обработчика : name (см .: BzrFrobnicator ) и
  • Все классы, которые смешиваются в LogFileReader (: name) модуль, будет зарегистрирован для обработчика : name , независимо от его имени (см .: NameThatDoesntFitThePattern ).
  • Обратите внимание, что это всего лишь надуманная демонстрация. Это, например, определенно не поточно-ориентированный. Это также может привести к утечке памяти. Используйте с осторожностью!

    Все, что
    будет зарегистрировано для обработчика : name (см .: BzrFrobnicator ) и
  • Все классы, которые смешиваются в LogFileReader (: name) модуль, будет зарегистрирован для обработчика : name , независимо от его имени (см .: NameThatDoesntFitThePattern ).
  • Обратите внимание, что это всего лишь надуманная демонстрация. Это, например, определенно не поточно-ориентированный. Это также может привести к утечке памяти. Используйте с осторожностью!

    Обратите внимание, что это просто очень надуманная демонстрация. Это, например, определенно не поточно-ориентированный. Это также может привести к утечке памяти. Используйте с осторожностью!

    Обратите внимание, что это просто очень надуманная демонстрация. Это, например, определенно не поточно-ориентированный. Это также может привести к утечке памяти. Используйте с осторожностью!

    18
    ответ дан Jörg W Mittag 7 November 2019 в 10:38
    поделиться
    Другие вопросы по тегам:

    Похожие вопросы: