Вложение модулей в классы и прямой вызов функций модуля - PullRequest
1 голос
/ 23 марта 2020

В целях тестирования и администрирования я ищу создание класса для взаимодействия с API. У меня отключены соединение и аутентификация, но я борюсь с базовой структурой и размером класса.

Моя главная цель - разделить каждый домен приложения, но при этом все еще легко получить доступ к одному классу / соединению. Я сделал более простой пример того, что я ищу. На самом деле каждый домен имеет свой собственный набор бизнес-правил, поэтому я хочу, чтобы они были отдельными, а соединение API остается неизменным.

Например, на уровне CLI я хочу вызвать:

$ client_one = Api.new("one")
$ client_two = Api.new("two")

$ client_one.Bikes.delete(1)
> deleted bike 1 from one

$ client_two.Phones.new(phone)
> posted phone iPhone to two

Мой мыслительный процесс заключался в том, чтобы вложить модули в класс Api, но я не могу заставить его работать или найти правильный синтаксис.

class Api
  def initialize(client)
    @client = client
    @connection = Authentication.get_connection(@client)
  end

  #preferable put each submodule in a separate file
  module Authentication
    def get_connection(client)
      #code to get Faraday connection
    end
  end

  module Bikes
    def new(object)
      #code to post new bike
      @connection.post(object)
      puts "posted bike #{object.name} to #{@client}"
    end
    def delete(id)
      #code to delete old bike
      @connection.delete(id)
      puts "deleted bike #{id} from #{@client}"
    end
  end

  module  Phones
    def new(object)
      #code  to post new phone
      @connection.post(object)
      puts "posted phone #{object.name} to #{@client}"
    end
  end
end

Это приводит к таким ошибкам, как:

NoMethodError: undefined method `Bikes' for #<Api:0x0000000003a543a0>

Можно ли достичь моей цели или есть лучшие 'Ruby' способы выполнить sh это?

Кроме того, можно ли разбить подмодули на разные файлы ? например:

api.rb
modules
  + -- authentication.rb
  + -- bikes.rb
  + -- phones.rb

1 Ответ

1 голос
/ 24 марта 2020

Существуют некоторые фундаментальные заблуждения о том, как Ruby OOP работает в вашем примере, и без полного примера кода и возможности опрашивать вас о том, что вы пытаетесь выполнить, sh трудно навести вас к что может быть наиболее подходящим ответом. Любой ответ, который я даю, будет основан частично на опыте, а частично на мнении, поэтому вы можете увидеть и другие ответы.

На высоком уровне у вас должны быть классы в модулях, а не модули в классах. Хотя вы можете помещать модули в классы, вы лучше понимаете , почему вы делаете это , прежде чем делать это.

Далее, модули и методы, которые вы в них определили, не автоматически становятся доступными для экземпляров родительского класса, поэтому client.Bikes никогда не будет работать, потому что Ruby ожидает найти метод экземпляра с именем Bikes внутри класса Api; он не будет искать модуль с таким именем.

Единственный способ получить доступ к определенным вами модулям и методам модулей - это использовать их на уровне класса / модуля. Поэтому, если у вас есть это:

class Foo
  module Bar
    def baz
      puts 'foobarbaz'
    end
  end
end

Вы можете сделать это на уровне класса / модуля:

Foo::Bar.baz
foobarbaz
=> nil

Но вы не можете ничего сделать на уровне экземпляра:

Foo.new::Bar.baz
TypeError: #<Foo:0x00007fa037d39260> is not a class/module

Foo.new.Bar.baz
NoMethodError: undefined method `Bar' for #<Foo:0x00007fa037162e28>

Итак, если вы до сих пор понимаете, почему структура вашего примера не работает, тогда вы можете работать над созданием чего-то более разумного. Давайте начнем с именования и структуры класса / модуля.

Во-первых, Api здесь является плохим именем, потому что вы обычно будете использовать Api для чего-то, что предоставляет API, а не подключается к одному, поэтому я бы рекомендовал сделать имя более описательным и использовать модуль, чтобы указать, что вы инкапсулируете один или несколько связанных классов:

module MonthyApiClient
end

Далее, я бы порекомендовал добавить Client класс для инкапсуляции всего, что связано с созданием экземпляра клиента, используемого для подключения к API:

module MonthyApiClient
  class Client
    def initialize
      @client = nil # insert your logic here
      @connection = nil # insert your logic here
    end
  end
end

Отношения между client и connection в вашем примере кода не ясны, поэтому для простоты я представим, что их можно объединить в один класс (Client) и что мы отбрасываем модуль Authentication полностью.

Далее нам нужен разумный способ объединения module Bikes и * 1041. * в этот код. Не имеет смысла преобразовывать их в классы, потому что нет необходимости создавать их экземпляры. Это чисто вспомогательные функции, которые делают что-то для экземпляра Client, поэтому они должны быть методами экземпляра в этом классе:

module MonthyApiClient
  class Client
    def initialize
      # insert your logic here
      @client = nil
      @connection = nil
    end

    def create_bike
      # insert your logic here
      # e.g., @connection.post(something)
    end

    def delete_bike
      # insert your logic here
      # e.g., @connection.delete(something)
    end

    def create_phone
      # insert your logic here
      # e.g., @connection.post(something)
    end
  end
end

Обратите внимание, что мы поменяли new на create; Вы не хотите называть метод new в Ruby, и в контексте, который мы используем, new будет означать создание экземпляра, но не сохранять новый объект , тогда как create будет означает создание и сохранение нового объекта .

И теперь, когда мы здесь, и теперь, когда мы удалили все вложенные модули, переместив их логику c в другое место, мы можем обратите внимание, что родительский модуль, который мы установили изначально, является излишне избыточным и может устранить его:

class MonthyApiClient
  def initialize
    # insert your logic here
    @client = nil
    @connection = nil
  end

  def create_bike
    # insert your logic here
    # e.g., @connection.post(something)
  end

  def delete_bike
    # insert your logic here
    # e.g., @connection.delete(something)
  end

  def create_phone
    # insert your logic here
    # e.g., @connection.post(something)
  end
end

Тогда вы сможете достичь sh вашей первоначальной цели:

client_one = MonthyApiClient.new
client_one.create_bike
client_two = MonthyApiClient.new
client_two.create_phone

Проработав это объяснение, я думаю, ваш оригинальный код - это пример того, как вы тратите много времени на преждевременную чрезмерную оптимизацию. Лучше спланировать бизнес-логику c и сделать ее максимально простой. На https://softwareengineering.stackexchange.com/a/80094 есть некоторая полезная информация, которая может помочь объяснить эту концепцию.

Я даже пропустил попытки оптимизировать код, который я здесь показал, потому что я точно не знаю, как Существует много общего между созданием и удалением велосипедов и телефонов. С этим функциональным классом и с лучшим пониманием другого кода в этом приложении я мог бы попытаться DRY его запустить (и это может означать возвращение к модулю с классом Client и или методы модуля или другие классы для инкапсуляции DRY logi c), но было бы преждевременным пытаться.

Ваш последний вопрос был о том, как структурировать файлы и каталоги для модулей и классов, и я бы направил вас к Идеальная ruby структура проекта (среди многих других вопросов на этом сайте) для получения дополнительной информации.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...