2017-01-25 2 views
2

Я изучаю реализацию adapter pattern в рубине. Я хочу получить доступ к переменной экземпляра в определении модуля адаптера. Взгляните на следующий код:Шаблон адаптера в рубине: доступ к вашим переменным экземпляра

module Adapter 
    module Dog 
    def self.speak 
     # I want to access the #name instance variable from my Animal instance 
     puts "#{name} says: woof!" 
    end 
    end 

    module Cat 
    def self.speak 
     # I want to access the #name instance variable from my Animal instance 
     puts "#{name} says: meow!" 
    end 
    end 
end 

class Animal 
    attr_accessor :name 

    def initialize(name) 
    @name = name 
    end 

    def speak 
    self.adapter.speak 
    end 

    def adapter 
    return @adapter if @adapter 
    self.adapter = :dog 
    @adapter 
    end 

    def adapter=(adapter) 
    @adapter = Adapter.const_get(adapter.to_s.capitalize) 
    end 
end 

Чтобы проверить это, я сделал следующее:

animal = Animal.new("catdog") 
animal.adapter = :cat 
animal.speak 

Я хочу, чтобы вернуться в следующем:

catdog says: meow! 

Вместо этого он говорит:

Adapter::Cat says: meow! 

Любые советы о том, как я c получить доступ к методу экземпляра Animal#name из модуля адаптера? Я думаю, проблема заключается в том, что мои методы-адаптеры являются методами класса.

Спасибо!

+0

Возможно, вы захотите '@adapter || =: dog', поскольку это часто более кратким, но не забывайте, что возврат': dog.speak' не будет работать. – tadman

ответ

2

Вы должны использовать модуль в качестве Mixin и обеспечивают возможность отслеживать, какой модуль активен, методы, похоже, не будут перезаписаны или Повторное включение resequending, поэтому я взял расширение и удалил методы, которые я нашел here.

module Adapter 
    module Dog 
    def speak 
     puts "#{name} says: woof!" 
    end 
    end 

    module Cat 
    def speak 
     puts "#{name} says: meow!" 
    end 
    end 

    def extend mod 
    @ancestors ||= {} 
    return if @ancestors[mod] 
    mod_clone = mod.clone 
    @ancestors[mod] = mod_clone 
    super mod_clone 
    end 

    def remove mod 
    mod_clone = @ancestors[mod] 
    mod_clone.instance_methods.each {|m| mod_clone.module_eval {remove_method m } } 
    @ancestors[mod] = nil 
    end 
end 

class Animal 
    include Adapter 
    attr_accessor :name, :adapter 

    def initialize(name) 
    @name = name 
    @adapter = Adapter::Dog 
    extend Adapter::Dog 
    end 

    def adapter=(adapter) 
    remove @adapter 
    extend Adapter::const_get(adapter.capitalize) 
    @adapter = Adapter.const_get(adapter.capitalize) 
    end 
end 

animal = Animal.new("catdog") 
animal.speak # catdog says: woof! 
animal.adapter = :cat 
animal.speak # catdog says: meow! 
animal.adapter = :dog 
animal.speak # catdog says: woof! 
+0

спасибо Питер! Это почти работает. Однако: в самом низу попробуйте сменить его на собаку, а затем называть «animal.speak». Он все еще реагирует на путь кошки, хотя мы хотим, чтобы он снова вернулся к собачьей дороге. – Neil

+0

дайте мне знать, если у вас есть предложения. И ваш ответ, и ответ тадмана действительно близки. Однако оба они позволяют только один раз переключать адаптер (: cat,: dog). Они оба не позволяют вам переключаться обратно на прежний (например: переключение адаптера обратно на: собака). – Neil

+1

Это было сложно, нужно было выделить расширенный и удаленный метод, получить его с https://www.ruby-forum.com/topic/164431, теперь он работает так, как ожидалось – peter

1

Это связано с тем, что name внутри контекста module ссылается на нечто совершенно иное, чем name, которого вы ожидаете. Класс Animal и модуль Cat не делят данные, они не имеют отношения. Кстати, вы звоните Module#name, который, как оказалось, возвращает Adapter::Cat, так как это имя модуля.

Чтобы обойти это, вам нужно сделать одну из двух вещей. Либо сделайте свой module смешением (удалите self, затем include при необходимости) или поделитесь необходимыми данными, передав их в качестве аргумента speak.

Первый метод выглядит следующим образом:

module Adapter 
    module Dog 
    def self.speak(name) 
     puts "#{name} says: woof!" 
    end 
    end 
end 

class Animal 
    attr_accessor :name 
    attr_reader :adapter 

    def initialize(name) 
    @name = name 

    self.adapter = :dog 
    end 

    def speak 
    self.adapter.speak(@name) 
    end 

    def adapter=(adapter) 
    @adapter = Adapter.const_get(adapter.to_s.capitalize) 
    end 
end 

Это не кажется так просто, как это может быть, как они в основном живут в двух разных мирах. Более Руби-эск способ заключается в следующем:

module Adapter 
    module Dog 
    def speak 
     puts "#{name} says: woof!" 
    end 
    end 
end 

class Animal 
    attr_accessor :name 
    attr_reader :adapter 

    def initialize(name) 
    @name = name 

    self.adapter = :dog 
    end 

    def adapter=(adapter) 
    @adapter = Adapter.const_get(adapter.to_s.capitalize) 

    extend(@adapter) 
    end 
end 
+0

Спасибо за ваш ответ! Я попробовал ваш второй предложенный ответ. К сожалению, изменение адаптера не меняет ответ. Пример: он начинается с использования версии Dog 'speak'. Затем я делаю 'animal.adapter =: cat'. Но когда я делаю «animal.speak», он все еще отвечает версией Dog. Похоже, что 'extend (@adapter)' не будет переопределять '' '' '' собаке' с '' говорить '' Кошки '. – Neil

+0

не видел ответа тадмана, иначе я бы пропустил это, но мое решение работает, я полагаю, из-за заказа в адаптере = – peter

+0

@Neil Убедитесь, что ваш модуль объявляет метод 'speak', а не' self.speak'. Если вы забудете, что метод не будет смешиваться должным образом. Я тестировал этот код, и он работал для меня, но я пропустил часть Cat для краткости. – tadman

Смежные вопросы