gangelo / realpage_calculator

Reverse Polish Notation (RPN) Calculator

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

RealPage Calculator Project (RPC)

A Ruby Command-Line Reverse Polish Notation (RPN) Calculator

Original Specifications

In This Document

Solution Overview

The RPC project consists of:

  • Two primary class categories.
  • Several secondary class categories whose sole purpose is to facilitate the functionality of the primary classes.
  • A series of support classes/modules that assist with the overall funcionality of this project.
  • Configuration files used to dynamically configure this project.
  • An executable command-line script that allows Users to run and interact with the RPN Calculator from the console.

Primary Class Categories

Primary Class Category Example Class High-Level Function
IO Interfaces RealPage::Calculator::IOInterface RealPage::Calculator::ConsoleInterface IO Interface class objects (IO Interface) are responsible for the communication between the stream (input, output, error) and the Calculator Service class object (Calculator Service) and vise versa.
Calculator Services RealPage::Calculator::CalculatorService RealPage::Calculator::RPNCalculatorService Calculator Services are responsible for computing the input received from the IO Interface and returning the result and/or any errors encountered back to the IO Interface.

Secondary Class Categories

Secondary Class Category Example Class High-Level Function
Calculator Results RealPage::Calculator::CalculatorResults CalculatorResult class objects (CalculatorResult) are returned to the IO Interace as a result of a Calculator Service request initiated by the IO Interface. The CalculatorResult contains the computed result and/or any error that may have been encountered during the request.
Input Parsers RealPage::Calculator::InputParser RealPage::Calculator::RPNInputParser InputParser class objects (InputParser) are used by Calculator Services and responsible for parsing input received from an IO Interface into a format suitable for processing by the Calculator Service.
Input Tokens RealPage::Calculator::InputToken InputToken class objects (InputToken) represent an individual, space delimited token received from the input stream. InputToken is responsible for identifying the nature and type of the token it encapsulates. For example, InputToken identifies the following token types: operators, operands and commands; it also identifies whether or not a token is valid.

Support Classes/Modules

Support Class/Module High-Level Function
RealPage::Calculator::Configuration The Configuration Singleton object (Configuration) is responsible for loading the /calculator/config/calculator_service_config.yml file and providing a single(ton) interface to configuration settings user by Calculator Services. Configuration settings such as valid operators, commands (i.e. as quit, view stack, clear stack) and console_interface_options are made available through the Configuration Singleton.
RealPage::Calculator::I18nTranslator The i18n Tranclator Singleton object (i18n Translator) is responsible for loading the /configuration/config/i18n.yml file and providing a single(ton) interface for translation services used by IO Interfaces.
RealPage::Calculator::InterfaceNotReadyError RealPage::Calculator::MustOverrideError The Error class objects (Errors) are used throughout the RPC project wherever a custom error needs to be raised.
RealPage::Calculator::Messages The Message Support module (Message Support) defines errors and warnings in the form of Hash values. When the Calculator Service encounters an error or warning, a CalculatorResult object is created and the Message Support message is embedded in the CalculatorResult object and sent to the IO Interface. The Message Support message Hash embedded in the CalculatorResult object is then used by the IO Interface as input to the i18n Translator ({type: :error | :warning, key: :key, scope: [:scope1, :scope2, ...]}) to return a locale specific message to the output stream.
RealPage::Calculator::Helpers::Blank RealPage::Calculator::Helpers::Arrays These Helper modules (Helpers) add convenience functionality in support of this project.

Configuration Files

File High-Level Purpose
/calculator/config/calculator.yml The Calculator Configuration file is used to configure operators and command-line commands that will be acknowledged by the CalculatorServices and IO Interfaces, as well as options to configure specific IO Interface behavior.
/calculator/config/i18n.yml The i18n Configuration file provides locale-specific translation entries in the form of key/scope pairs used by the i18n Translator

Executable Command-Line Scripts

Script Type Purpose
/calculator/rpn_cal.rb Ruby The RPN Calculator file is an executable Ruby script that Users can use to run and interact with the RPN calculator either in a UNIX-like CLI (console interface), or via input file (file interface).

Technical/Architectural Reasoning

According to the specification, the RPC project was to create a command-line, Reverse Polish Notation calculator for people who are comfortable with UNIX-like CLI utilities. The specification also dictated that the initial project installment must implement the four basic calculator operators (+, -, /, *) and be designed in such a way as to allow for additional operators and interfaces (WebSocket, file or TCP Socket) to be added in the future. In addition to these, I took the liberty of presuming the possibility of additional calculator types as well. Consequently, I sought to design this RPC project in such a way as to be (among other things):

  • Configurable (additional operators)
  • Extensible (additional IO Interfaces, Calculator Service types)
  • DRY
  • Testable

From an architectural perspective, the RPC project consists of a series of what will be referred to (arbitrarily) as:

  • Primary class categories
  • Secondary class categories
  • Support classes/modules
  • Executable command-line scripts

The remainder of this section will give an overview of each, as well as the technical/architectural reasoning behind the same.

Primary Class Categories

Classes

