From 100dffbe84e799a3d8997d2c09152312d0d5145f Mon Sep 17 00:00:00 2001 From: Patrick Schiefer Date: Thu, 21 Apr 2022 09:44:53 +0200 Subject: [PATCH 1/8] template method pattern --- .../patterns/template-method-pattern/index.md | 265 ++++++++++++++++++ 1 file changed, 265 insertions(+) create mode 100644 content/docs/patterns/template-method-pattern/index.md diff --git a/content/docs/patterns/template-method-pattern/index.md b/content/docs/patterns/template-method-pattern/index.md new file mode 100644 index 00000000..8a5edc92 --- /dev/null +++ b/content/docs/patterns/template-method-pattern/index.md @@ -0,0 +1,265 @@ +--- +title: "Template Method Pattern" +tags: ["AL"] +categories: ["Pattern"] +--- + +_Created by Patrick Schiefer, Described by Patrick Schiefer_ + +## Abstract +The goal of this pattern is too simplify the solution of similar problems and make your code more readable + + +## Problem +In nearly every app you have sometimes to solve similar problems for diferent cases. Mostly not the same developer will solve every case. This results in diferent solutions. + +## Description +The pattner is used when you have problems which are independent but require the same logical flow. Examples which occure very often are: Posting Documents, Printing Reports or Exporting Data. + +## Bad Code Example +```al +codeunit 50010 ExportSalesLines +{ + procedure ExportData(SalesHeader: Record "Sales Header", SalesLine : Record "Sales Line") + begin + if not SalesHeader.CheckData() then + exit; + repeat + case SalesHeader.ExportType of + Enum::ExportType::A: + GenerateLineTypeA(SalesLine); + Enum::ExportType::B: + GenerateLineTypeB(SalesLine); + end; + until SalesLine.Next() = 0; + + case SalesHeader.ExportType of + Enum::ExportType::A: + WriteToFile(); + Enum::ExportType::B: + SendToWebService(); + end; + end; + + //TODO Implementation of procedures used in example +} +``` +As you can see in this example the readability gets bader with every new case. + +## The Pattern +To implement the Pattern you need at least 3 objects: +- A template codeunit +- An Interface which provides the needed procedures +- A codeunit which Implements the interrface + +In my Example I show how to implement a data export with templating + +We start with the template +```al +codeunit 50000 ExportTemplate +{ + procedure ExportData(export: Interface IDataExport) + begin + if not export.CheckData() then + exit; + if export.GetLinesToExport() then begin + repeat + export.ExportLine(); + until not export.NextLine(); + end; + export.Finish(); + end; +} +``` +As you can see the template just calls procedures via an interface and just defines the flow of the export without really implementing it. + +As the second part we need an interface for the export functions +```al +interface IDataExport +{ + procedure CheckData(): Boolean; + procedure GetLinesToExport(): Boolean; + procedure ExportLine(); + procedure NextLine(): Boolean; + procedure Finish(); +} +``` + +And now we need an implementation. For my example I wrote a export Codeunit for Sales Headers +```al +codeunit 50001 SalesHeaderExport implements IDataExport +{ + procedure SetSalesHeader(DocType: Enum "Sales Document Type"; No: Code[10]) + begin + SalesHeader.Get(DocType, No); + end; + + procedure CheckData(): Boolean + begin + SalesHeader.TestField(Status, Enum::"Sales Document Status"::Released); + end; + + procedure GetLinesToExport(): Boolean + begin + SalesLines.SetRange("Document Type", SalesHeader."Document Type"); + SalesLines.SetRange("Document No.", SalesHeader."No."); + exit(SalesLines.FindSet()); + end; + + procedure ExportLine() + begin + //Generate Exportdata here + end; + + procedure NextLine(): Boolean + begin + exit(SalesLines.Next() > 0); + end; + + procedure Finish() + begin + // Send or Save data here + end; + + var + SalesHeader: Record "Sales Header"; + SalesLines: Record "Sales Line"; +} +``` + +Now lets have a look how to use the pattern +```al +codeunit 50002 ExportOrders +{ + procedure ExportOrder(DocType: Enum "Sales Document Type"; No: Code[10]) + var + export: Codeunit ExportTemplate; + exportImpl: Codeunit SalesHeaderExport; + exportInt: Interface IDataExport; + begin + exportImpl.SetSalesHeader(DocType, No); + exportInt := exportImpl; + export.ExportData(exportInt); + end; +} +``` + + + +## Benefits +The logical flow is very easy to adopt, it is even possible to add entries to the queue while it is processed. + +## Example + +In this short example I show you how to post multiple sales orders and display message after finishing the last post. + + +We have two commands in this example, the "SalesOrderPostCommander" is used to post a sales order and the "MessageCommander" displays a message. +```al +codeunit 50104 "SalesOrderPostCommander" implements ICommand +{ + procedure SetSalesOrderNumber(value : Code[20]) + begin + No := value; + end; + + procedure Execute() + begin + // TODO Post Sales Header + end; + + + var + No : Code[20]; +} + + +codeunit 50103 "MessageCommander" implements ICommand +{ + procedure SetText(value: Text); + begin + t := value; + end; + + procedure Execute() + begin + Message(t); + end; + + var + t: Text; +} +``` + +Using this two codeunits we can now implement a patch posting +```al + +codeunit 50105 PatchPostQueue +{ + procedure PatchPost() + begin + FilterSalesOrdersToPost(); + if not SalesOrders.Findset(false) then + exit(); // Nothing to post + + repeat + AddSalesOrderToQueue(SalesOrder."No."); + until SalesOrders.Next() = 0; + + AddMessageToQueue('Posting Complete'); + ExecuteQueue(); + end; + + local procedure ExecuteQueue() + var + object : interface "ICommand"; + begin + repeat + object := queue.Pop(); + object.Execute(); + until queue.GetSize() = 0; + end; + + local procedure FilterSalesOrdersToPost() + begin + // Filter Sales Orders here + end; + + local procedure AddMessageToQueue(message : Text); + var + t: Codeunit MessageCommander; + object: Interface ICommand; + begin + t.SetText(message); + object := t; + queue.Push(object); + end; + + local procedure AddSalesOrderToQueue(No : Text); + var + SaleOrderCommander: Codeunit SalesOrderPostCommander; + object: Interface ICommand; + begin + SaleOrderCommander.SetSalesOrderNumber(No); + object := SaleOrderCommander; + queue.Push(object); + end; + + + var + SalesOrders : Record "Sales Header"; + queue: Codeunit Queue; +} +``` + +## Benefits +Your code gains readability and it is very easy to add new cases for the template. You don't always have to think about the whole logic. You just have to implement the details + +## When not to use +The Pattern should not be used for problems which differ to much. So for example if you have two data exports in your app, one is exporting header and lines and the second one only exports headers. In this case I would suggest to not use the pattern or to make two templates out of it. + + + +## References +[Detailed Explanation of the pattern](https://patrickschiefer.com/2022/04/08/template-method-pattern/) + From 691c2fd48f6fa7df7eef3a473ee84f4236d0ef47 Mon Sep 17 00:00:00 2001 From: Patrick Schiefer Date: Thu, 21 Apr 2022 09:59:50 +0200 Subject: [PATCH 2/8] Update Template method Pattern --- .../patterns/template-method-pattern/index.md | 110 +----------------- 1 file changed, 1 insertion(+), 109 deletions(-) diff --git a/content/docs/patterns/template-method-pattern/index.md b/content/docs/patterns/template-method-pattern/index.md index 8a5edc92..27663738 100644 --- a/content/docs/patterns/template-method-pattern/index.md +++ b/content/docs/patterns/template-method-pattern/index.md @@ -1,6 +1,6 @@ --- title: "Template Method Pattern" -tags: ["AL"] +tags: ["AL", "Interface", "Readability"] categories: ["Pattern"] --- @@ -144,114 +144,6 @@ codeunit 50002 ExportOrders } ``` - - -## Benefits -The logical flow is very easy to adopt, it is even possible to add entries to the queue while it is processed. - -## Example - -In this short example I show you how to post multiple sales orders and display message after finishing the last post. - - -We have two commands in this example, the "SalesOrderPostCommander" is used to post a sales order and the "MessageCommander" displays a message. -```al -codeunit 50104 "SalesOrderPostCommander" implements ICommand -{ - procedure SetSalesOrderNumber(value : Code[20]) - begin - No := value; - end; - - procedure Execute() - begin - // TODO Post Sales Header - end; - - - var - No : Code[20]; -} - - -codeunit 50103 "MessageCommander" implements ICommand -{ - procedure SetText(value: Text); - begin - t := value; - end; - - procedure Execute() - begin - Message(t); - end; - - var - t: Text; -} -``` - -Using this two codeunits we can now implement a patch posting -```al - -codeunit 50105 PatchPostQueue -{ - procedure PatchPost() - begin - FilterSalesOrdersToPost(); - if not SalesOrders.Findset(false) then - exit(); // Nothing to post - - repeat - AddSalesOrderToQueue(SalesOrder."No."); - until SalesOrders.Next() = 0; - - AddMessageToQueue('Posting Complete'); - ExecuteQueue(); - end; - - local procedure ExecuteQueue() - var - object : interface "ICommand"; - begin - repeat - object := queue.Pop(); - object.Execute(); - until queue.GetSize() = 0; - end; - - local procedure FilterSalesOrdersToPost() - begin - // Filter Sales Orders here - end; - - local procedure AddMessageToQueue(message : Text); - var - t: Codeunit MessageCommander; - object: Interface ICommand; - begin - t.SetText(message); - object := t; - queue.Push(object); - end; - - local procedure AddSalesOrderToQueue(No : Text); - var - SaleOrderCommander: Codeunit SalesOrderPostCommander; - object: Interface ICommand; - begin - SaleOrderCommander.SetSalesOrderNumber(No); - object := SaleOrderCommander; - queue.Push(object); - end; - - - var - SalesOrders : Record "Sales Header"; - queue: Codeunit Queue; -} -``` - ## Benefits Your code gains readability and it is very easy to add new cases for the template. You don't always have to think about the whole logic. You just have to implement the details From f4b53bdd454b26b858fb33eec1274dc816cd3484 Mon Sep 17 00:00:00 2001 From: Jeremy Vyska <35526546+JeremyVyska@users.noreply.github.com> Date: Fri, 6 May 2022 12:04:06 +0200 Subject: [PATCH 3/8] Update content/docs/patterns/template-method-pattern/index.md Co-authored-by: Henrik Helgesen --- content/docs/patterns/template-method-pattern/index.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/content/docs/patterns/template-method-pattern/index.md b/content/docs/patterns/template-method-pattern/index.md index 27663738..a2770d3d 100644 --- a/content/docs/patterns/template-method-pattern/index.md +++ b/content/docs/patterns/template-method-pattern/index.md @@ -7,8 +7,7 @@ categories: ["Pattern"] _Created by Patrick Schiefer, Described by Patrick Schiefer_ ## Abstract -The goal of this pattern is too simplify the solution of similar problems and make your code more readable - +The goal of this pattern is to simplify the solution of similar problems and make your code more readable. ## Problem In nearly every app you have sometimes to solve similar problems for diferent cases. Mostly not the same developer will solve every case. This results in diferent solutions. From e9225a9ca8c925825f770f4cc94a24bac8a17742 Mon Sep 17 00:00:00 2001 From: Jeremy Vyska <35526546+JeremyVyska@users.noreply.github.com> Date: Fri, 6 May 2022 12:04:12 +0200 Subject: [PATCH 4/8] Update content/docs/patterns/template-method-pattern/index.md Co-authored-by: Henrik Helgesen --- content/docs/patterns/template-method-pattern/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/docs/patterns/template-method-pattern/index.md b/content/docs/patterns/template-method-pattern/index.md index a2770d3d..ba7fde3f 100644 --- a/content/docs/patterns/template-method-pattern/index.md +++ b/content/docs/patterns/template-method-pattern/index.md @@ -13,7 +13,7 @@ The goal of this pattern is to simplify the solution of similar problems and mak In nearly every app you have sometimes to solve similar problems for diferent cases. Mostly not the same developer will solve every case. This results in diferent solutions. ## Description -The pattner is used when you have problems which are independent but require the same logical flow. Examples which occure very often are: Posting Documents, Printing Reports or Exporting Data. +The pattern is used when you have problems which are independent but require the same logical flow. Examples which occur very often are: _Posting Documents_, _Printing Reports_ or _Exporting Data_. ## Bad Code Example ```al From f5f8983e0b803f92bec7ed2c096ab8bb026f0d3e Mon Sep 17 00:00:00 2001 From: Jeremy Vyska <35526546+JeremyVyska@users.noreply.github.com> Date: Fri, 6 May 2022 12:04:18 +0200 Subject: [PATCH 5/8] Update content/docs/patterns/template-method-pattern/index.md Co-authored-by: Henrik Helgesen --- content/docs/patterns/template-method-pattern/index.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/content/docs/patterns/template-method-pattern/index.md b/content/docs/patterns/template-method-pattern/index.md index ba7fde3f..3f7df048 100644 --- a/content/docs/patterns/template-method-pattern/index.md +++ b/content/docs/patterns/template-method-pattern/index.md @@ -110,9 +110,9 @@ codeunit 50001 SalesHeaderExport implements IDataExport //Generate Exportdata here end; - procedure NextLine(): Boolean + procedure NextLine(Steps : integer): Boolean begin - exit(SalesLines.Next() > 0); + exit(SalesLines.Next(Steps) <> 0); end; procedure Finish() From 3f0255364526f199a68678797603b91e760360d0 Mon Sep 17 00:00:00 2001 From: Jeremy Vyska <35526546+JeremyVyska@users.noreply.github.com> Date: Fri, 6 May 2022 12:04:24 +0200 Subject: [PATCH 6/8] Update content/docs/patterns/template-method-pattern/index.md Co-authored-by: Henrik Helgesen --- content/docs/patterns/template-method-pattern/index.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/content/docs/patterns/template-method-pattern/index.md b/content/docs/patterns/template-method-pattern/index.md index 3f7df048..c8e5c3f7 100644 --- a/content/docs/patterns/template-method-pattern/index.md +++ b/content/docs/patterns/template-method-pattern/index.md @@ -147,9 +147,7 @@ codeunit 50002 ExportOrders Your code gains readability and it is very easy to add new cases for the template. You don't always have to think about the whole logic. You just have to implement the details ## When not to use -The Pattern should not be used for problems which differ to much. So for example if you have two data exports in your app, one is exporting header and lines and the second one only exports headers. In this case I would suggest to not use the pattern or to make two templates out of it. - - +The Pattern should not be used for problems which differ too much. For example, if you have two data exports in your app, one is exporting header and lines and the second one only export header. In this case I would suggest to not use the pattern or to make two templates out of it. ## References [Detailed Explanation of the pattern](https://patrickschiefer.com/2022/04/08/template-method-pattern/) From 32f0ec017f765239b912ba6fef58dd6d25e74c29 Mon Sep 17 00:00:00 2001 From: Patrick Schiefer <35697862+PatrickSchiefer@users.noreply.github.com> Date: Tue, 14 Jun 2022 15:16:03 +0200 Subject: [PATCH 7/8] Update content/docs/patterns/template-method-pattern/index.md Co-authored-by: Henrik Helgesen --- content/docs/patterns/template-method-pattern/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/docs/patterns/template-method-pattern/index.md b/content/docs/patterns/template-method-pattern/index.md index c8e5c3f7..a75ff5e6 100644 --- a/content/docs/patterns/template-method-pattern/index.md +++ b/content/docs/patterns/template-method-pattern/index.md @@ -43,7 +43,7 @@ codeunit 50010 ExportSalesLines //TODO Implementation of procedures used in example } ``` -As you can see in this example the readability gets bader with every new case. +As you can see in this example the readability gets worse with every new case. ## The Pattern To implement the Pattern you need at least 3 objects: From b952cc542164a7459ff2fd66f7d1f58f73fb32ca Mon Sep 17 00:00:00 2001 From: Patrick Schiefer <35697862+PatrickSchiefer@users.noreply.github.com> Date: Tue, 14 Jun 2022 15:16:45 +0200 Subject: [PATCH 8/8] Apply suggestions from code review Co-authored-by: Henrik Helgesen --- content/docs/patterns/template-method-pattern/index.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/content/docs/patterns/template-method-pattern/index.md b/content/docs/patterns/template-method-pattern/index.md index a75ff5e6..5465efec 100644 --- a/content/docs/patterns/template-method-pattern/index.md +++ b/content/docs/patterns/template-method-pattern/index.md @@ -10,7 +10,7 @@ _Created by Patrick Schiefer, Described by Patrick Schiefer_ The goal of this pattern is to simplify the solution of similar problems and make your code more readable. ## Problem -In nearly every app you have sometimes to solve similar problems for diferent cases. Mostly not the same developer will solve every case. This results in diferent solutions. +In nearly every app you sometimes have to solve similar problems for different cases. Mostly not the same developer will solve every case. This results in different solutions. ## Description The pattern is used when you have problems which are independent but require the same logical flow. Examples which occur very often are: _Posting Documents_, _Printing Reports_ or _Exporting Data_. @@ -51,7 +51,7 @@ To implement the Pattern you need at least 3 objects: - An Interface which provides the needed procedures - A codeunit which Implements the interrface -In my Example I show how to implement a data export with templating +In my example I show how to implement a data export with templating. We start with the template ```al @@ -144,7 +144,7 @@ codeunit 50002 ExportOrders ``` ## Benefits -Your code gains readability and it is very easy to add new cases for the template. You don't always have to think about the whole logic. You just have to implement the details +Your code gains readability and it is very easy to add new cases for the template. You don't always have to think about the whole logic. You just have to implement the details. ## When not to use The Pattern should not be used for problems which differ too much. For example, if you have two data exports in your app, one is exporting header and lines and the second one only export header. In this case I would suggest to not use the pattern or to make two templates out of it.