Ruby设计模式:如何使可扩展的工厂类? [英] Ruby design pattern: How to make an extensible factory class?

查看:100
本文介绍了Ruby设计模式:如何使可扩展的工厂类?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

好的,假设我有Ruby程序来读取版本控制日志文件,并用数据做一些事情。 (我不是,但情况类似,我很乐意与这些类比)。我们假设现在我想支持巴扎和吉特。假设程序将执行一些参数,指出使用哪个版本控制软件。



鉴于此,我想制作一个LogFileReaderFactory,其名称为版本控制程序将返回一个适当的日志文件读取器(从泛型子类)读取日志文件并吐出规范的内部表示。所以我当然可以让BazaarLogFileReader和GitLogFileReader将它们硬编码到程序中,但是我希望它可以这样设置,即添加对新版本控制程序的支持就像一个新的类文件一样简单在Bazaar和Git读者的目录中。



所以,现在你可以调用do-something-with-log -software git和do -something-with-log -software bazaar,因为那里有日志读取器。我想要的是可以简单地将SVNLogFileReader类和文件添加到同一个目录,并自动调用do-something-with-log -software svn,而不会对其余的程序。 (这些文件当然可以用一个特定的模式命名,并且在需要调用中加上globbed。)



我知道这可以在Ruby中完成...我只是'我应该怎么做...或者我应该这样做。

解决方案

你不需要一个LogFileReaderFactory ;只是教你的LogFileReader类如何实例化它的子类:

  class LogFileReader 
def self.create type
案例类型$​​ b $ b当:git
GitLogFileReader.new
当:bzr
BzrLogFileReader.new
else
raise错误的日志文件类型:#{type}
end
end
end

class GitLogFileReader< LogFileReader
def显示
puts我是一个git日志文件读取器!
end
end

class BzrLogFileReader< LogFileReader
def显示
putsA bzr日志文件阅读器...
end
end

如您所见,超类可以作为自己的工厂。现在,自动注册怎么样?那么,为什么我们不要保留我们注册的子类的哈希值,并在我们定义它们时注册它们:

  class LogFileReader 
@@ subclasses = {}
def self.create type
c = @@ subclasses [type]
如果c
c.new
else
raise坏日志文件类型:#{type}
end
end
def self.register_reader name
@@ subclasses [name] = self
end
end

class GitLogFileReader< LogFileReader
def显示
puts我是一个git日志文件读取器!
end
register_reader:git
end

class BzrLogFileReader< LogFileReader
def显示
putsA bzr日志文件阅读器...
end
register_reader:bzr
end

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

class SvnLogFileReader< LogFileReader
def显示
putsSubversion reader,在您的服务中。
end
register_reader:svn
end

LogFileReader.create(:svn).display

你有它。只需将其分解成几个文件,并要求它们适当。



您应该阅读Peter Norvig的动态语言中的设计模式,如果您对这种事情感兴趣。他演示了多少设计模式实际上围绕您的编程语言的限制或不足之处;并且使用足够强大而灵活的语言,您并不需要设计模式,您只需实现所需的功能。他使用Dylan和Common Lisp作为例子,但是他的很多观点也与Ruby有关。



你可能还想看看为什么是Ruby的恶意指南,特别是第5章和第6章,虽然只有当你可以处理超现实主义的技术写作时。



编辑:立即解除Jörg的回答;我喜欢减少重复,所以不要在类和注册中重复版本控制系统的名称。将以下内容添加到第二个示例中将允许您编写更简单的类定义,同时仍然非常简单易懂。

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

log_file_reader:git do
def显示
puts我是一个git日志文件阅读器!
end
end

log_file_reader:bzr do
def显示
放置a bzr日志文件阅读器...
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


Ok, suppose I have Ruby program to read version control log files and do something with the data. (I don't, but the situation is analogous, and I have fun with these analogies). Let's suppose right now I want to support Bazaar and Git. Let's suppose the program will be executed with some kind of argument indicating which version control software is being used.

Given this, I want to make a LogFileReaderFactory which given the name of a version control program will return an appropriate log file reader (subclassed from a generic) to read the log file and spit out a canonical internal representation. So, of course, I can make BazaarLogFileReader and GitLogFileReader and hard-code them into the program, but I want it to be set up in such a way that adding support for a new version control program is as simple as plopping a new class file in the directory with the Bazaar and Git readers.

So, right now you can call "do-something-with-the-log --software git" and "do-something-with-the-log --software bazaar" because there are log readers for those. What I want is for it to be possible to simply add a SVNLogFileReader class and file to the same directory and automatically be able to call "do-something-with-the-log --software svn" without ANY changes to the rest of the program. (The files can of course be named with a specific pattern and globbed in the require call.)

I know this can be done in Ruby... I just don't how I should do it... or if I should do it at all.

解决方案

You don't need a LogFileReaderFactory; just teach your LogFileReader class how to instantiate its subclasses:

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

As you can see, the superclass can act as its own factory. Now, how about automatic registration? Well, why don't we just keep a hash of our registered subclasses, and register each one when we define them:

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

And there you have it. Just split that up into a few files, and require them appropriately.

You should read Peter Norvig's Design Patterns in Dynamic Languages if you're interested in this sort of thing. He demonstrates how many design patterns are actually working around restrictions or inadequacies in your programming language; and with a sufficiently powerful and flexible language, you don't really need a design pattern, you just implement what you want to do. He uses Dylan and Common Lisp for examples, but many of his points are relevant to Ruby as well.

You might also want to take a look at Why's Poignant Guide to Ruby, particularly chapters 5 and 6, though only if you can deal with surrealist technical writing.

edit: Riffing of off Jörg's answer now; I do like reducing repetition, and so not repeating the name of the version control system in both the class and the registration. Adding the following to my second example will allow you to write much simpler class definitions while still being pretty simple and easy to understand.

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

Of course, in production code, you may want to actually name those classes, by generating a constant definition based on the name passed in, for better error messages.

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

这篇关于Ruby设计模式:如何使可扩展的工厂类?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