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

Notifications

Announcements

No record found.

Community site session details

Community site session details

Session Id :
Finance | Project Operations, Human Resources, ...
Suggested Answer

improve x++ code and design

(2) ShareShare
ReportReport
Posted on by 1,965
Hi,

I have two menu items one connected to Class1 and the other one connected to Class2
below i showed the code for each class. 

I think the code can be made better as there are some common things between those two classes. How can i refactor it to make it better?
Also please note the difference between Class1 and Class2, is that class2 is dealing with Table1 which is considered extra to what class1 does. Also class1 is using Enum1, while class2 is using Enum2
class Class1 extends AARunBase
{
    SalesPurchLine       salesPurchLine;


    public ClassDescription caption()
    {
        return "@Label1:Class1";
    }


    protected str helpText()
    {
        return "@Label1:Class1";
    }


    public static Class1 construct()
    {
        return new Class1();
    }


    public static void main(Args _args)
    {
        Class1 class1 = Class1::construct();

        class1.getLast();
        class1.initFromArgs(_args);

        if (class1.prompt())
        {
            class1.runOperation();
        }
    }

    public void doRun()
    {
        if (salesPurchLine)
        {
            if(salesPurchLine.salesPurchLineInterface().AASalesPurchLineInterface().parmAAField1())
            {
                ClassA::sendA(salesPurchLine, AAEnum::Enum1);
            }
            else
            {
                ClassB::sendB(salesPurchLine, AAEnum::Enum1);
            }
        }
    }


    public void initFromArgs(Args _args)
    {
        if(_args.record())
        {
            if(_args.record().TableId == tableNum(SalesQuotationLine) || _args.record().TableId == tableNum(SalesLine))
            {
                salesPurchline = _args.record();
            }
            else
            {
                throw error(Error::wrongUseOfFunction(funcName()));
            }
        }
        
    }


    public boolean canGoBatch()
    {
        return true;
    }


    public boolean canRunInNewSession()
    {
        return false;
    }

    public SalesPurchLine parmSalesPurchLine(SalesPurchLine _salesPurchLine = salesPurchLine)
    {
        salesPurchLine = _salesPurchLine;

        return salesPurchLine;
    }

}


 
class Class2 extends AARunBase
{
    Table1              table1;
    SalesPurchLine      salesPurchLine;


    public ClassDescription caption()
    {
        return "@Label2:Class2";
    }


    protected str helpText()
    {
        return "@Label2:Class2";
    }


    public static Class2 construct()
    {
        return new Class2();
    }

    public static void main(Args _args)
    {
        Class2 class2 = Class2::construct();

        class2.getLast();
        class2.initFromArgs(_args);

        if (class2.prompt())
        {
            class2.runOperation();
        }
    }


    private void doRun()
    {
        if(salesPurchLine)
        {
            if(salesPurchLine.salesPurchLineInterface().AASalesPurchLineInterface().parmAAField1()))
            {
                ClassA::sendA(salesPurchLine, AAEnum::Enum2);
            }
            else
            {
                ClassB::sendB(salesPurchLine, AAEnum::Enum2);
            }
        }
        else if(table1)
        {
            ClassA::sendA(table1, AAEnum::Enum2);
        }
    }

  
    public void initFromArgs(Args _args)
    {
        if (_args.record())
        {
            switch (_args.record().TableId)
            {
                case tableNum(SalesQuotationLine):
                case tableNum(salesline):
                    salesPurchLine =  _args.record();
                    break;

                case tableNum(Table1):
                    table1  = _args.record();
                    break;

                default:
                    throw error(Error::wrongUseOfFunction(funcName()));
            }
        }
    }

    public boolean canGoBatch()
    {
        return true;
    }


    public boolean canRunInNewSession()
    {
        return false;
    }

    public SalesPurchLine parmSalesPurchLine(SalesPurchLine _salesPurchLine = salesPurchLine)
    {
        salesPurchLine = _salesPurchLine;

        return salesPurchLine;
    }

}


And this how sendA and sendB methods look like in classA and classB
also code is repetitive, only classB has extra switch for Table
 

class ClassA extends classCommon
{   
    public static ClassA construct()
    {
        return new ClassA();
    }

