Модуль управления является frontend — узлом проекта. Скрипт cvs.lua взаимодействует с исполнительными модулями, которые являются бизнес — логикой проекта.

В недавно я переделал строение модуля базы и исполнительных модулей, но на модуль управления это не повлияло. Он представляет собой тексто-графический «ламповый» интерфейс, в котором пользователь вводом цифры выбирает действие и параметры:

Модуль cvs.lua:
local cvs = {}

function cvs.run() --[[ Локальное определение центральной функции. Наименование её может быть любое, главное, чтобы она вызывала сама себя :-D  --]]

       customer = require "customer"
       document = require "document"
       position = require "position"
       feedback = require "feedback"
       saldo = require "saldo"
       init = nil

--[[ Определение исполнительных модулей: по этим адресам вызываются функции, определённые сценариями взаимодействия с пользователем. --]]

       repeat
              print("\n Выберите действие: \n")
              print(":  0 : Выход из программы")
              print(":  1 : Добавить клиента")
              print(":  2 : Добавить позицию")
              print(":  3 : Добавить документ")
              print(":  4 : Добавить телефон клиента \n")
              print(":  5 : Изменить наименование клиента")
              print(":  6 : Изменить наименование позиции")
              print(":  7 : Изменить стоимость позиции")
              print(":  8 : Изменить выплату в документе")
              print(":  9 : Изменить телефон клиента \n")
              print(": 10 : Удалить клиента")
              print(": 11 : Удалить позицию")
              print(": 12 : Удалить документ")
              print(": 13 : Удалить телефон клиента \n")
              print(": 14 : Показать список клиентов")
              print(": 15 : Показать список позиций \n")
              print(": 16 : Показать баланс по клиенту")
              print(": 17 : Показать общий баланс \n")  --[[ Все процедуры строго определены списком use case. Остальные процедуры определяют background.  --]]

              init = tonumber( io.stdin:read() )		

              if init == 1 then   --[[ Цикл определения операции. --]]
                     customer.add()

              elseif init == 2 then
                     position.add()

              elseif init == 3 then
                     document.add()

              elseif init == 4 then
                     feedback.add()

              elseif init == 5 then
                     customer.change()

              elseif init == 6 then
                     position.change_nominal()

              elseif init == 7 then
                     position.change_price()

              elseif init == 8 then
                     document.change()

              elseif init == 9 then
                     feedback.change()

              elseif init == 10 then
                     customer.drop()
		
              elseif init == 11 then
                     position.drop()

              elseif init == 12 then
                     document.drop()

              elseif init == 13 then
                     feedback.drop()

              elseif init == 14 then
                     customer.show()

              elseif init == 15 then
                     position.show()

              elseif init == 16 then
                     saldo.customer()

              elseif init == 17 then
                     saldo.common()

              elseif init == 0 then
                     print("\n Завершение программы. \n")
              else
                     print("\n Команда не корректна. \n")
              end
       until init == 0

       customer = nil
       document = nil
       position = nil
       feedback = nil
       saldo = nil
       init = nil  --[[ По окончании просто сливаем экземпляры модулей. Таким образом очищаем программный мусор. --]]

end

cvs.run()  --[[ Вот здесь мы инициализируем центральную функцию. Один раз и до конца исполнения. --]]

return cvs


На самом деле становится обёрткой для остальной системы и работает, как циклический переключатель. Исполнительные процедуры отрабатывают свой функционал и возвращают управление центральному модулю. Точнее, его циклу управления.

Строка:
cvs.run()

Используется для инициализации общего цикла при вызове самого модуля. Поскольку cvs.lua определён, как обычный модуль, то вызываться он автоматически не будет. Для этого в теле модуля, который находится между определением:
local cvs = {}

И командой возврата экземпляра модуля:
return cvs

Пишется порядок команд и функций для инициализации. Фактически, здесь можно расписать порядок запуска различных функций и циклов исполнения, которые создадут любую произвольную логику. Функции здесь являются логическими элементами, повторяющимися в любом произвольном порядке и с любыми произвольными входными / исходящими данными.

