This repo / project contains a starting point for the OOABL PUG workshop.
The premise is a UI to capture a set of parameters for a customer report, and validate the values specified. Default values may be provided as inputs.
The parameter values are returned to a caller , together with a code indicating success or failure of the validation.
The dialog box abl/d-customerreport-parameter.w
is used to create/input and validate parameters for a customer report using ABL GUI.
To run this dialog, call run-report.p
.
The form gui/CustomerReportParam.cls
is used to create/input and validate parameters for a customer report using GUI for .NET.
To run this dialog, call run-report-net.p
.
The dialogs asks for a date range (from & to), a customer number range (from & to) and a output file name.
If the initial input to-date or the to-custnum are null/unknown, then only ask for a from- value (ie range with a floor).
If the initial input from-date or the from-custnum are null/unknown, then only ask for a to- value (ie range with a ceiling).
Basic validation is also performed:
- from- values must be <= to- values
- a filename must be specified
In addition to the screen values, a character result is returned: one of OK or CANCEL to indicate whether the report should be printed or not.
The goal of these exercises is to extract interfaces, value objects, enums and other types that can be used as generally as possible - in order to be able to reuse functionality as much as possible, and to follow the Single Resposibility Principle.
This document assumes you will refactor the gui/CustomerReportParam.cls
dialog.
-
Create and use a value object for the report parameters Look at the
SetParameters
andGetParameters
methods, and extract the input and output parameters into a value object. Once a value object is created, decide whether to refactor the methods to take and return the new value object, or whether to use a property. You may decide on both, in order to not break any calling code. A property is a good approach, since you will need to store the values in the dialog somewhere (due to the fact that the screen values are cleared after the dialog closes). -
Create and use an enum for the result While this dialog already returns an enum, it's an instance of
System.Windows.Forms.DialogResult
. This is specific to a particular client type; we want a result enum that's useable across all client- and ui-types in the application. The enum should contain at least OK and Cancel values. Convert the returned DialogEnum to the new enum usingGetEnum()
and perform any checks. For theabl/d-customerreport-parameter.w
you'd return this enum (since the ABL UI doesn't return enums). -
Extract validator logic into its own object As a first step, change the input validation methods to consume the value object created earlier. Now you should be able to create a new class, and move the method that performs validation on that value object it it. The dialog should now call the validator with the value object (containing screen values) and behave appropriately according to the result. The validator could return a simple logical value or something more complex. It should not throw an error, since we should not be programming logic via exception. The validator should also be passed into the dialog (through the constructor or possibly a property).
-
Extract an interface from the validator & implement
-
Decide whether to extract an interface from the value object & implement It may be adequate to add a simple marker interface, or none. THe more specific an interface you are able to extract, the less type checking and casting you have to do.
-
Refactor the code that consumes the validator and value object to wokr off the interface types
-
Create a Service Manager that implements the CCS IServiceManager interface You can decide how to store the mapping from the interface to the concrete implementation. Typical choices are in config files (JSON or XML) or in code.
-
Assign it to the CCS Application static property Create a session
start.p
that gets run first in a session. In this, assign a new instance of your Service Manager to theCcs.Common.Application:ServiceManager
property. -
(OPTIONAL) Create an include to reduce boilerplate An include that calls
getService
and casts the result to the input type will make the calling code cleaner. -
Replace calls with getService() Starting with those places that have parameters, variables or properties defined as interfaces, change an
NEW
statements you have with calls to the Service Manager'sgetService
method.
A set of solutions can be found in https://github.com/4gl-fanatics/2019_Workshops_Solutions .