    public static void sendA(Common _common, AAEnum _enum)
    {
        SalesQuotationLine   quotationLine;
        SalesLine            salesLine;
        Table1               table1;

        if (_common)
        {
            ClassA classA = ClassA::construct();

            switch (_common.TableId)
            {
                case tableNum(SalesQuotationLine):
                    quotationLine = _common;
                    classA.parmSalesPurchLine(quotationLine);
                    classA.parmAAEnum(_enum);
                    classA.setDescription(strFmt("@Label3:Desc", quotationLine.AAField2, quotationLine.AAField2, quotationLine.AAField3,quotationLine.QuotationId));
                    classA.TableZ = TableZ::find(curExt(), quotationLine.InventDim().InventLocationId);
                    break;

                case tableNum(Salesline):
                    salesLine = _common;
                    classA.parmSalesPurchLine(salesLine);
                    classA.parmAAEnum(_enum);
                    classA.setDescription(strFmt("@Label3:Desc", salesLine.AAField1, salesLine.AAField2, salesLine.AAField3, salesLine.SalesId));
                    classA.TableZ = TableZ::find(curExt(), salesLine.InventDim().InventLocationId);
                    break;

                case tableNum(Table1):
                    table1 = _common;
                    classA.parmTable1(table1);
                    classA.parmAAEnum(_enum);
                    classA.setDescription(strFmt("@Label3:Desc", table1.AAField1, table1.AAField2, table1.AAField3, table1.Id));
                    classA.TableY = TableY::findRecId(table1.AAField4);
                    break;

                default:
                    throw error(Error::wrongUseOfFunction(funcName()));
            }

            classA.send();
        }
    }
}
 
class ClassB extends classCommon
{   
    public static ClassB construct()
    {
        return new ClassB();
    }

    public static void sendB(Common _common, AAEnum _enum)
    {
        SalesQuotationLine   quotationLine;
        SalesLine            salesLine;

        if (_common)
        {
            ClassB classB = ClassB::construct();

            switch (_common.TableId)
            {
                case tableNum(SalesQuotationLine):
                    quotationLine = _common;
                    classB.parmSalesPurchLine(quotationLine);
                    classB.parmAAEnum(_enum);
                    classB.setDescription(strFmt("@Label3:Desc", quotationLine.AAField2, quotationLine.AAField2, quotationLine.AAField3,quotationLine.QuotationId));
                    classB.TableZ = TableZ::find(curExt(), quotationLine.InventDim().InventLocationId);
                    break;

                case tableNum(Salesline):
                    salesLine = _common;
                    classB.parmSalesPurchLine(salesLine);
                    classB.parmAAEnum(_enum);
                    classB.setDescription(strFmt("@Label3:Desc", salesLine.AAField1, salesLine.AAField2, salesLine.AAField3, salesLine.SalesId));
                    classB.TableZ = TableZ::find(curExt(), salesLine.InventDim().InventLocationId);
                    break;

                default:
                    throw error(Error::wrongUseOfFunction(funcName()));
            }

            classB.send();
        }
    }
}
 
