web
You’re offline. This is a read only version of the page.
close
Skip to main content

Announcements

News and Announcements icon
Community site session details

Community site session details

Session Id :

Object Oriented Programming - OOP

Anton Venter Profile Picture Anton Venter 20,909 Super User 2026 Season 1

In Dynamics 365 Finance and Operations (F&O), Object Oriented Programming (OOP) is used frequently in the standard application code base and is fundamental to how the business logic is applied in the system.

The basics

As the name implies, OOP makes uses of objects in programming. In X++, and also other programming languages, objects are designed with classes. These classes they represent a real life objects. Just like real life objects, they also have properties or attributes and they can do certain things.

The image below illustrates an OOP example of three different types of pets. It consists of a hierarchy with a base class and three different sub types (child classes) inheriting properties and methods from the base (parent) class.

Note
A key principle of OOP is inheritance. In fact, inheritance is a core design principle in the business logic / code base as well as the actual data of the system in F&O.

Image 1: Class design.

Base class

The base class is called Pet because it is generic and contains all the property definitions and functionality for all pets. In our example, we have three methods defined in the base class: species, legs and eyes. These are some basic attributes of all pets.

Note that the Pet base class does not implement the two abstract methods. This is by design because we want each child class to implement these methods specifically. Hence the abstract keyword in the method declarations. The Pet class declaration also contains the abstract keyword. This means that Pet class to be instantiated directly, only the child classes can be instantiated. This is also by design.

The third method called eyes, is implemented in the base class because most pets have two eyes. But, if it just so happens that there are pets with more than 2 eyes, like spiders, the child class of that pet can implement the eyes method and specify how many eyes there are.


public abstract class Pet
{
    //the species of the pet e.g. mammal, reptile, bird etc.
    public abstract str species()
    {
    }

    //the number of legs of the pet.
    public abstract int legs()
    {
    }

    //the number of eyes of the pet.
    public int eyes()
    {
        return 2;
    }

}

Base class.

Sub types

The sub types or child classes inherit properties and methods from the Pet base class using the extends keyword. They contain the pet specific properties and or methods. Each sub type class implements the species and legs methods.

Note
Because these two methods are declared abstract in the base class, the compiler enforces the implementation of the abstract methods in each child class or else will raise a compile error.


public class Pet_Bird extends Pet
{
    public str type()
    {
        return 'Bird';
    }

    public int legs()
    {
        return 2;
    }
}

public class Pet_Dog extends Pet
{
    public str type()
    {
        return 'Mammal';
    }

    public int legs()
    {
        return 4;
    }
}

public class Pet_Lizard extends Pet
{
    public str type()
    {
        return 'Reptile';
    }

    public int legs()
    {
        return 4;
    }
}

Runnable class

To demonstrate OOP with our pet example, the runnable class below creates an object of each pet class and then calls the showMessage method.

The showMessage method calls the species, legs and eyes methods of the Pet object. So, which method gets called in which class? Well, that depends on the child class implementation of any of the methods in the base class.

None of the child classes implement the eyes method but all of child classes implement the species, legs methods. The showMessage method has single argument of the type Pet which is the aprent class of all the child classes. This is one of the advantages of OOP and this is how we can use a single variable type for the different child classes.


public class PetDemoJob
{
    public static void main(Args _args)
    {
        //variable of type Pet
        Pet pet;
        
        //create object of type Pet_Dog
        pet = new Pet_Dog();
        
        //show message
        PetDemoJob::showMessage(pet);

        //create object of type Pet_Bird
        pet = new Pet_Bird();

        //show message
        PetDemoJob::showMessage(pet);

        //create object of type Pet_Lizard
        pet = new Pet_Lizard();

        //show message
        PetDemoJob::showMessage(pet);
    }

    public static void showMessage(Pet _pet)
    {
        info(strFmt('A %1 has %2 eyes and %3 legs.',
            _pet.species(),
            _pet.eyes(),
            _pet.legs()));
    }

}

Running the class

Let's run our class using SysClassRunner in F&O to see what happens.

The system calls the main method our runnable class and three infolog messages are displayed:

Image 2: The infolog messages.

  • A Reptile has 2 eyes and 4 legs
  • A Bird has 2 eyes and 2 legs
  • A Mammal has 2 eyes and has 4 legs

This demontrates that the species and legs methods of each different pet object was called, but for all pets the eyes method in the base class was called.

OOP in F&O

Okay, so that's all fine and dandy but how is does this relate to F&O? How is OOP implemented in F&O with real life objects?

The Bank class

