Proof of concept: Model, View, Controller and Interfaces

Discussion forum for all Windows batch related topics.

Moderator: DosItHelp

Post Reply
Message
Author
Maylow
Posts: 21
Joined: 15 Sep 2019 05:33

Proof of concept: Model, View, Controller and Interfaces

#1 Post by Maylow » 19 Apr 2021 19:02

I've written a script containing a macro which acts like the 'Implements' statement in many OOP supported languages.
As proof of concept, I've written 6 other scripts which demonstrate the usage of a Model, View, Controller and Interface.
Apart from the concept Interface, no other OOP features are used, just MVC + Interface.

Feel free to use/change any part or all of the code snippets, I hope they can be of use to anyone.
The scripts are merely an educational example of what can be accomplished, they are far from finished.

Those who are not familiar with Object Oriented Programming, with the Model, View, Controller design pattern, or with Interfaces, information can be found online.
In short:
OOP is a programming paradigm which features Classes, Objects, Inheritance, Encapsulation, Polymorphism and many more.
MVC is a design pattern with the purpose to separate data, presentation and actions from each other.
An Interface is a 'contract' between code that implements an interface and the implementation of routines and data fields declared by that interface.


The MVC logic is handled by the MVC scripts themselves.
In short:
  • Model defines data.
  • View calls Model to get data, then displays output.
  • Controller calls Model to update data, then calls View to display output.
The user interacts with Controllers which manipulate data in Models and present output via Views.


The Interface logic is defined by interface script files and handled by macro '#implements'.
The #implements macro is called with the #call macro which is explained elsewhere on this forum.
The definition of #call, a short description and its usage are shown in the examples.

Labels in interfaces are declared the same way as in normal batch scripts.
They act as label definitions which are to be implemented in the exact same way as declared by the interface.

Code: Select all

:{label_name} [{arg}]
Variables in interfaces which are to be implemented have predefined prefixes to separate them from other variables.
I've chosen for %~n0.IDATA. as prefix for interface variables, however, any variable prefix can be used of course as long as it makes interface variables distinguishable from other variables and used references to them are valid.

Code: Select all

set "%~n0.IDATA.{variable}={data_type}"
NOTE: scripts which use variables declared by interfaces must have those variables defined BEFORE calling #implements because #implements currently validates only defined variables.
Macro #implements could be altered to find variables in scripts using findstr, much in the same way labels are currently validated.
Keep in mind though that findstr is pretty resource consuming.


I've kept the coding as simple as possible with minimal comments to provide a clear example.
Allthough I've only used one interface per model/view/controller, scripts can implement multiple interfaces.
Multiple interfaces can be implemented with multiple #call and #implements statements.


Code.

File 'ImplementsMacro.cmd' contains macros #call and #implements:

Code: Select all

@echo off
::: Define line feed and new line characters:
set ^"\lf=^
%=empty=%
^"
set ^"\n=^^^%\lf%%\lf%^%\lf%%\lf%^^"

::: Define macro to call other macros:
::: ---------------------------------------------------------------------------
:#call ('"Arg"["Arg"][..]') Macro
:::
::  @param    (str) Arg                     argument passed to macro
::  @param    (var) Macro                   variable containing macro
::  @return   (unk)
:::
::: Call macro with double quoted arguments.
:::
::: Example:
:::   set #macro= do echo(%%A %%B
:::   %#call%('"Hello""'world'!"') %#macro%
:::
set #call=for /f usebackq^^^ delims^^^=^^^"^^^ tokens^^^=1-26 %%A in 