Categories:
I have the same question (0)
  • André Arnaud de Calavon Profile Picture
    300,917 Super User 2025 Season 2 on at
    Hi ..,
     

    Where exactly do you have the two menu items and what are their functional labels? What is the process where these menu items are used? Are they both on e.g. the same form or on distinct forms?
  • .. Profile Picture
    1,965 on at
     
    The action menu item connected to class1 exists on SalesLine, SalesQuotationLine
    The action menu item connected to class2 exists on SalesLine, SalesQuotationLine and a custom table
     
    So yes they exist on same forms, where class1 menu item exists on an additional form as well
     
     
    when one of those menu items is clicked, we gather the data from the caller (quotationLine, salesLine or customTable) to prepare an xml to send it to a custom API.
    The xml file might differ a little based on if it's clicked from class1 or class2 menu items, but still there are lots of common things.
    Also some xml nodes might be filled if it was quotationLine but not if it was customTable/SalesLine..etc
  • Martin Dráb Profile Picture
    237,807 Most Valuable Professional on at
    First of all, read the discusssion we had in April in your thread avoid repetitive code, which was about the topic. Also, you may want to apply what you learn  about table maps (in the old thread and your recent thread AOT map) and rewrite your code accordingly.
     
    If you know about class hierarchies and table maps, why don't apply one of these solutions and you ask again how to redesing your code, as if you've never got any answers? If you tried to do it and found you don't know how to use either classes or maps, explain your specific problem to us. We can't address your actual questions if you don't write them down. Or can't you decide which of the two options to use?
  • .. Profile Picture
    1,965 on at
    Hi @Martin Dráb

    but if you look at the code i put in the question, I'm already using SalesPurchLine map in class1 and class2

    so i'm asking how can i make the code better for those 4 classes (class1, class2, classA and classB) to avoid repeating logic
  • Martin Dráb Profile Picture
    237,807 Most Valuable Professional on at
    A good starting point would be thinking about the purpose of each class and giving it a descriptive name. Names like Class2 say nothing and make working with your code (and undestanding your intention) quite difficult. A part of this process is desiding which classes will contain common logic and which (if any) logic specific to a perticular table or process.
     
    I see you've created a class hierarchy with classCommon, ClassA and ClassB, but you don't use for anything. Your current implementation could be replaced with  single class with two methods (sendA() and sendB()).
     
    The only difference between (sendA() and sendB()) seems to be the handling in Table1. You need to decide how you want to handle it and then implement is accordingly. Here are two idea:
    1. Use a parameter to control whether Table1 should be processed or the method should thrown an exception when called with Table1. This may be a method parameter or an instance variable of the class.
    2. Utilize the table inheritance when the base class will handle SalesLine and SalesQuotationLine, and a specialized class that will be able to handle Table1 too. The caller code will decide which logic to use by instantiating the right class instead of using a single class and setting the parameter.
    Then you have the switch where you check whether _common is SalesLine, SalesQuotationLine or Table1. If all three are mapped to your map, you can write all the code against the map. The type of the parameter will be the map (instead of Common) and you'd refer to map fields (instead of casting the parameter to a specific table and use table fields).
     
    For reference, we've discussed in AOT map why a custom map would be a better choice than reusing SalesPurchLine map.
     
    Am not going to deal with Class1 and Class2 in this post; it's already quite long.
     
  • André Arnaud de Calavon Profile Picture
    300,917 Super User 2025 Season 2 on at
    I'm still trying to figure out what you are intending to provide to end users. I asked for the labels on the menu items. How does a user know what menu item to click? I can only read about a technical difference using table1, but don't understand the requirement. This is also caused by using non meaningful names for your classes like class1, classA, etc.
    Also still the question pending about what is enum1 and what is enum2?
  • Suggested answer
    Syed Amir Ali Profile Picture
    179 on at
    Hi, 

    What I understand from your statement is that you're looking to refactor the duplicated logic shared between Class1 and Class2 (the menu item classes), where the operations are performed by calling the sendA() method of ClassA and the sendB() method of ClassB.

    The solution is to define an abstract base class as shown below.
     
    abstract class TaskABC_CommonHelper extends AARunBase
    {
        protected SalesPurchLine salesPurchLine;
     
        public boolean canGoBatch() { return true; }

        public boolean canRunInNewSession() { return false; }
     
        public SalesPurchLine parmSalesPurchLine(SalesPurchLine _salesPurchLine = salesPurchLine)
        {
            salesPurchLine = _salesPurchLine;
            return salesPurchLine;
        }
     
        public void initCommonArgs(Args _args)
        {
            if (_args.record())
            {
                switch (_args.record().TableId)
                {
                    case tableNum(SalesQuotationLine):
                    case tableNum(SalesLine):
                        salesPurchLine = _args.record();
                        break;
                    default:
                        throw error(Error::wrongUseOfFunction(funcName()));
                }
            }
        }
     
        protected abstract AAEnum getEnumValue();
    }

    This abstract class helps eliminate about 90% of the duplicated code between Class1 and Class2. Make sure to rename the class according to your specific task or context.

     

    Please mark my answer as 'Verified' if you found it helpful.



       
  • Martin Dráb Profile Picture
    237,807 Most Valuable Professional on at
    Syed, your suggested answer unfortunately doesn't contain any solution. The abstract class itself is useless without any plan about how it'll be used and how the necessary logic will be implemented either in this class or its childern. So far, it contains only a way to set a variable (which doesn't even cover all requirements), which isn't really a solution of the problem. If you really want to suggest a solution, you'll need to do more work on it. That a class hierarchy can be used is something I told ".." months ago, therefore that's not a new idea.
  • .. Profile Picture
    1,965 on at
    Hi Andre,


    The action menu item connected to class1 exists on SalesLine, SalesQuotationLine  (it's used to check existing orders in the external system by calling an api) (Enum1: is called 'examine')  so u can call the menu item 'examineOrder' and you could call class1 the same examineOrder

    The action menu item connected to class2 exists on SalesLine, SalesQuotationLine And a Custom table (it's used to create a new order in the external system by calling an api) (Enum2 is called 'create') and you can call the menu item 'CreateOrder' and you could call class2 the same CreateOrder


    Hi Martin,

    For the descriptive names, i provided this with my answer to Andre, so how would u refactor class1 and class2? would you make a common base class by doing initArgs like this where class2 can add logic to "handleMore())? but what how would the main method look like in the abstract class and in class1 and class2?
    
        public void initFromArgs(Args _args)
        {
            if(_args && _args.dataset())
            {
                switch(_args.dataset())
                {
                    case tableNum(SalesQuotationLine):
                    case tableNum(salesLine):
                        salesPurchline = _args.record();
                        break;
                    default:
                        if(!this.handleMore(_args.record()))
                        {
                            throw error(Error::wrongUseOfFunction(funcName()));
                        }
                        break;
                }
            }
            
        }
    
        protected boolean handleMore(Common _record)
        {
            return false;
        }


    for your sentence "a class hierarchy with classCommon, ClassA and ClassB"    This classCommon is used to prepare the request to the api, so maybe there should be another level for classCommon? If you saw at the end of sendA and sendB method, i called classA.send() and classB.send(), the send logic is in classCommon

    Also regarding this "Your current implementation could be replaced with  single class with two methods (sendA() and sendB())" what do you mean i should do?

    yes the only difference between sendA and SendB is table1, but as i said classA and classB extends classCommon to prepare the xml and send the reuqest, and the xml could have few different things between classA and classB, so i would override some of classCommon methods in classA and class B, that's why i think i need two classes

    regarding this "use a parameter to control whether Table1 should be processed or the method should thrown an exception when called with Table1. This may be a method parameter or an instance variable of the class."  you mean in class1 and class2 i do this
    ClassA::sendA(salesPurchLine, AAEnum::Enum2, false); but how would sendA look like now from the inside

    Regarding "utilize the table inheritance when the base class will handle SalesLine and SalesQuotationLine, and a specialized class that will be able to handle Table1 too. The caller code will decide which logic to use by instantiating the right class instead of using a single class and setting the parameter."   can you please show me


    Yes i will create a new map instead of extending salespurchLine map, but table1 can't be added to the map, that's why i will still need to use common instead of map. For example Table1 doesn't contain salesPool field and Table1 contains field that don't exist in both salesline/salesQuotationLine (but there are common fields as well)

     
  • Martin Dráb Profile Picture
    237,807 Most Valuable Professional on at
    You have too many questions at once. Let me give you two examples, one using a conditon and one inheritance. Choose one of the approaches, try to implement it and ask questions if you get into troubles.
     
    The first one uses no inheritance, just a single class with conditional logic.
    class MySender
    {
        boolean supportTable1;
        
        public boolean parmSupportTable1(boolean _supportTable1 = supportTable1)
        {
            supportsTable1 = _supportsTable1;
            return supportTable1;
        }
        
        void send(SalesPurchLine _record, AAEnum _enum)
        {
            if (_record is Table1 && !supportTable1)
            {
                throw error(Error::wrongUseOfFunction(funcName()));
            }
            
            ...
        }
    }
    
    // Caller code
    
    MySender sender = new Sender();
    if (...)
    {
        // Here we decide which logic should be used
        sender.parmSupportTable1(true);
    }
    
    sender.send(...);
    And now the same with two sender classes:
    class MySender
    {
        void send(SalesPurchLine _record, AAEnum _enum)
        {
            ... handle SalesLine and SalesQuotationLine, throw an exception for other tables
        }
    }
    
    class MySenderWithTable1 extends MySender
    {
    
        void send(SalesPurchLine _record, AAEnum _enum)
        {
            if (_record is Table1)
            {
                ...
            }
            else
            {
                super(_record, _enum);
            }
        }
    }
    
    // Caller code
    
    // Now we choose the behavior by selecting the class to instantiate
    MySender sender;
    if (...)
    {
        sender = new MySenderWithTable1();
    }
    else
    {
        sender = new MySender();
    }
    
    sender.send(...);

Under review

Thank you for your reply! To ensure a great experience for everyone, your content is awaiting approval by our Community Managers. Please check back later.

Helpful resources

Quick Links

Responsible AI policies

As AI tools become more common, we’re introducing a Responsible AI Use…

Neeraj Kumar – Community Spotlight

We are honored to recognize Neeraj Kumar as our Community Spotlight honoree for…

Leaderboard > Finance | Project Operations, Human Resources, AX, GP, SL

#1
Martin Dráb Profile Picture

Martin Dráb 664 Most Valuable Professional

#2
André Arnaud de Calavon Profile Picture

André Arnaud de Cal... 522 Super User 2025 Season 2

#3
Sohaib Cheema Profile Picture

Sohaib Cheema 303 User Group Leader

Last 30 days Overall leaderboard

Product updates

Dynamics 365 release plans