Let's take a simple example, the Bank class. The Bank base class represents a bank account in real life. In this case a bank account is not tangible but classes can also represent intangible objects too. Like our Pet base class, it contains properties and methods. Some of these methods are overwritten in the child classes (sub types) depending on the local bank account rules in the related country. For simplicity and to keep this article as short as possible, the class desing diagram below contains just two of the Bank class methods.

The base class contains the properties and methods and all child classes that extend the Bank class, inherit those properties and methods. It is no surprise but bank account numbers are different per country. So, how is that handled with OOP? This is the power of OOP, each child class implements the base class methods differently. This provides isolation en encapsulation of the code. Let's take the checkBankAccount method declared in the base class. As the name indicates, it's purpose is to check the bank account number and returns a BOOLEAN value if the bank account number is valid or not. This is validation is different in different countries.

Image 3: Bank class design (simplified).

The Bank class

The Bank class is the base or parent class of all the Bank sub types or child classes. The class contains more methods but I have included only two methods to keep things simple. These two methods in the Bank_NL, Bank_FR and Bank_ES classes are implemented differently because of the differences in bank accounts in these respective countries.


public class Bank
{
    public boolean checkBankAccount(BankAccountMap _bankAccountMap)
    {
        boolean ret = true;

        ret = ret && this.checkBankAccountNum(_bankAccountMap.AccountNum);

        return ret;
    }

    public boolean checkBankAccountNum(BankAccount _bankAccount)
    {
        boolean ret = true;
        return ret;
    }
}

Bank_NL class

The checkBankAccountNum method is overwritten and implemented according to the NL bank account format. It checks if the format is correct and is valid according to the specifications.


public class Bank_NL extends Bank
{
    public boolean checkBankAccount(BankAccountMap _bankAccountMap)
    {
        return this.checkBankAccountNum(_bankAccountMap.AccountNum);
    }

    public boolean checkBankAccountNum(BankAccount _bankAccount)
    {
        GlobalizationInstrumentationHelper::featureRun(GlobalizationConstants::FeatureReferenceNL00016, funcName());

        boolean ok = true;

        if (strLen(_bankAccount) == 9)
        {
            if (strRem(subStr(_bankAccount,1,9), '1234567890') != '')
            {
                error(strfmt("@SYS86814", _bankAccount));
                ok = false;
            }
        }

        if (ok)
        {
            // "P" or "G" Stands for a Giro Number (Post Bank)
            if (strUpr(subStr(_bankAccount,1,1)) == 'p' || strupr(subStr(_bankAccount,1,1)) == 'g')
            {
                ok = this.giroTest(_bankAccount);
            }
            else
            {
                ok = this.bankTest(_bankAccount);
            }
        }

        return ok;
    }
}

Bank_FR class

The checkBankAccountNum method is overwritten and implemented according to the FR bank account format. It checks if the format is correct and is valid according to the specifications.


public class Bank_FR extends Bank
{
    public boolean checkBankAccount(BankAccountMap _bankAccountMap)
    {
        boolean ret = this.checkBankRegNum(_bankAccountMap.RegistrationNum);
        if (ret)
        {
            ret = this.checkBankAccountNum(_bankAccountMap.AccountNum);
        }

        if (ret)
        {
            ret = this.checkControlText(_bankAccountMap.RegistrationNum, _bankAccountMap.AccountNum);
        }

        return ret;
    }

    public boolean checkBankAccountNum(BankAccount _bankAccount)
    {
        GlobalizationInstrumentationHelper::featureRun(GlobalizationConstants::FeatureReferenceFR00013, funcName());

        if (!_bankAccount)
        {
            return true;
        }

        if (strLen(_bankAccount) != 13)
        {
            return checkFailed(strFmt("@SYS74835", fieldPName(BankAccountMap, AccountNum)));
        }

        return true;
    }

    protected boolean checkControlText(BankRegNum _bankRegNum, BankAccount _bankAccount)
    {
        const str digits = '0123456789';

        str 23 controlTxt = _bankRegNum + _bankAccount;

        int idx = strNFind(controlTxt, digits, 1, 99999);
        while (idx)
        {
            controlTxt = strPoke(controlTxt, this.accountChar2Num(subStr(controlTxt, idx, 1)), idx);

            idx = strNFind(controlTxt, digits, idx + 1, 99999);
        }

        return true;
    }
}

Bank_ES class

The checkBankAccountNum method is overwritten and implemented according to the ES bank account format. It checks if the format is correct and is valid according to the specifications.


public class Bank_ES extends Bank
{
    public boolean checkBankAccount(BankAccountMap _bankAccountMap)
    {
        return this.checkBankAccountNum(_bankAccountMap.AccountNum);
    }

