[英]What's the best way to add interchangeable behavior to a ruby class?
我來自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課堂上使用不同的模塊?
提前致謝!
感覺應該是一個模塊
我不知道 模塊用於將相同的行為注入類(或其他模塊),而您想要不同的行為。
如何在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
您走在正確的軌道上。 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
結合其他答案的想法:
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答案之間的一種快樂媒介。 所有數據處理器都是一類的實例,它使您可以強制執行常見的行為,並且每個處理器都使用一個簡單的塊進行定義,從而在定義新的塊時最大程度地減少了代碼替換。
您的界面只有一個方法。 僅具有單一方法的對象與函數/過程同構。 (並且具有某些字段和單個方法的對象與閉包同構)。 因此,您實際上將使用一流的過程。
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());
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.