RealPage::Calculator::CalculatorService RealPage::Calculator::RPNCalculatorService RealPage::Calculator::IOInterface RealPage::Calculator::ConsoleInterface

Reasoning

Primary class categories include IO Interfaces and Calculator Services. Classes that derive from RealPage::Calculator::IOInterface and RealPage::Calculator::CalculatorService, respectfully, fall into these categories. IO Interfaces and Calculator Services are considered Primary classes because these are the categories of classes Developers and Users will interact with most often.

IO Interface

The IO Interface acts as a liaison between the Calculator Service and the particular stream the IO Interface represents. Consequently, the IO Interface is also responsible for the format (plain text, json, xml, etc.) and translation of all output sent to the output stream using i18n.

Calculator Service

The Calculator Service is a service responsible for accepting input from, and returning a result (computation or error) to, the IO Interface. Consequently, the Calculator Service, by implementing an Input Parser, is responsible for manipulating the raw input received from the IO Interface into a format specific to its own needs, and returning the raw result (CalculatorResult object) back to the IO Interface.

Interdependence

The IO Interface and Calculator Service are interdependent; therefore, they need to communicate with eachother. However, the IO Interface and Calculator Service have distinct concerns. As a result, the decision was made to create two categories of classes - each concerned solely with its own, distinct, funcionality - maintaining a clear separation of concern. In addition to this, the decision was made to require that a RealPage::Calculator::CalculatorService object be provided when instantiating a RealPage::Calculator::IOInterface object. This losely coupled relationship (as opposed to Composition) makes for a more extensible, testable, and potentially DRY framework, and opens up the ability for future non-RPN-based Calculator Services to be implemented using existing IO Interfaces. Similar rationale and implementation can be witnessed between the Calculator Service -> Input Parser relationship (i.e. RealPage::Calculator::CalculatorService, RealPage::Calculator::InputParser)

Secondary Class Categories

Classes

RealPage::Calculator::CalculatorResult RealPage::Calculator::InputParser RealPage::Calculator::RPNInputParser RealPage::Calculator::InputToken

Reasoning

Calculator Result