    public boolean checkBankAccountNum(BankAccount _bankAccount)
    {
        GlobalizationInstrumentationHelper::featureRun(GlobalizationConstants::FeatureReferenceES00013, funcName());

        int digit, multiply;
        int sum1 = 0;
        int sum2 = 0;

        int i = strLen(_bankAccount);
        if (!i)
        {
            return true;
        }

        if (i != 20)
        {
            return checkFailed(strFmt("@SYS54162", 20));
        }

        for (i = 0; i < 20; i = i)
        {
            do
            {
                i++;
                digit = char2Num(_bankAccount, i) - char2num('0', 1);
                if (digit < 0 || digit > 9)
                {
                    return checkFailed("@SYS97947");
                }
            }
            while (i == 9 || i == 10);

            switch ((i < 9 ? i + 2 : i) mod 10)
            {
                case 0: multiply = 6; break;
                case 1: multiply = 1; break;
                case 2: multiply = 2; break;
                case 3: multiply = 4; break;
                case 4: multiply = 8; break;
                case 5: multiply = 5; break;
                case 6: multiply = 10; break;
                case 7: multiply = 9; break;
                case 8: multiply = 7; break;
                case 9: multiply = 3; break;
            }

            if (i < 9)
            {
                sum1 += digit * multiply;
            }
            else
            {
                sum2 += digit * multiply;
            }
        }

        if (!this.checkDigit(subStr(_bankAccount, 9, 1), sum1))
        {
            return checkFailed(strFmt("@SYS54163",1));
        }

        if (!this.checkDigit(subStr(_bankAccount, 10, 1), sum2))
        {
            return checkFailed(strfmt("@SYS54163",2));
        }

        return true;
    }
}

Testing OOP with the Bank child classes

The code below is a runnable class and in the first part, it creates an instance of the Bank_NL class and calls the checkBankAccountNum method. Because the bank account number passed to the Bank_NL object is valid and the code in the checkBankAccountNum method of the Bank_NL class is executed, it shows a message "Bank account is valid".

In the second part, it creates an instance of the Bank_FR type and calls the checkBankAccountNum method. Because the bank account number passed to the Bank_FR object is a NL bank account number and the code in the checkBankAccountNum method of the Bank_FR class is executed, it shows a message "Bank account is invalid". This is because the first thing that is checked if bank account number length is 13. In our example it is 10, so that's why the method returns false and that's why the message is shown.

public class BankDemoJob
{
    public static void main(Args _args)
    {
        //variable of type Bank
        Bank bank;
        
        //create object of type Bank_NL
        bank = new Bank_NL();
        
        //check bank account number using a NL bank account number with the Bank_NL object
        if (bank.checkBankAccountNum('0417164300'))
        {
            info('Bank account is valid');
        }
        else
        {
            error('Bank account is invalid');
        }


        //create object of type Bank_FR
        bank = new Bank_FR();
        
        //check bank account number using a NL bank account number with the Bank_FR object
        if (bank.checkBankAccountNum('0417164300'))
        {
            info('Bank account is valid');
        }
        else
        {
            error('Bank account is invalid');
        }
        
    }

}

Runnable class.

Running the class

Let's run our class above using SysClassRunner in F&O to see what happens. Three different types of infolog messages are displayed. One info, one warning and one error is displayed. This is expected because we checked a NL bank account with the FR class.

Image 4: The infolog messages.

  • Bank account is valid
  • The length of Bank account number should be 13 characters including control key.
  • Bank account is invalid

Example using construct

For simplicity in our two examples, I create an intance of each class expliticly like this bank = new Bank_NL();. However, in F&O this is usually done properly using a construct method in the base class. This method "constructs" an instance of the related sub type (child) class depending on the argument passed to the method. This is best practice and I definitely recommend that you follow this pattern in your own designs.

Below is the construct method of the Bank class. It has a single argument: ISO country code. The data type used is specific for the ISO country codes with a length of 2 characters. The country code passed to the construct method could be "NL", "FR" or "ES" or any of the supported country codes in the construct method. The method checks which ISO country code is passed in and then creates the related instance of the sub type (child) class for that country. So passing "NL" will return an instance of the Bank_NL class.

Below is an example in the standard application of how the system calls the construct method of the Bank class. The code passes an ISO country code and then class the checkBankAccountNum method. See line 39 and 40 below. The ISO country code from the bank account of the worker bank account table record is used.

This is a simple but good example of OOP in F&O and there are endless examples of this kind of object construction in the application. This is a fundamental code design principle in F&O that drives the business logic in many different ways. If you browse the application source, you will find many examples.


This was originally posted here.

Comments

*This post is locked for comments