Creating a class, the best practice way in D365F&O
Some time ago, I shared my thoughts on the 'construct' pattern, which is commonly used in AX. While I often make use of it, it’s not without its flaws—it can actually violate some best practices. (I’ll cover the issue of deep class hierarchies resulting from this pattern in a future post.)
First, it’s worth noting that you can customize the compiler level and the best practice checks it performs.
To do this, compile something to bring up the compiler output window. Then, click the Setup button to adjust the settings.
For this example, I used compiler level 4 and enabled all best practice checks.
Now, let’s create a simple class. This class will simply display information from an InventTable record.
1. ClassDeclaration
As we start designing this class, it quickly becomes clear that we need to declare an InventTable record as a member variable.
/// <summary>
/// Class written to test the construct and new... patterns
/// </summary>
/// <remarks>
/// Delete this class when proven a point :)
/// </remarks>
class MHKFORTestBP
{
InventTable inventTable;
}
Also note that I’ve added documentation to this method. I’ll be doing the same for other methods as well. If you don’t include documentation, you’ll receive a best practice (BP) deviation warning.
2. Parm Method
One important practice, at least in my opinion, is to use the parm methods for your class variables. So, let’s go ahead and create one:
// use the parm methods to access class variables
public InventTable parmInventTable(InventTable _inventTable = inventTable)
{
inventTable = _inventTable;
return inventTable;
}
3. new
Method
You should never write logic inside the new
method or add parameters to it. Instead, this method should always be marked as protected.
The recommended approach is to use a static construct()
or new...()
method to create instances of the class.
//A new method should be protected
// use static new... or construct instead
protected void new()
{
}
4. run
Method
The run
method will contain the core logic of our class. In this case, it simply prints information to the screen.
/// <summary>
/// Executes logic on the parameters of this class
/// </summary>
/// <remarks>
/// Just for demo purposes
/// </remarks>
public void run()
{
;
info(this.parmInventTable().NameAlias);
}
Note that we're using the parm method instead of directly accessing the class variable. This helps enforce encapsulation and aligns with best practices.
5. Construct
Method
Since the new
method is now protected, we need a constructor
method to create an instance of the class. This static method will handle object instantiation and any necessary setup.
private static MHKFORTestBP construct()
{
return new MHKFORTestBP();
}
When a class doesn’t require parameters for instantiation, the construct()
method doesn’t need to be private
. However, in this case, we make it private
because we plan to create a new...()
method that accepts parameters.
Keep in mind:
-
The
construct()
method should not accept parameters. -
It should be used only to create a new instance of the class.
-
It’s also the only place in your application where the class’s
new()
method should be called directly.
6. new...()
Method
As mentioned earlier, since we need to pass a parameter (an InventTable
record), we’ll use a new...()
method instead of construct()
to create the instance.
I'll name it newFromInventTable()
to clearly reflect the parameter it accepts.
In this method, we:
-
Call the
construct()
method to create an instance (instead of callingnew()
directly). -
Set the class variable using the
parm
method. -
Return the fully initialized object.
/// Creates a new MHKFORTestBP object from an InventTable record
/// </summary>
/// <param name="_inventTable">
/// The InventTable used to create the object
/// </param>
/// <returns>
/// New instance of MHKFORTestBP with the InventTable parameter set
/// </returns>
/// <remarks>
/// Use a new... method instead of the constuct method because it takes parameters/// A
/// </remarks>
public static MHKFORTestBP newFromInventTable(InventTable _inventTable)
{
MHKFORTestBP MHKFORTestBP;
;
MHKFORTestBP = MHKFORTestBP::construct();
MHKFORTestBP.parmInventTable(_inventTable);
return MHKFORTestBP;
}
You could also create an init()
method to handle any class initialization logic. While we don’t need initialization in this example, it’s a useful pattern to follow when setup is required.
The init()
method should return a boolean value, indicating whether the initialization succeeded. If it fails, you can throw an error or handle it accordingly.
Here’s what a typical init()
method might look like:
public static MHKFORTestBP newFromInventTable(InventTable _inventTable)
{
MHKFORTestBP MHKFORTestBP;
;
MHKFORTestBP = MHKFORTestBP::construct();
MHKFORTestBP.parmInventTable(_inventTable);
if(!MHKFORTestBP.init())
{
throw error("Item record has no data"); // todo: create label
}
return MHKFORTestBP;
}
Remember to create a label for any text strings used in your code. Hardcoded strings will trigger a best practice warning, so always use labels to stay compliant.
7. main
Method
Finally, we need a main()
method to make this class executable—either by pressing F5 in the editor or by calling it from a menu item.
A simple main()
method might look like this:
server public static void main(Args _args)
{
MHKFORTestBP MHKFORTestBP;
InventTable inventTable;
;
if(_args && _args.dataset() == tablenum(InventTable))
{
// a button was pressed on a InventTable datasource
inventTable = _args.record();
}
else
{
// for demo, simple select the first one
select firstonly inventTable;
}
// display item information
MHKFORTestBP = MHKFORTestBP::newFromInventTable(inventTable);
MHKFORTestBP.run();
}
Normally, you would retrieve the record from the _args
object, but for demonstration purposes, I used a select firstOnly
so the class can be run directly by pressing F5.
As you can see, we use the newFromInventTable()
method to create an instance of the class, followed by a call to the run()
method to execute the logic.
And there you have it—a best practice deviation-free class!
8. Using the Class
Using this class either from a menu item or directly in code is very straightforward.
To use it as a menu item:
-
Simply drag and drop the class into the Action Menu Item node in the AOT.
-
You’ll then be able to use it on any form that has an
InventTable
data source.
To use it in code, just write a single line (assuming there is a variable named inventTable
):
MHKFORTestBP::newFromInventTable(inventTable).run();
Comments
Post a Comment