A simple practical example how the SysExtension
framework will rescue us to adhere to SOLID principles and make our code
testable:
We have a table that has 4
fields:

1) GrossValue (Real, extends
RealBase)
2) NetValue (Real, extends
RealBase)
3) Target (Real, extends
RealBase)
4) Type (Enum)
The enum Type has two elements

Now the requirement is to create a display method,
contractValue, that according to the type it does a different
calculation:
So you could simply create your method like
this:
[SysClientCacheDataMethodAttribute(true)]
public
display Total
contractValue()
{
Total
value;
if
(this.Type ==
EF_EnumsForSysExtFramworkDemo1::Type1)
value = this.GrossValue *
this.Target;
if
(this.Type ==
EF_EnumsForSysExtFramworkDemo1::Type2)
value = this.NetValue *
this.Target;
return
value;
}
Can you guess what is the problem with this method?
· Brilliant!! Yes you are right, too many ‘If’ clauses
are there and if we want to introduce another new type, then you need to write
another ‘if’. In other words, it violates the Open closed principle (OCP): a
class should be open for extension but closed for
modification.
· Second issue this method can’t be properly unit tested!
· It violates the single responsibility
principle
If you work on a small project you can simply ignore
these points and claim and that you need a quick simple solution to finish your
work quickly and go home early, but if you work on a large enterprise project
you as developer have the responsibility to deliver good quality code and
Dynamics is by definition an enterprise project. So, let’s see how we can
improve this code and make our code SOLID and
testable.
So how can you do it?
Use the sys Extension Framework!
there are other
ways to achieve it (for example using the strategy pattern https://refactoring.guru/design-patterns/strategy/csharp/example)
but I prefer to use ax built in tools as it also caches and is much faster (see
this article to know more https://blogs.msdn.microsoft.com/mfp/2014/05/08/x-pedal-to-the-metal/).
Create an attribute class that extends
SysAttribute
class
EF_SysExtFramworkDemo1Attribute extends
SysAttribute
{
EF_EnumsForSysExtFramworkDemo1
types; //the enum for
types
}
public
void new(EF_EnumsForSysExtFramworkDemo1
_types)
{
super();
types
= _types;
}
public
EF_EnumsForSysExtFramworkDemo1 parmTypes(EF_EnumsForSysExtFramworkDemo1 _types =
types)
{
types
= _types;
return
types;
}
Create an Abstract base
class
abstract
class
EF_SysExtFramworkDemo1BaseClass
{
EF_TableForSysExtFramworkDemo1
tableBuffer;
}
protected
void initTableBuffer(EF_TableForSysExtFramworkDemo1
_tableBuffer)
{
tableBuffer
= _tableBuffer;
}
abstract
public void
getContractValue()
{
}
Create the derived classes for the different type
options
[EF_SysExtFramworkDemo1Attribute(EF_EnumsForSysExtFramworkDemo1::Type1)]
class
EF_SysExtFramworkDemo1Type1 extends
EF_SysExtFramworkDemo1BaseClass
{
}
public
Total getContractValue()
{
return
tableBuffer.GrossValue * tableBuffer.Target;
}
[EF_SysExtFramworkDemo1Attribute(EF_EnumsForSysExtFramworkDemo1::Type2)]
class
EF_SysExtFramworkDemo1Type2 extends
EF_SysExtFramworkDemo1BaseClass
{
}
public
Total getContractValue()
{
return
tableBuffer.NetValue * tableBuffer.Target;
}
Create a factory
class
class
EF_SysExtFramworkDemo1Factory extends
EF_SysExtFramworkDemo1BaseClass
{
}
public
Total getContractValue()
{
return
0;
}
public
Total getContractValue()
{
return
0;//default
value
}
public
static EF_SysExtFramworkDemo1Factory
construct(EF_EnumsForSysExtFramworkDemo1 _type, Common
_tableBuffer)
{
EF_SysExtFramworkDemo1Attribute
attr;
EF_SysExtFramworkDemo1BaseClass
baseCl;
ClassName
baseClName =
classStr(EF_SysExtFramworkDemo1BaseClass);
attr
= new
EF_SysExtFramworkDemo1Attribute(_type);
baseCl
= SysExtensionAppClassFactory::getClassFromSysAttribute(baseClName,
attr);
if
(!baseCl)
{
baseCl = new
EF_SysExtFramworkDemo1Factory(); //so it will use the default getContractValue in this
class
//OR just throw an
error
//throw
error(Error::wrongUseOfFunction(funcName()));
}
baseCl.initTableBuffer(_tableBuffer);
return
baseCl;
}
Update the display method on the
table
[SysClientCacheDataMethodAttribute(true)]
public
display Total
contractValue2()
{
Total
value;
EF_SysExtFramworkDemo1Factory
cl;
cl
= EF_SysExtFramworkDemo1Factory::construct(this.Type,
this);
value
= cl.getContractValue();
return
value;
}
Now we have achieved two important
goals
1) If we add a new type that will require a different
calculation, we don’t have to touch anywhere our code (closed for
modification). We simply have to create a new derived class (open for
extension)
[EF_SysExtFramworkDemo1Attribute(EF_EnumsForSysExtFramworkDemo1::Type3)]
class
EF_SysExtFramworkDemo1Type3 extends
EF_SysExtFramworkDemo1BaseClass
{
}
public
Total getContractValue()
{
return
….;
}
2) We can now unit test
To complete the picture, we can create the Unit
test:
class
EF_SysExtFramworkDemo1Type1Test extends
SysTestCase
{
}
[SysTestCheckInTestAttribute]
public
void testType1()
{
//
Arrange
EF_SysExtFramworkDemo1BaseClass
cl;
EF_TableForSysExtFramworkDemo1
table;
Total
actualTotal, expectedTotal;
//
Act
table.GrossValue
= 100;
table.Target
= 2;
expectedTotal
= 200;
cl
=
EF_SysExtFramworkDemo1Factory::construct(EF_EnumsForSysExtFramworkDemo1::Type1,
table);
actualTotal
= cl.getContractValue();
//
Assert
this.assertEquals(expectedTotal,
actualTotal);
}
[SysTestCheckInTestAttribute]
public
void testType2()
{
//
Arrange
EF_SysExtFramworkDemo1BaseClass
cl;
EF_TableForSysExtFramworkDemo1
table;
Total
actualTotal, expectedTotal;
//
Act
table.NetValue
= 100;
table.Target
= 2;
expectedTotal
= 200;
cl
=
EF_SysExtFramworkDemo1Factory::construct(EF_EnumsForSysExtFramworkDemo1::Type2,
table);
actualTotal
= cl.getContractValue();
//
Assert
this.assertEquals(expectedTotal,
actualTotal);
}
To run the test you can just now just right click on
the class, AddIns -> Run Tests.
But I usually create also a main method to the test
class so that I can just run the class and even add a menu item to run my
test
public
static void main(Args
_args)
{
ListEnumerator
msgs;
SysTestSuite
suite = new
SysTestSuite(classstr(EF_SysExtFramworkDemo1Type1Test));
//use
this to run in transaction
isolation
//SysTestSuite
suite = new
SysTestSuiteTTS(classstr(EF_SysExtFramworkDemo1Type1Test));
SysTestResult
result = new
SysTestResult();
result.addListener(new
SysTestListenerPrint());
result.addListener(new
SysTestListenerDB());
result.addListener(new
SysTestListenerInfolog());
result.parmCodeCoverageEnabled(true);
suite.run(result);
info(result.getSummary());
msgs
= result.getMessages().getEnumerator();
while
(msgs.moveNext())
{
error(strFmt('%1',
msgs.current()));
}
}

One side note, The extension framework cache so every
time you do some changes during development cleae the cache
:
SysExtensionCache::clearAllScopes();
Project
Here is how your project might look
like:

I know what you think, 6 classes, lot of complexity to
replace 4 lines of code!!
Yes, at first glance seems a non-sense, but believe me
(actually not me but all the community), writing solid extensible, testable code
is the only way to make withstand the changes and the new requirements which
always come sooner or later. And the ability to just extend and simply add new
classes without touching the existing code is so much powerful that worth all
the effort.
Once you get used is not so much a time to write your
code properly and you will start to realize that a code
that is not backed by unit tests is simply Bad
Code.