Фактическки, я мог сделать локальный модуль без определения метатаблицы и структуры возврата (ибо структура позволяет). Я не сделал это по следующим причинам:
  • Неструктурированый код — большая проблема вне зависимости от того, какой модуль по размеру пишется;
  • Модульная структура главного модуля позволяет делать меньше ошибок при планировании взаимодействия исполнительных модулей;
  • Также она позволяет уменьшить количество «осечек» при конечной компилляции командой luac, ибо компиллятор будет точно знать — что с чем связано и из чего собирать;
  • В конце — концов, структурированный код сам по себе выглядит «причёсаным» и удобочитаем.

Итоговый вид управляющего модуля (извините, в xterm):
image

В следующей статье покажу работу нового модуля database.lua

P.S. Маленькая просьба — если хотите передать замечание по этому циклу статей, прошу предварительно прочитать статью «Разработка микро-учётной системы на lua, часть вторая. Постановка задачи», в которой я указываю условия разработки программы и функции, которые на неё возлагаются. Учитывайте это.
Поделиться с друзьями
-->

Комментарии (3)


  1. nick0x01
    01.06.2016 22:24
    +1

    Используйте local для объявления переменных, ибо customer, document, position, feedback, saldo, init — глобальные.

    я мог сделать локальный модуль без определения метатаблицы

    Тут и так нет метатаблицы.

    «причёсаным» и удобочитаем

    Lua позволяет делать очень клевый и действительно удобочитаемый код. Зачем столько print'ов и if-else?

    Например вот так
    local function map_(arr, fn)
      for i,v in pairs(arr) do
        fn(v, i)
      end
    end
    
    local function main()
      local actions = {
        [0] = { title = 'Exit', action = function() print 'Bay!' end },
        [1] = { title = 'Say Hi', action = function() print 'Hi!' end },
        [2] = { title = 'Say Opps', action = function() print 'Opps!' end }
      };
      local cm = nil;
    
      repeat
        print '\nMenu:'
        map_(actions, function(act, i)
          print(string.format('%4d - %s', i, act.title))
        end)
        io.write '\nYour choise: '
        cm = tonumber( io.stdin:read() );
    
        if cm <= #actions then
          actions[cm].action();
        else
          print 'Unknown command. =('
        end
      until cm == 0;
    
    end
    
    main();
    


    1. hantenellotf
      03.06.2016 12:47

      Можно разъяснить подробнее функционал этого кода?


      1. nick0x01
        03.06.2016 21:39

        Для определения доступных пользователю действий использовал таблицу actions — это массив с начальным индексом 0, элементы оного содержат всю необходимую информацию для действия — названия и само действие — собственно функция.
        В вашем случае будет что-то вроде:

        {
          { title = 'Добавить пользователя' , action = customer.add },
          { title = 'Добавить позицию'      , action = position.add },
          -- ...
        }
        


        Обратите внимание, что action будет хранить адрес ф-ии (поля add таблицы customer, например), которую необходимо вызвать для данного действия, то есть там не вызов ф-ии. В title я бы хранил некий id (а не само названием), который можно использовать для локализации, но это не существенно. Еще бы добавил теги — буквенная строка, и использовал ее наравне с цифрами для выбора действия пользователя.

        Далее в map_ просто выводится список доступных действий (это устраняет необходимость писать print для каждого элемента меню). После получения пользовательского ввода, в if проверяется есть ли такое действие (по индексу), и если все ок — выполняется: строка
        actions[cm].action();
        

        Этим убирается монструозная конструкция ifelse'ов.

        Посредством такого подхода автоматически решается вопрос добавления/изменения функционала, доступного пользователю — необходимо отредактировать только действия в actions, а остальной код останется неизменным. Разделение данных, бизнес-логики и UI.
        Как-то так.

        Еще такой момент, я бы вынес requires модулей за рамки ф-ии cvs.run — сделал бы их локальными для модуля (судя повашей архитектуре, это не повлечет нежелательных последствий), и таблицу actions так же бы сделал локальной для модуля cvs — это бы позволило бы лишь раз инициализировать данные переменные (небольшая оптимизация).