::: Define macro to implement Interfaces:
::: ---------------------------------------------------------------------------
:#implements Caller Interface
:::
::  @param    (str) Caller                  path to caller script
::  @param    (str) Interface               path to interface script
::  @exit     (int) 404                     caller or interface not found
::  @exit     (int) 501                     implementation failed
::  @return   (void)
::  @uses     (chr) 10                      \n 1250
:::
::: Implement interface and validate implemented routines and variables.
:::
set #implements= do (%\n%
    if exist "%%~fA" (%\n%
        if exist "%%~fB" (%\n%
            setlocal enableDelayedExpansion%\n%
            set "#implements.Errorlevel=0"%\n%
            call "%%~fB"%\n%
            REM Variables in Interface must be implemented in Caller exactly as defined by the Interface:%\n%
            for /f "usebackq tokens=1,* delims==" %%b in (%\n%
                `set %%~nB.IDATA. 2^^^>nul`) do (%\n%
                for /f "tokens=3 delims=." %%d in ("%%~b") do (%\n%
                    if not defined %%~d (%\n%
                        set "#implements.Errorlevel=501"%\n%
                        1>&2 echo(Variable not implemented in %%~nA: ^^^(%%~c^^^) %%~d%\n%
                    ) else (%\n%
                        REM Type Validation:%\n%
                        if "%%~c" equ "int" (%\n%
                            echo("!%%~d!"^|findstr /ric:"[0-9][0-9]*" ^>nul 2^>nul ^|^| (%\n%
                                set "#implements.Errorlevel=501"%\n%
                                1>&2 echo(Variable '%%~d' is not of type ^^^(%%~c^^^) in %%~nA: !%%~d!%\n%
                            )%\n%
                        ) else if "%%~c" equ "str" (%\n%
                            echo("!%%~d!"^|findstr /ric:"[a-zA-Z0-9][a-zA-Z0-9]*" ^>nul 2^>nul ^|^| (%\n%
                                set "#implements.Errorlevel=501"%\n%
                                1>&2 echo(Variable '%%~d' is not of type ^^^(%%~c^^^) in %%~nA: !%%~d!%\n%
                            )%\n%
                        )%\n%
                    )%\n%
                )%\n%
            )%\n%
            REM Labels in Interface must be implemented in Caller exactly as defined by the Interface:%\n%
            set "#implements.ILabels=0"%\n%
            set "#implements.Labels=0"%\n%
            for /f "usebackq tokens=*" %%b in (%\n%
                `type "%%~fB"^^^|findstr /ric:"^[ ]*:[a-zA-Z0-9#][a-zA-Z0-9#]*"`) do (%\n%
                set /a "#implements.ILabels+=1"%\n%
                set #implements.ILabel.!#implements.ILabels!=%%b%\n%
            )%\n%
            for /f "usebackq tokens=*" %%b in (%\n%
                `type "%%~fA"^^^|findstr /ric:"^[ ]*:[a-zA-Z0-9#][a-zA-Z0-9#]*"`) do (%\n%
                set /a "#implements.Labels+=1"%\n%
                set #implements.Label.!#implements.Labels!=%%b%\n%
            )%\n%
            for /f "usebackq tokens=1,* delims==" %%b in (%\n%
                `set #implements.ILabel. 2^^^>nul`) do (%\n%
                set "%%~b.Implemented=0"%\n%
                for /f "usebackq tokens=1,* delims==" %%d in (%\n%
                    `set #implements.Label.`) do (%\n%
                    if "%%~c" equ "%%~e" (%\n%
                        set %%~b.Implemented=1%\n%
                    )%\n%
                )%\n%
                if "!%%~b.Implemented!" neq "1" (%\n%
                    set "#implements.Errorlevel=501"%\n%
                    1>&2 echo(Routine not implemented in %%~nA: %%c%\n%
                )%\n%
            )%\n%
            for /f "usebackq tokens=2 delims==" %%e in (%\n%
                `set #implements.Errorlevel`) do (%\n%
                endlocal%\n%
                cmd /d /c exit /b %%~e%\n%
            )%\n%
        ) else (%\n%
            cmd /d /c exit /b 404%\n%
        )%\n%
    ) else (%\n%
        cmd /d /c exit /b 404%\n%
    )%\n%
)
exit /b
File 'IMyScript_Model.cmd' is an interface which defines a set of required data fields (variables):

Code: Select all

@echo off
::: Define model data:
set %~n0.IDATA.param1=str
set %~n0.IDATA.param2=str
set %~n0.IDATA.foo=int
set %~n0.IDATA.bar=str
exit /b
File 'MyScript_Model.cmd' is an implementation of 'IMyScript_Model.cmd' and uses macros #calll and #implements to implement the interface:

Code: Select all

@echo off
::: Load macro to implement interfaces:
if not defined #implements call ImplementsMacro.cmd

::: Implementation of model data:
if "%~1" neq "" set param1=%1
if "%~2" neq "" set param2=%2
if not defined param1 set param1=Hello
if not defined param2 set param2=world!
::: Remove or change the following model data with other data type 
::: to show effect of missing data or incorrect data type declared by Interface:
set foo=123
set bar=test

::: Implement interface:
%#call%('"%~f0""%~dp0I%~nx0"') %#implements%
exit /b
File 'IMyScript_View.cmd' is an interface which defines a set of required routines (labels):

Code: Select all

@echo off
::: Define view routines:
:Display 
:Foo 
exit /b
File 'MyScript_View.cmd' is an implementation of 'IMyScript_View.cmd' and uses macros #calll and #implements to implement the interface:

Code: Select all

@echo off
::: Load macro to implement interfaces:
if not defined #implements call ImplementsMacro.cmd

::: Implement interface:
%#call%('"%~f0""%~dp0I%~nx0"') %#implements% || exit /b

::: Load model data:
call MyScript_Model.cmd || exit /b

::: Implementation of routine:
:Display 
echo(%param1% %param2%
exit /b

::: Implementation of routine, remove to show effect of missing routine declared by Interface:
:Foo 
exit /b
File 'IMyScript_Controller.cmd' is an interface which defines a set of required routines (labels):

Code: Select all

@echo off
::: Define controller routines:
:Act Param1 Param2
:Foo Param1 Param2
exit /b
File 'MyScript_Controller.cmd' is an implementation of 'IMyscript_Controller.cmd' and uses macros #calll and #implements to implement the interface:

Code: Select all

@echo off
::: Load macro to implement interfaces:
if not defined #implements call ImplementsMacro.cmd

::: Implement interface:
%#call%('"%~f0""%~dp0I%~nx0"') %#implements% || exit /b

::: Implementation of routine:
:Act Param1 Param2
call MyScript_Model.cmd %* || exit /b
call MyScript_View.cmd Display
exit /b

::: Implementation of routine, remove to show effect of missing routine declared by Interface:
:Foo Param1 Param2
exit /b
Executing MVC scripts with correctly implemented interfaces without arguments:
>MyScript_Model.cmd
>MyScript_Controller.cmd
Hello world!
>MyScript_View.cmd
Hello world!

Executing MVC scripts with correctly implemented interfaces with arguments:
>MyScript_Model.cmd Bye
>MyScript_View Bye
Hello world!
>MyScript_Controller.cmd Bye
Bye world!

Executing MVC scripts with incorrectly implemented Model Interface:
>MyScript_Model.cmd
Variable not implemented in MyScript_Model: (str) bar
Variable 'foo' is not of type (int) in MyScript_Model: test
>MyScript_View.cmd
Variable not implemented in MyScript_Model: (str) bar
Variable 'foo' is not of type (int) in MyScript_Model: test
>MyScript_Controller.cmd
Variable not implemented in MyScript_Model: (str) bar
Variable 'foo' is not of type (int) in MyScript_Model: test

Executing MVC scripts with incorrectly implemented View Interface:
>MyScript_Model.cmd
>MyScript_View.cmd
Routine not implemented in MyScript_View: :Foo
>MyScript_Controller.cmd
Routine not implemented in MyScript_View: :Foo

Executing MVC scripts with incorrectly implemented Controller Interface:
>MyScript_Model.cmd
>MyScript_View.cmd
Hello world!
>MyScript_Controller.cmd
Routine not implemented in MyScript_Controller: :Foo Param1 Param2

Executing all MVC scripts with incorrectly implemented Interfaces:
>MyScript_Model
Variable not implemented in MyScript_Model: (str) bar
Variable 'foo' is not of type (int) in MyScript_Model: test
>MyScript_View.cmd
Routine not implemented in MyScript_View: :Foo
>MyScript_Controller.cmd
Routine not implemented in MyScript_Controller: :Foo Param1 Param2


As I've said earlier, this code is far from finished. For example, more Data Types could be added, Data Type validation of arguments and return values could be added,
the findstr regular expressions could be improved, to name a few.
Enjoy!

Post Reply