• Ruby 70 0 1 发布

    “如果一个东西,走路像鸭子,叫声也像鸭子,那么它就是鸭子”。这个就是Duck Tying的通俗解释。

    在ruby中,对象有效的语义,不是由继承自特定的类或实现特定的接口,而是由当前方法和属性的集合决定。

    也就是说对象类型是根据对象能作什么即对象的行为(method)来决定,而不是其类。

    Dock Typing Wiki

    下面通过实例来加深理解:

    想象一下小黄鸭的制作流程

    Worker制作小黄鸭模具。Cooker将塑料倒入模具烘制塑料小黄鸭。Painter给小黄鸭上色QualityAssurance对小黄鸭进行质保检查。Packager打包小黄鸭。 class Duck def prepare(workers) workers.each do |worker, index| case worker when Moulder worker.pour_mold(self) when Cooker worker.cook_duck(self) when Painter worker.paint_duck(self) when QualityAssurance worker.test_duck(self) when Packager worker.package_duck(self) end end end end

    上面的代码,造成了互相依赖的问题,Dock需要知道worker以及各worker的具体实现细节(work的类以及work的prepare方法)。

    这样的代码,高度耦合,不利于后续的扩展与维护。

    下面使用Dock Typing的概念对其重构:

    class Duck def prepare(workers) workers.each{ |worker| worker.prepare_duck(self) } end end class Moulder def prepare_duck(duck) puts "heat_plastic" puts "pour_mold" end end class Cooker def prepare_duck(duck) puts "set_oven_temp('150')" puts "cook_for('15min')" end end class Painter def prepare_duck(duck) puts "paint_body('yellow')" puts "paint_beak('orange')" puts "paint_eyes" end end class QualityAssurance def prepare_duck(duck) puts "float_duck" puts "inspect_duck" end end class Packager def prepare_duck(duck) puts "wrap_duck" puts "box_duck" end end d = Duck.new d.prepare([Moulder.new, Cooker.new, Painter.new, QualityAssurance.new, Packager.new])

    改造后,如果后续需要加入运输流程:

    6.Transporter发送小黄鸭。

    只需给Transporter定义一个prepare_duck的方法,而无需改动duck的代码。

    class Transporter def prepare_duck(duck) puts "sending" end end d = Duck.new d.prepare([Moulder.new, Cooker.new, Painter.new, QualityAssurance.new, Packager.new, Transporter.new])

    套用Duck Typing的说法“如果一个流程实现了prepare_duck的方法,那么它就可以并入到duck的制作流程中”。

    这里只关心prepare_duck这个行为,而不关心其具体的类。

    考虑下如果想给制作流程增加限制条件(每个流程都依赖前一流程),要怎么实现?

    class Duck attr_access :processes def prepare(workers) workers.each do |work| if(self.is_prepared(work) work.prepare_duck(self) self.add_process(work.name, true) end end end def add_process(name, value) item = @processes[name]; if(item) item.value = value; else @processes[name] = { name: name, value: value } end end def is_prepared(process) if(process) process.pre ? @proecess[process.pre].value : true else @processes.values.filter{|p| return !p.value}.length end end end class Moulder attr_writer :name attr_writer :pre def name @name || "moduler" end def pre @pre || null end def prepare_duck(duck) ... end end class Cooker attr_writer :name attr_writer :pre def name @name || "cooker" end def pre @pre || "moduler" end def prepare_duck(duck) ... end end ... d = Duck.new d.prepare([Moulder.new, Cooker.new, Painter.new, QualityAssurance.new, Packager.new, Transporter.new]) puts "congratulation your duck is prepared!" if d.is_prepared()

热门总结

  • Ruby 70 0 1 发布

    “如果一个东西,走路像鸭子,叫声也像鸭子,那么它就是鸭子”。这个就是Duck Tying的通俗解释。

    在ruby中,对象有效的语义,不是由继承自特定的类或实现特定的接口,而是由当前方法和属性的集合决定。

    也就是说对象类型是根据对象能作什么即对象的行为(method)来决定,而不是其类。

    Dock Typing Wiki

    下面通过实例来加深理解:

    想象一下小黄鸭的制作流程

    Worker制作小黄鸭模具。Cooker将塑料倒入模具烘制塑料小黄鸭。Painter给小黄鸭上色QualityAssurance对小黄鸭进行质保检查。Packager打包小黄鸭。 class Duck def prepare(workers) workers.each do |worker, index| case worker when Moulder worker.pour_mold(self) when Cooker worker.cook_duck(self) when Painter worker.paint_duck(self) when QualityAssurance worker.test_duck(self) when Packager worker.package_duck(self) end end end end

    上面的代码,造成了互相依赖的问题,Dock需要知道worker以及各worker的具体实现细节(work的类以及work的prepare方法)。

    这样的代码,高度耦合,不利于后续的扩展与维护。

    下面使用Dock Typing的概念对其重构:

    class Duck def prepare(workers) workers.each{ |worker| worker.prepare_duck(self) } end end class Moulder def prepare_duck(duck) puts "heat_plastic" puts "pour_mold" end end class Cooker def prepare_duck(duck) puts "set_oven_temp('150')" puts "cook_for('15min')" end end class Painter def prepare_duck(duck) puts "paint_body('yellow')" puts "paint_beak('orange')" puts "paint_eyes" end end class QualityAssurance def prepare_duck(duck) puts "float_duck" puts "inspect_duck" end end class Packager def prepare_duck(duck) puts "wrap_duck" puts "box_duck" end end d = Duck.new d.prepare([Moulder.new, Cooker.new, Painter.new, QualityAssurance.new, Packager.new])

    改造后,如果后续需要加入运输流程:

    6.Transporter发送小黄鸭。

    只需给Transporter定义一个prepare_duck的方法,而无需改动duck的代码。

    class Transporter def prepare_duck(duck) puts "sending" end end d = Duck.new d.prepare([Moulder.new, Cooker.new, Painter.new, QualityAssurance.new, Packager.new, Transporter.new])

    套用Duck Typing的说法“如果一个流程实现了prepare_duck的方法,那么它就可以并入到duck的制作流程中”。

    这里只关心prepare_duck这个行为,而不关心其具体的类。

    考虑下如果想给制作流程增加限制条件(每个流程都依赖前一流程),要怎么实现?

    class Duck attr_access :processes def prepare(workers) workers.each do |work| if(self.is_prepared(work) work.prepare_duck(self) self.add_process(work.name, true) end end end def add_process(name, value) item = @processes[name]; if(item) item.value = value; else @processes[name] = { name: name, value: value } end end def is_prepared(process) if(process) process.pre ? @proecess[process.pre].value : true else @processes.values.filter{|p| return !p.value}.length end end end class Moulder attr_writer :name attr_writer :pre def name @name || "moduler" end def pre @pre || null end def prepare_duck(duck) ... end end class Cooker attr_writer :name attr_writer :pre def name @name || "cooker" end def pre @pre || "moduler" end def prepare_duck(duck) ... end end ... d = Duck.new d.prepare([Moulder.new, Cooker.new, Painter.new, QualityAssurance.new, Packager.new, Transporter.new]) puts "congratulation your duck is prepared!" if d.is_prepared()