A Calculator Result is returned via notification from a Calculator Service to an IO Service in response to a Calculator Service request (e.g. RealPage::Calculator::CalculatorService#compute). A Calculator Result object encapsulates a Calculator Service computation or error encountered along with the offending token. Calculator Result provides a standard container class for communicating results from the Calculator Service to the IO Interface.

Encapsulating the result returned from a Calculator Service (in a Calculator Result object) enforces a standard in the way Calculator Services and IO Interfaces exchange Calculator Service results. Calculator Result provides rudimentary methods to retrieve computed results and identify/retrieve errors when they occur (single responsibility). Calculator Result can be easily extended to provide additional information or functionality should it be required by a future Calculator Service or IO Interface. Finally, in addition to providing extensibility, Calculator Result classes are testable, and its enforced use helps to maintain the DRY principle.

Input Parsers

Input Parsers are used by Calculator Services. A RealPage::Calculator::CalculatorService, in fact, cannot be instantiated without providing an RealPage::Calculator::InputParser. Input Parsers are responsible for parsing input received from an IO Interface into a format suitable for processing by the Calculator Service.

Calculator Services have the single responsibility of computing. Likewise, Input Parsers have the single responsibility of parsing input received from an IO Interface into a format suitable for processing by the Calculator Service. Input Parsers relieve the Calculator Service of this burden by eliminating this concern. Input Parsers help keep things DRY as they may be shared between Calculator Services or extended. In addition to this, Input Parsers provide better, isolated testability.

Input Tokens

Input Tokens are used by classes derived from RealPage::Calculator::InputParser and represent an individual, space delimited token received from the input stream. Input Token is responsible for identifying the nature and type of the token it encapsulates. For example, Input Token identifies the following token types: operators, operands and commands; it also identifies whether or not a token is valid.

Input Tokens exist to identify the nature (#empty?, #invalid?, #valid?) and type (#command?, #operator?, #operand?, #quit?, etc.) of each input token encountered; this relieves the other classes of this responsibility and makes for better testability. Input Token provides the same class methods as the instance implementation so that input may be interrogated without the need to instantiate an Input Token object.

Support Classes/Modules

Classes

RealPage::Calculator::Configuration RealPage::Calculator::I18nTranslator RealPage::Calculator::InterfaceNotReadyError RealPage::Calculator::MustOverrideError

Modules

RealPage::Calculator::Messages RealPage::Calculator::Helpers::Blank RealPage::Calculator::Helpers::Arrays

Reasoning

The functionality that each of these classes/modules provides is necessary to the RPC project. However, that doesn't justify the existance of any of these classes/modules in particular - this is true of any class/module in this project. In general, however, the justification for these particular classes/modules primarily includes the need to separate the concern each class has from the rest of the application, and to limit this concern to a single responsibility. The reason they are coded the way they are, depends on their individual purpose.

Configuration

The RealPage::Calculator::Configuration class provides application configuration settings. This is necessary to make the project more dynamic. For example, you should not have to change the program code in order to add a new operator, or change the value of the quit command. It is a Singleton. It is also a Singleton because only one instance of this class ever needs to exist. This is because the data it makes available never changes after the class has been instantiated. It is not a Module, because it needs to load a yaml file as part of its instantiation processing and it makes the most sense to do this one time, during object instantiation. Modules do not get instantiated. This is not regular a Class either, for slightly different reasons; it doesn't make sense to instantiate multiple objects of this types, only to have to load the yaml file over and over.

i18n

The RealPage::Calculator::I18nTranslator classes provides i18n translation key/scope pairs used for text translation. This is necessary to provide localization. For example, if I speak Spanish and am using the RPN Calculator, I should see error messages in Spanish. It also is a Singleton for the same reasons mentioned previously.

Messages Module

RealPage::Calculator::Messages is a module. Likewise, the data it makes available never changes; however, the data it makes available is static (not dependant upon loading a yaml file). Therefore, a Module makes the most sense.

Errors

The RealPage::Calculator::InterfaceNotReadyError and RealPage::Calculator::MustOverrideError error classes provide custom errors where the standard Ruby errors fall short.

Helpers Module

RealPage::Calculator::Helpers is a convenience module. The RealPage::Calculator::Helpers::Blank module can be included to mix in the #blank? method, which can be used to determine whether or not an object is nil or empty This method is used by the Input Parser classes to determine whether or not the input it receives is nil or empty. Likewise, the RealPage::Calculator::Helpers::Arrays is also a module that may be mixed into a class to take advantage of the #upper_bound member that returns the upper bound of an Array.

Executable Command-Line Scripts

Scripts

/calculator/rpn_calc.rb

Reasoning

The justification for this script is that Users need a simple script to run the RPN calculator from the command-line via input or command-line via input file. This script enables users to do that without having to start irb and instantiate

Technical/Architectural Reflections

There are a few things I would do differently if I spent more time on the project. The first thing I would do, is make this project a Ruby gem. A gem would enable this project to be distributed properly and easily incorporated into any Ruby or Rails application. In fact, I would most likely break the project up into multiple gems, for example, an rpc_core gem and perhaps one gem for each additional IO Interface type. This would also eliminate the need to hard-code yaml file names in the RealPage::Calculator::Configuration and RealPage::Calculator::I81nTranslator classes and probably eliminate these classes altogether in favor of Ruby standard config scripts.

Some of the other things I would do or do differently would be:

  • Create additional IO Interfaces, for example, a WebSocket (I've never use WebSocket before), then create a Rails app host and see how it works.
  • Refactor RealPage::Calculator::CalculatorService to allow input stack as a param during initialization so the Calculator Service state could be restored in a stateless environment, HTTP Interface for example.
  • Provide a means of setting (in the case of stateful environments) or accepting (in the case of stateless environments) a locale to be used for localization.
  • Refactor RealPage::Calculator::InputToken into a base class by eliminating calculator-specific command methods (class and instance) and force Calculator Services to implement their own, calculator-specific InputToken class. It's not very extensible the way it is.
  • Refactor RealPage::Calculator::InputParser to allow a token delimiter param during initialization to be used to parse and tokenize raw input; currently, only spaces are recognized.
  • Add a help command to be associated with RealPage::Calculator::RPNCalculatorService that returns a lists all available commands and in the case of a Console Interface, displays help in a UNIX-like CLI fashion.

Script Execution

There are two ways to run the RPN calculator using the console as the interface, Rake task and Ruby script. The instructions for each are outlined below.

For your convenience, view stack (v) and clear stack (c) commands have been implemented. These RPN calculator commands allow you to view the input stack and clear the input stack respectfully.

Rake Task

To run a version of the RPN calculator from the console using Rake, make sure you are in the project root directory (/realpage_calculator), then type either of the following command into the command-line followed by ENTER:

$ rake console $ rake file

The $ rake console command runs the RPN Calculator using the console and allows the user to interact with the calculator via user input into the terminal. The $ rake file command also runs the RPN Calculator using the console; however, this command uses the spec/files/rpn_input_file.txt file as input to the RPN Calculator.

Ruby Script

To run a version of the RPN calculator from the console using Ruby script, from the project root folder (/realpage_calculator), change to the /calculator folder by typing the following into the command-line followed by ENTER:

$ cd calculator

Now type one of the following command into the command-line followed by ENTER to run the RPN calculator:

$ ruby rpn_calc.rb $ ruby rpn_calc.rb spec/files/rpn_input_file.txt

The $ ruby rpn_calc.rb command runs the RPN Calculator using the console and allows the user to interact with the calculator via user input into the terminal. The $ ruby rpn_calc.rb spec/files/rpn_input_file.txt command also runs the RPN Calculator using the console; however, this command uses the spec/files/rpn_input_file.txt file as input to the RPN Calculator.

Make the Script Permanently Executable

If you don't wish to have to type $ ruby followed by the the script name every time you run this script, from the /calculator folder, type the following into the command-line followed by ENTER:

$ chmod 755 rpn_calc.rb

You may now execute the rpn_calc.rb script by typing the following into the command-line followed by ENTER:

$ ./rpn_calc.rb

About

Reverse Polish Notation (RPN) Calculator


Languages

Language:Ruby 100.0%