Ruby 객체에 대한 호출 방법의 이점

10529 단어 rubyprogramming

공동 작업을 용이하게 하기 위해 Ruby의 개체 모델 활용



일상적인 Ruby 코딩에서 사용하는 일반적인 패턴 중 하나는 모듈 또는 레벨call 메서드를 정의하는 것입니다. 저는 이러한call 방법을 서로 다른 관심사 사이의 선명한 인터페이스로 설정하고 싶습니다.

호출에 응답하는 개체를 사용한 종속성 주입



다음 예제에서는 deliver! 메서드가 있는 "패키지"클래스를 구성했습니다. 한 번 보자:

module Handler
  def self.call(package:)
    # Complicated logic
  end
end

module Notifier
  def self.call(package:)
    # Complicated logic
  end
end

class Package
  def initialize(origin:, destination:, notifier: Notifier, handler: Handler)
    @origin = origin
    @destination = destination
    @handler = handler
    @notifier = notifier
  end
  attr_reader :origin, :destination, :handler, :notifier

  def deliver!(with_notification: true)
    notifier.call(package: self) if with_notification
    handler.call(package: self)
  end
end

deliver! 메서드를 테스트하려면 몇 가지 시나리오를 고려해야 합니다.
  • 알림을 보내지 않습니다
  • .
  • 알림을 보내고 있는데 예외가 발생합니다
  • 알림을 보냈는데 성공함

  • 그리고 Notifier.callHandler.call 모두 테스트에서 실행하는 데 비용이 많이 든다고 가정해 보겠습니다. 스터빙이나 조롱 없이 다음 테스트를 작성할 수 있습니다.

    def test_assert_not_sending_notification
      # By having the notifier `raise`, we'll know if it was called.
      notifier = ->(package:) { raise }
      handler = ->(package:) { :handled }
      package = Package.new(
        origin: :here,
        destination: :there,
        notifier: notifier,
        handler: handle
      )
    
      assert(package.deliver!(with_notification: false) == :handled)
    end
    


    협업 개체의 메서드를 Lambda로 사용하는 종속성 주입



    이제 내려갈 수 있는 몇 가지 흥미로운 경로가 있습니다. 첫째, .call 메서드 명명 규칙이 정말 마음에 들지 않으면 어떻게 할까요?

    module PlanetExpress
      def self.deliver(package:)
        # Navigates multiple hurdles to renew delivery licenses
      end
    end
    

    PlanetExpress.deliver라는 별칭을 만들 수 있지만 약간의 Ruby 마법을 사용할 수도 있습니다.

    Package.new(
      origin: :here,
      destination: :there,
      handler: PlanetExpress.method(:deliver)
    )
    


    Object.method 메소드는 call 에 응답하는 메소드 객체를 반환합니다. 이를 통해 call 기반 인터페이스의 유연성을 계속 즐기면서 PlanetExpress 모듈을 수정하지 않아도 됩니다.

    ActiveRecord와의 인터페이스에 대해 생각할 때 이것은 아마도 훨씬 더 관련이 있습니다. 기록을 찾아서 처리하고 싶은 경우가 있나요? 어쩌면 그 기록을 만드는 데 비용이 많이 들 수도 있습니다. 그것을 단락시키자.

    class User < ActiveRecord::Base
    end
    
    # An async worker that must receive an ID, not the full object.
    class CongratulatorWorker
      def initialize(user_id:, user_finder: User.method(:find))
        @user = user_finder.call(user_id)
      end
    
      def send_congratulations!
        # All kinds of weird things with lots of conditionals
      end
    end
    


    위와 같이 이제 테스트에서 다음을 설정할 수 있습니다.

    def test_send_congratulations!
      user = User.new(email: "[email protected]")
      finder = ->(user_id) { user }
      worker = CongratulatorWorker.new(user_id: "1", user_finder: finder)
    
      worker.send_congratulations!
    end
    


    위의 시나리오에서 User 클래스에 선언된 User.call 메서드를 보면 머리가 긁힐 것입니다. 그러나 CongratulatorWorker에서 나는 무슨 일이 일어나고 있는지 추론할 기회를 좀 더 가질 것입니다.

    메서드 개체를 블록으로 사용



    이 예제는 다른 방향으로 진행되지만 call 메서드를 갖는 규칙의 유용성을 강조합니다.

    module ShoutOut
      def self.say_it(name)
        puts "#{name} is here"
      end
    end
    
    ["huey", "duey", "louie"].each(&ShoutOut.method(:say_it))
    


    나는 callShoutOut를 정의하고 그것을 블록으로 사용할 수 있기를 바랐습니다(예: .each(&ShoutOut) ). 그러나 그것은 효과가 없었습니다.

    결론



    이러한 정도의 역동성은 제가 Ruby에 대해 좋아하는 한 가지 측면입니다. 그리고 그것은 Ruby에만 국한된 것이 아닙니다. 나는 Emacs-Lisp에서 이것을 한다.

    Ruby를 배우는 초기에 나는 Ruby에 대해 알려주는 몇 가지 문장을 우연히 발견했습니다.
  • 모든 것이 객체입니다
  • 클래스는 개체이고 개체는 클래스임

  • 그리고 객체에 대한 메서드도 그 자체로 객체입니다. 좋은 점은 이러한 물체에 모양과 형태가 있다는 것입니다. 메서드를 "분리"하고 전달하는 것을 볼 수 있습니다.

    좋은 웹페이지 즐겨찾기