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 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}]
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}"
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
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
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
Code: Select all
@echo off
::: Define view routines:
:Display
:Foo
exit /b
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
Code: Select all
@echo off
::: Define controller routines:
:Act Param1 Param2
:Foo Param1 Param2
exit /b
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
>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!