为ruby类添加可互换行为的最佳方法是什么?

时间:2014-07-22 15:15:42

标签: c# ruby oop module

我来自C#背景,这可能是我犹豫的原因,但想象我们有以下情况。我们有一个类接收一些原始数据,然后将其推送到一些外部API。在这样做的过程中,我们需要根据一些业务规则处理数据。问题是,这些业务规则并不总是相同的。在C#中,我会使用一个接口进行数据处理"业务规则,然后在不同时间使用不同的实现:

public interface IDataProcessor
{
    object Process(object data);
}

public class SomeBuilder
{
    private object _data;
    private IDataProcessor _dataProcessor;
    public SomeBuilder(object data, IDataProcessor dataProcessor)
    {
        _data = data;
        _dataProcessor = dataProcessor;
    }

    public void Build()
    {
        var processedData = _dataProcessor.Process(_data);
        //do the fun building stuff with processedData
    }
}

在上面的示例中,我将使用不同的IDataProcessor实现来处理数据。

现在,在Ruby世界中,我的第一个想法是做类似的事情:

class SomeBuilder
    def initialize(some_data, data_processor)
        @some_date = some_date
        @data_processor = data_processor
    end

    def build
        processed_data = @data_processor.process(@some_data)
        #do build logic
    end
end

我的问题有两个方面:首先,这不知道"感觉"在Ruby生态系统中非常正确。例如这是Ruby方式吗?第二,如果我沿着这条路走下去,data_processor会是什么样子?感觉它应该是一个模块,因为它只是一些行为,但如果 只是一个模块,我如何在SomeBuilder类中互换使用不同的模块?

提前致谢!

4 个答案:

答案 0 :(得分:1)

  

感觉它应该是一个模块

我不知道这件事。模块用于将相同的行为注入到类(或其他模块)中,并且您需要不同的行为。

  

如何在SomeBuilder中交替使用不同的模块   类?

在ruby中,您将获得运行时错误,而不是编译器强制执行您的接口类具有Process方法的规则。因此,您可以将所需的任何类与SomeBuilder类结合使用,但如果该类没有Process方法,则会出现运行时错误。换句话说,在ruby中,您不必向编译器宣布您与SomeBuilder类一起使用的类必须实现IDataProcessor接口。

评论示例:

class SomeBuilder
  def initialize(some_data)
    @some_data = some_data
  end

  def build
    processed_data = yield @some_data
    puts processed_data
  end
end

sb = SomeBuilder.new(10)

sb.build do |data|
  data * 2
end

sb.build do |data|
  data * 3
end


--output:--
20
30

答案 1 :(得分:0)

你走在正确的轨道上。可以使用Ruby模块和类。在这种情况下,我倾向于拥有一个DataProcessor模块来包装处理器的各种具体实现。所以,这会给:

banana_processor.rb:

module DataProcessor
  class Banana
     def process(data)
       # ...
     end

     # ...
  end
end

apple_processor.rb:

module DataProcessor
   class Apple
      def process(data)
        # ...
      end

      # ...
   end
end

然后用:

实例化处理器
processor = DataProcessor::Apple.new

基本上,模块DataProcessor“命名”您的处理器类,在更大的库中更有用,在这些库中您可能会发生名称冲突,或者如果您生成的gem可能包含在可能存在名称冲突的任意数量的项目中。

然后是您的Builder类

class SomeBuilder
  attr_reader :data, :processor, :processed_data

  def initialize(data, processor)
    @data = data
    @processor = processor
  end

  def build
    @processed_data = @processor.process(@data)
    # do build logic
  end

......并使用:

some_processor = DataProcessor::Apple.new
builder = SomeBuilder.new(some_data, some_processor)
builder.build

答案 2 :(得分:0)

结合其他答案的想法:

class DataProcessor
  def initialize &blk
    @process = blk
  end

  def process data
    @process[data]
  end
end

multiply_processor = DataProcessor.new do |data|
  data * 10
end

reverse_processor = DataProcessor.new do |data|
  data.reverse
end

这适用于您的示例SomeBuilder类。

这是Michael Lang和7stud的答案之间的一种愉快的媒介。所有数据处理器都是一个类的实例,它允许您强制执行常见行为,并且每个处理器都使用一个简单的块来定义,这样可以在定义新的时最小化代码重现。

答案 3 :(得分:0)

您的界面只有一种方法。仅具有单个方法的对象与函数/过程同构。 (并且具有一些字段和单个方法的对象与闭包同构)。因此,您实际上会使用一流的程序。

class SomeBuilder
  def initialize(some_data, &data_processor)
    @some_data, @data_processor = some_data, data_processor
  end

  def build
    processed_data = @data_processor.(@some_data)
    #do build logic
  end
end

# e.g. a stringifier-builder:
builder = SomeBuilder.new(42) do |data| data.to_s end
# which in this case is equivalent to:
builder = SomeBuilder.new(42, &:to_s)

实际上,您可能也会在C#中做同样的事情。 (另外,你可能应该把它变成通用的。)

public class SomeBuilder<I, O>
{
    private I _data;
    private Func<I, O> _dataProcessor;
    public SomeBuilder(I data, Func<I, O> dataProcessor)
    {
        _data = data;
        _dataProcessor = dataProcessor;
    }

    public void Build()
    {
        var processedData = _dataProcessor(_data);
        //do the fun building stuff with processedData
    }
}

// untested

// e.g. a stringifier-builder
var builder = new SomeBuilder(42, data => data.ToString());