我来自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类中互换使用不同的模块?
提前致谢!
答案 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());