Skip to Content

I could give number of reasons why I think we should commonly use Object Oriented Programming style and probably equal number of reasons why I think is it so magical. Anyhow, this blog is not intended to write a thesis, but to share with you, the Reader, with couple nice tricks I have learnt recently when I decided to take OO approach for some trivial issue. I leave it to your decision, whether you smuggle any of these to your applications or just share your thoughts about that.

Case study


In SAP HR module, different objects can be stored in infotype 1000 (table HRP1000, where PLVAR (2char) OTYPE (2char) and OBJID (8 numc) are key info of the object). Among others we can have i.e. priority object. The object can have long description stored in infotype 1002 (co-joined tables HRP1002 and HRT1002). SAP delivers by standard a function RH_OBJECT_DESCRIPTION_WRITE which allows to store the text w/o bothering of how it is splited b/w these tables. All we should do is to give object coordinates (key info) and its description with PT1002 table type to function.

In my case I had the data originated from excel file. Long description was provided in form of a STRING. So, I needed a conversion from STRING to PT1002 table type. Seems that function HRHAP_CONVERT_STRG_TO_PT1002 was ideal for this task. It turned out, however, it could not parse the STRING correctly. I wanted to handle extra chars (i.e. new line) but it didn’t. Instead it parsed the text at 79th char. So the split could happen i.e. in the middle of a word. This caused that below snippet…

data  lv_data   type string.
data: lt_pt1002 type hap_t_pt1002,
        lw_pt1002 type pt1002.
 concatenate
 'Do I/we...'
 '... collaborate?'
 '... communicate openly ?'
 '... share ideas?'
 '... leverage knowledge?'
 '... have a focus outside and inside (about customers, partners)?'
 into lv_data separated by cl_abap_char_utilities=>newline.
 call function 'HRHAP_CONVERT_STRG_TO_PT1002'
   exporting
     string_description = lv_data
   importing
     t_desc_tab         = lt_pt1002.
 loop at lt_pt1002 into lw_pt1002.
   write: / lw_pt1002-tline.
endloop.

… resulted in

Do I/we…#… collaborate?#… communicate openly ?#… share ideas?#… lever

age knowledge?#… have a focus outside and inside (about customers, partners)?

This was wrong. I should have data splitted both at new line and prevent split at 79th char. So I came with custom solution.

Common approach


The requirement was to:

  • split the string into lines
  • if a line doesn’t fit 79th chars restriction, it should break into words and split should happen after last fitting word
  • I decided to build more general solution where I could split at char level if a word does not fit restriction other than my module defined

I ended up with procedular solution encapsulated within function module and some local routines. Please don’t study the code, just take a brief look how the flow is organizated.

function z_glpm_fum_sstr_to_pt1002.
*"----------------------------------------------------------------------
*"*"Local Interface:
*"  IMPORTING
*"     VALUE(STRING_DESCRIPTION) TYPE  STRING
*"  EXPORTING
*"     REFERENCE(T_DESC_TAB) TYPE  PTM_T_PT1002
*"----------------------------------------------------------------------
* this is a custom version of function HRHAP_CONVERT_STRG_TO_PT1002
* which splits string either at 79th char or cut it if it doesn't fiy
  data: lw_row    type pt1002.
  data: lt_lines  type string_table,
        lw_line   type string.
  data: lt_words  type string_table,
        lw_word   type string.
  data: lt_chars  type string_table,
        lw_char   type string.
  data: lv_fits   type flag,
        lv_windex type i,
        lv_cindex type i,
        lv_exc    type c.
**-------------------------------------------------------------------- LINES start
* get lines                                                       
  perform get_lines using string_description changing lt_lines.       
* for each line
  loop at lt_lines into lw_line.
*  line fits row?
    perform is_line_fitting using lw_line lw_row changing lv_fits.
    if lv_fits = 'X'.                                                
*    add line
      perform conv_line_2_row using lw_line changing lw_row.          
    else.
**-------------------------------------------------------------------- WORDS start
*    get words                                                  
      perform get_words using lw_line changing lt_words.           
*    for each word
      do.
        add 1 to lv_windex.
        read table lt_words into lw_word index lv_windex.
        if sy-subrc <> 0 .
          exit.
        else.
*        word fits row?
          perform is_word_fitting using lw_word lw_row changing lv_fits.
          if lv_fits = 'X'.                                              
*          add word
            perform conv_word_2_row using lw_word changing lw_row.      
          else.
*          word does not fit empty row?
            if lw_row is initial.                                       
**-------------------------------------------------------------------- CHARS start
*            get chars                                           
              perform get_chars using lw_word changing lt_chars.     
*            for each char
              do.
                add 1 to lv_cindex.
                read table lt_chars into lw_char index lv_cindex.
                if sy-subrc <> 0.
                  exit.
                else.
*                char fits row?
                  perform is_char_fitting using lw_char lw_row changing lv_fits.
                  if lv_fits = 'X'.                                     
*                  add char
                    perform conv_char_2_row using lw_char changing lw_row.
                  else.
*                  char does not fit empty row?
                    if lw_row is initial.                                
*                     the smallest possible entity does not fit, this situation can't be handled, leave the application
                      lv_exc = 'X'.                                     
                      exit.
                    else.
*                    EOR - end of row
                      append lw_row to t_desc_tab.                      
                      clear lw_row.
*                    char does not fit, redo process for the char once again
                      subtract 1 from lv_cindex.                          "Format2
                    endif.
                  endif.
                endif.
              enddo.
              if lv_exc = 'X'.
                exit.
              endif.
              clear lv_cindex.
**-------------------------------------------------------------------- CHARS end
            else.
*            EOR - end of row
              append lw_row to t_desc_tab.                              
              clear lw_row.
*            word does not fit, redo process for the word once again
              subtract 1 from lv_windex.                                
            endif.
          endif.
        endif.
      enddo.
**-------------------------------------------------------------------- WORDS end
    endif.
    if lv_exc = 'X'.                                                  "Converter
      exit.
    endif.
*  EOR - end of row
    append lw_row to t_desc_tab.                                      "Converter
    clear: lw_row, lv_windex.
  endloop.
**-------------------------------------------------------------------- LINES end
 endfunction.
 form get_lines using ip_data  type string
            changing ct_lines type string_table.
 ...
endform.                    "get_lines
 form get_words using ip_data  type string
            changing ct_words type string_table.
...
endform.                    "get_words
form get_chars using ip_data  type string
            changing ct_chars type string_table.
...
endform.                    "get_chars
form is_line_fitting using is_line type string
                           is_row  type pt1002
                  changing cp_fits type flag.
 ...
endform.                    "is_line_fitting
form is_word_fitting using is_word type string
                           is_row  type pt1002
                  changing cp_fits type flag.
 ...
endform.                    "is_word_fitting
form is_char_fitting using is_char type string
                           is_row  type pt1002
                  changing cp_fits type flag.
 ...
endform.                    "is_char_fitting
 form conv_line_2_row using is_line type string
                  changing cs_row  type pt1002.
...
endform.                    "conv_line_2_row
form conv_word_2_row using is_word type string
                  changing cs_row  type pt1002.
 ...
endform.                    "conv_word_2_row
form conv_char_2_row  using is_char type string
                   changing cs_row  type pt1002.
...
endform.                    "conv_char_2_row

The flow of a converter FM is controlled with three loops and bunch of conditions. Although some separation was achieved by introducing routines, the main part isn’t state-of-the-art programming example.

Pros and cons


What if we want to:

  • reuse the same module in other application with different length restriction and different definition what is a line, a word or a char. Sure, we could distinguish them with number of parameters but how will that look like?
  • split just by words and chars not by lines. We would need to introduce ugly conditions which would check that. Assuming we would also like to have combination of lines and words, the condition might get complicated more.

Basically I would like to have a “dynamically configurable” tool. I want the configuration be done at code level, not in the DB where, I still would need to maitain the entries. As I feel frustrated seeing to much of a complicated logic at one spot I would probably want to simplify it. Hey, isn’t that a perfect situation for OO solution? Sure it is! Let’s see what objects can propose us.

Object Oriented Analysis & Design

Candidates

The first thing to do would we be looking for the candidates for the objects. Let’s create CRC cards first. Basically CRC card denotes Class Responsibility Collaboration card and serve the purpose for simplier design. It let us visualize what the candidates are responsible for and who they will interact with. CRCs are cool as they don’t require technical tools. For instance I use office yellow stickers for them. Seeing the candidates will ease seeing the proces they support by moving them on the table. Besides such candidate can be easily replaced or removed at all, if we decide it doesn’t meet our needs.

CRC Line.png

CRC Word.png

CRC Char.png


CRC Formatted Part.png

CRC Converter.png

Product.png

Patterns


With these candidates on board, let’s see if there is any common design pattern that we could use as a base solution. After googling a bit, I have found a Builder Pattern that seems suitable for our case. Let’s see how we can adapt it to our situation.

We have a Product, we have a Builder (Converter), we have also some extra candidate Formatted Part (I will call it Part for short), but this seems to be used only internally. We are missing a Director, who controls the flow and delegates job to build certain parts to concrete Builder. Here I decided that the Builder itself can serve the purpose of a Director too. Moreover, who would pay this guy for doing nothing that the rest of the team couldn’t handle itself ๐Ÿ™‚

Ok, so let’s change our Converter candidate to be called Builder. It now looks like (changes in red):

Builder alone.png

Note!

Intentionally I am not working with classes yet, just with CRC cards, so that we can easily change them if required.

We still miss one thing here. Although the Builder defines uniform approach for the Parts to be put together to a Product, it must introduce different ways of producing these Parts (its initial responsibility). I.e. building Parts will be different for Lines, Words, Chars. That means we will need different kinds of Builders to be able to decide which Part it want to use as Product parts. Quick modification to CRC card with give us below:

Builder.png

Builder now also serves the purpose of an Factory Method, where in general, concreate Builders are responsible for building concrete Parts.

Final look at candidates

A lot has happened to our candidates, so let’s take final look at what we have on the table.


Diagram v1.png

More patterns

There is one major feature emphasized here. We have a Builder who is able to build a Product based on Part type we choose. This would suffice if we wanted to split the text either to Lines or Words or Chars. But how about utilizing them all at a time. We need Lines and Words and Chars to correctly address the split process. In other words, if concrete Builder cannot process the request (cannot add the parts), we should delegate this to sub Builder which we try to process it at more detailed level. If that one fails too, we will again delegate to Sub Builder of the current sub Builder. Such chain of one task delegation can be easily implemented with variant of a Decorator Pattern .

Again we will need to adapt it, just to ease its structure. In fact all we need to add to the Builder, is a reference to its sub Builder, so that we know who we can delegate certain job to. Our candidate then gains new responsibility.

Builder v3.png

Note that this new Responsibility is a private one. From the Client perspective it should be transparent how the build is done. It is only the Builder who knows how to do the job.

Centralized control style

The candidates can defend they candidacy, so we are probably fine with turning them into classes. Anyhow, before that happens, let’s take closer look how the build process will look like (in the Builder).

Create Parts based on the Builder we choose

For each part

                Does Part fits product buffer (i.e. currently filled work area)?

                               Append to buffer

                Else

                               Does part fit empty buffer at all (i.e. empty work area)?

                                               Create Parts based on the sub Builder we have

                                                               For each Part

…we repeat the steps for the conditions and delegate the job to another sub Builder in case it cannot be processed at this level

                               Else

                                               Finalize current context (i.e. append buffer work area to output table)

                                               Flush buffer and add the part to it

Finalize the Part add (i.e. put line break at the end of part join to the output table)

The above logic reflects the procedural code I shared at the beginning. There are lots of conditions to be handled at one spot. This doesn’t look good. What we can remark here:

  • The Builder controls entire consolidation process, he is the master of a ceremony
  • It decides when to delegate the process to sub Builder if it cannot be processed at given granularity
  • Part and Product are very simple, unintelligent candidates

Tendency of one object to gather all the responsibility of an application for process control, is an example of centralized control style.  Such object usually lies at application services layer. In fact here we have some variation of this kind of control, namely a clustered control style. This means that the control is spread across the objects of same type (Builders), but they still reside at same layer of an abstraction (application).

Classes

Before we go further we would like to see some code. The abstraction we have is enough to build classes from the candidates.

Note!

I used abstract classes not interfaces because the classes share common behaviour with their implementation (sub classes). With a bit of a struggle, we could also use interfaces and delegate common behaviour to i.e. external utility classes. In this case it would however introduce extra complexity. The aim here is to show how to design classes responsibility and how these classes can collaborate to fulfill application responsibility.

Builder

class lcl_builder definition abstract.
   public section.
     methods: constructor   importing value(ir_subbuilder) type ref to lcl_builder OPTIONAL,
              build_product importing ip_text              type string
                            changing  value(cr_product)    type ref to lcl_product.
   protected section.
     methods: get_formatted_type abstract
                                 importing ip_text type string
                                 returning value(rr_formatted_type) type ref to lcl_formatted_part.
   private section.
     data: product    type ref to lcl_product,
           subbuilder type ref to lcl_builder.
     methods: add_part           importing ir_part    type ref to lcl_formatted_part,
              parse_to_formatted importing ip_text    type string
                                 returning value(rt_formatted_part) type lcl_formatted_part=>tt_formatted_parts.
 endclass.             


Let’s leave the implementation details now. We only need to know that:

  • get_formatted_type method returns instance of a Part. It is concrete type of Builder who decides what type of Part instance to return. The method is protected and abstract as it needs to be redefined in the Builder subclass. Here is where Factory Method comes into play.
  • parse_to_formatted method gets Parts instances based on given text. This one need to return the same type that above method will return in order we work only with one type of Part objects. Details will come later. 
  • build_product method is the heart of application control. This one makes all the decisions as for when and how to add the Parts to the Product.

Part


class lcl_formatted_part definition abstract.
   public section.
     types tt_formatted_parts type table of ref to lcl_formatted_part with default key.
     methods: constructor     importing ip_text type string,
              get_formatted   returning value(rp_formatted) type string,
              get_length      returning value(rp_len) type i,
              get_separator   returning value(rp_sep) type string. 
   private section.
     data formatted type string.
 endclass.              

Part class gives us following information:

  • All of its methods are public and are used purely to expose some information of its internal state
  • As with the previous class it will receive their own implementation in the subclass, i.e. the separator is different for Line and different for Word

Product

class lcl_product definition abstract.
   public section.
     methods: is_fitting_buffer     importing ir_part type ref to lcl_formatted_part
                                    returning value(rp_fits) type flag,
              is_fitting_new_buffer importing ir_part type ref to lcl_formatted_part
                                    returning value(rp_fits) type flag,
              add_to_buffer         importing ir_part type ref to lcl_formatted_part,
              add_to_new_buffer     importing ir_part type ref to lcl_formatted_part,
              finalize_part         importing ir_part type ref to lcl_formatted_part.
   protected section.
     methods  get_max_buffer_length abstract returning value(rp_len) type i.
   private section.
     data     product type table of string.
     data     buffer  type string.
 endclass.   

Here what we notice that Product:

  • Has lots of public methods which Builder will call based on decisions it makes. Basically these will be used to control process of adding the parts to the Product
  • get_max_buffer_length method decides about the output restriction (product length). Again, as in case of a Builder, the subclass which redefines this method will decide on this factor

Delegated control style

The delegated control programming style is preferred over above one in order to avoid application which consists of one big intelligent object and several “dumb” ones. Instead we will have application intelligence spread across all of them, resulting in small, decent objects that can make some decision upon themselves and the application they belong to. The idea is to split the responsibility b/w objects and get involved those from the domain application layer,

Ok. Nice words, but how to inject some intelligence to our Part and Product, taking over some of a Builder’s burden. Let’s update our CRCs.

CRC Builder v3.pngCRCR Formatted Part v2.png

CRC Product v2.png

The Builder now has transformed from the Controller to a simple Coordinator. It does not do any decision anymore, it simply gathers generated Parts (from within Part class itself) and redirects them to the Product which knows how to add them. Finally it delegates the job of finalizing part addition to the Part itself. This one knows how to finalize itself by calling appropriate method of a Product.

Let’s have a look how classes interface has changed.

class lcl_builder definition abstract friends lcl_formatted_part.
   public section.
     methods: constructor   importing value(ir_subbuilder) type ref to lcl_builder OPTIONAL,
              build_product importing ip_text              type string
                            changing  value(cr_product)    type ref to lcl_product.
   protected section.
     methods: get_formatted_type abstract
                                 importing ip_text type string
                                 returning value(rr_formatted_type) type ref to lcl_formatted_part.
   private section.
     data: product    type ref to lcl_product,
           subbuilder type ref to lcl_builder.
     methods: add_part           importing ir_part    type ref to lcl_formatted_part,
              parse_to_formatted importing ip_text    type string
                                 returning value(rt_formatted_part) type lcl_formatted_part=>tt_formatted_parts.
endclass.     

             

Builder now:

  • in parse_to_formatted method use static method of Part to generate objects. To assure type consistency it passes its reference in order that static method could determine type of Part Builder is attached to. It simply utilizies method get_formatted_type of a Builder and gets an instance of the object Builder is responsible to build. As the method is protected (we don’t want someone from the outside generate formatted parts) we must ensure Part is on friends list of the Builder. Then it is able to use its protected and private merhods.
class lcl_formatted_part definition abstract.
   public section.
     types tt_formatted_parts type table of ref to lcl_formatted_part with default key.
     methods: constructor     importing ip_text type string,
              get_formatted   returning value(rp_formatted) type string,
              get_length      returning value(rp_len) type i,
              finalize        importing ir_product type ref to lcl_product,
              get_separator   returning value(rp_sep) type string.
     class-methods: get_parts importing ir_builder type ref to lcl_builder
                                        ip_text    type string
                              returning value(rt_formatted_parts) type tt_formatted_parts.
   protected section.
     methods  parse  abstract importing ir_builder type ref to lcl_builder
                                        ip_text    type string
                              returning value(rt_formatted_parts) type tt_formatted_parts.
   private section.
     data formatted type string.
endclass.               

   

Part now:

  • introduces static get_parts method which receives a Builder instance who requested parsing
  • each its subclass provides different way of parsing input text i.e. Line needs to be split at \n, while Char needs to be analyzed char by char
  • when static method receives concrete Builder, it gets the Part with entire initial text out of it. It then delegates parsing the text to the concrete Part. This one knows how to parse it in order to generate set of objects of same type (Lines, Words or Chars)
  • was enhanced with new method finalize. Now concrete Part type knows how to finalize its addition to the Product by calling its appropriate method. This decision is no longer made in the Product. It was simply spread across subclasses of Part and the polimorphism did the job. 

class lcl_product definition abstract.
   public section.
     methods: add_part           importing ir_part type ref to lcl_formatted_part
                                 returning value(rp_added) type flag,
              finalize_part.
   protected section.
     methods  get_max_buffer_length abstract returning value(rp_len) type i.
   private section.
     data     product type table of string.
     data     buffer  type string.
     methods: is_fitting_buffer     importing ir_part type ref to lcl_formatted_part
                                    returning value(rp_fits) type flag,
              is_fitting_new_buffer importing ir_part type ref to lcl_formatted_part
                                    returning value(rp_fits) type flag,
              add_to_buffer         importing ir_part type ref to lcl_formatted_part,
              add_to_new_buffer     importing ir_part type ref to lcl_formatted_part.
endclass.              

Product now:

  • introduces method add_part which makes the decision whether and how the part should be added to current Product buffer. Only this one is called from within the Builder
  • all the methods which handle complexity of the process are now private and only above method knows how to correctly use them (defines the process)
  • gets new finalize_part method which Part could call directly from its finalize method (if finalizing this part is required)

Complete code

It’s high time to look into details. Please download the code in SAP Link format and study implementation details if you like. If not, just go to below diagram and study how messages are exchanged.

Sequence diagram

Below a pseudo UML diagram showing the flow of the application.

Diagram v2.png

As we can see now:

  • Client indicates the Product he wishes to get
  • Client create the Builders and nest them within one another as sub Builders
  • Client request to build_product to the Builder
  • Builder needs to get Parts out of non converted input text, for this it sends its reference to Part via static method get_parts
  • This Builder reference is used to get Part object which the Builder is responsible to build (method get_formatted_part)
  • Using above Part object, creation process (method parse) takes place and objects of same type are generated, representing text as set of Part objects
  • The Parts are returned to the Builder
  • The Builder iterates over returned Parts (of one type) and sends them to Product requesting add_part
  • Product does necessary internal check and makes decision if this part can be added at all (i.e. line does not fit Product restriction of 20 chars)
  • Builder receives information if add process succedded
    • If so, Part is finalized by calling it’s finalize method. Concrete Part itself decides then how to finalize its build (i.e. we want to call Products finalize_part method at the end of a Line or Word, but not necessary at the end of a Char). This way we avoid Builder to check what concrete Part type is the Part object of
    • If not, build_product is triggered against sub Builder as long as there is one available; the process repeats for new sub Builder
  • At the end of process, the Product gets returned to the Client


Test run

This is what application generates for 79 char Product – only Line Builder comes into play

Do I/we…

… collaborate?

… communicate openly ?

… share ideas?

… leverage knowledge?

… have a focus outside and inside (about customers, partners)?

For 20 char Product – both Line and Word Builders used

Do I/we…

… collaborate?

… communicate

openly ?

… share ideas?

… leverage

knowledge?

… have a focus

outside and inside

(about customers,

partners)?

For 6 char Product – utilizing Line and Word and Char Builders

Do

I/we..

.

collab

orate?

commun

icate

openly

?

share

ideas?

levera

ge

knowle

dge?

have a

focus

outsid

e and

inside

(about

custom

ers,

partne

rs)?

Drawbacks


There are no perfect solutions, neither is this one. Here are some reasons why:

  • Product methods add_part / finalize_part are made public in order Builder or Part could call them at right time. For the price of exposing Product very intrinsic details, we encapsule the decision of how Parts are added to it. As the Client has direct access to the Product, he can easily change Product’s intrenal state
  • In order Part can call protected method get_formatted_type of a Builder (to generate objects of same type Builder is responsible for), we must put Part on Product’s friends list. This makes these classes coupled. Such situation is acceptable for the objects at same hierarchy level (i.e. siblings) but here Bulider and Part are of different type, so the design “sucks” a bit
  • There is probably too much of an abstraction especially for the Product. We could delegate some of Product sub classes responsibility to the Builder i.e. getting required Product lenght. Anyhow this would contradict separation of concerns where we would mix application service layer (Builder) with a domain layer (Product)

Design Power


Modularity / Encapsulation

As we introduced clear object distinction, each object received unique responsibility. The objects by fulfilling their goals, collaborate with each other and allow to achieve more general aim.

We have clear separation b/w objects from the domain layer (Product, Part) and application services layer (Builder). Though, the Builder need to know someting about the Part, it is fine as this object it is application specific. Bad design would do the opposite – couple domain object with the application one.

By introducing delegated style, objects match their stereotype and focus on elementary tasks, rather than on huge range of tasks. We end up with simplier to read and easier to maintain application. The objects also fit more their existence purpose and are not overloaded with additional heavy responsibilities.

Reusability

Object oriented programming style gives us ability to reuse same objects in different context. We can easily use this functionality in some other application which has different Product length and Part requirements. All we do, is to use different kind of object that share common interface and polimorphism will do the job. The beauty sits in the dynamically configurable application which depends on what Client chooses. The Builder is even not aware which objects the Client has selected.

Maintainability via separation

Changing one object doesn’t affect the other one. You may i.e. change the way Product conducts “add part” process and the rest of the application will stay untouched. It will still magically work.

Flexibility

Adding new Parts is not a problem anymore. We create a class which inherits from the Formatted Part and define entity which introduces new semantic text presentation. I.e. we could ask to split by Hex digits, double char mark etc. The same applies to Product, where adding new length restriction is not a problem now. With some efford we could even request some more complex form of internal product representation.

Conclusion

Although there are some gaps in this design and we could further study it to enhance a bit, it show us how we should do an Object Oriented Analysis and Design in order to break the responsibility of the entire applicaiton into smaller, more unique units. Using delegated style let us inject some inteligence to the objects used by the application (residing at domain level). Patterns are helpful here, but don’t need to be the heart of the design. If we pick one, we should always try to adpat it to our needs and context. Using only the most valuable part of them in conjunction with adaptation to our needs, creates more reliable and custom-made applications.

Next time you have a chance, pick the Object Oriented approach and create your own solution.

Questions? Ideas? Share your thoughts please!




To report this post you need to login first.

4 Comments

You must be Logged on to comment or reply to a post.

  1. Naimesh Patel

    Hello Marcin,

    Thanks for the great post.

    I have few comments on the design:

    • Product should be built by Builder. As a client, I don’t know what is going to take to build the product. I ask give me this product and builder give me the product of the LINE, which has WORD which has a CHAR. My concrete builder should have this facility. In your LCL_BUILDER, you need to pass your CR_PRODUCT. Builder should be responsible for building this product and giving back to client. You should send what type of product is required like Paragraph or line or Word etc. Suppose you say Paragraph, it would return you a object using decorator which has chain of all the required objects.
    • Decorator Pattern – Its more kind of Composite pattern than the Decorator pattern. LINE contains one to many WORDS, WORD contains one to many CHAR. You tell your LINE object to give you its components, in turn it passes that command to WORD and WORD in turn sends that to CHAR. Of course, you only send the command to next level, when you really need to get sub string of that.
    • LCL_FORMATTED_PART should have instance method like GET_PARTS instead of the static method. As you are actually working with the information of what you have collected so far.
    • Method PARSE – should only do the splitting. Rest of the logic to get the builder should be common on LT_SPLITTED
    • PRODUCT – Generally when using a Builder, the final PRODUCT would be Façade

    Please let me know your thoughts.

    Regards,

    Naimesh Patel

    (0) 
    1. Marcin Pciak Post author

      Hello Naimesh,

      Thanks for your comments. Let me share my thoughts:

      • I was thinking of hiding the Product selection from the Client. The reason I moved this outside was to let the client pick format the Product must be created in. That is even I want my Product be created out of Paragraphs, Lines or Words, I still may require some output restiction. In more uniform approach your solution fits better, in my case I wanted to facilitate creation of different Products (one with restriction of 79 chars, one with 6 chars etc). If Part i.e. Line doesn’t fit this restirction, then it is splited into Words an so one. The Part here represent an internal entity which is used to split the text, but they are not added directly to the Product. Instead the Product itself decides how to add the Part. It may require adding some extra termination marks. In other words. Normally your solution would be preferable one, but here I want also to differiantiate b/w different Product’s format. i.e. Client requests building a Product consiting of Lines, but no more than 79 chars.
      • Here we are actually nesting Builders, not the Parts themselves. So we nest references of the Builders. Each Builder has specific knowledge of the Parts it is using, but the Part itself is not related to the next Part level at all. This causes that we don’t nest Lines -> Words -> Chars (so one to many relationships), instead we use Line Builder -> Word Builder -> Char Builder (so one to one relationships). In my opinion the Composite would come into play in the latter case. Of course here we have a variation of a Decorator, as the build_product method call of the sub object may happen, but don’t have to (i.e. Builder was able to handle text at Line level, so no need to call same method of Word sub Builder). Decorator would probably always delegate this job to its sub objects first, then continue working on its level.
      • Well this was an ugly approach I took. I didn’t want get_parts be instance method, as I wanted each instance of Part i.e. Line represent only some portion of my initial text. I just wanted any instance of Part be created only once the text is already splited. So I request to split the text and represent it in given Parts format.
      • again, I wanted my Builder to work with object of Lines, Words, Chars where each instance represent single splited entity (from LT_SPLITTED). Sure, I could just return LT_SPLITTED, but I wanted to avoid exchanging basic data like tables, structues etc. Instead I return a set of Part objects that parse method creates.
      • Facade – I did not spotted that, but it is probably as you say Sir ๐Ÿ™‚

      Regards

      Marcin

      (0) 
      1. Naimesh Patel

        Hello Marcin,

        Thanks for your reply.

        Regarding Builder – I would say, there is a some more potention in your builder design approach. As a client, you say what you want to Builder and it makes a product for you.

        Like when you order a Pizza, you ask for sause, toppings, crust, cheese etc and the Cashier passes that to cook and it would build your pizza. No matter in what sequence you gave the order of the parts, it the final product would be a good pizza – provided cook is great ๐Ÿ™‚

        For Part builders, I agree that you would need to get the individual part using the builder. You call each builder to get you a new formatted part. But collect of these parts is a Composite in nature. Means when you break a Line to words of 20 char, you would get say 5 words and 3 trailing chars. So total of 4 object collection.

        Regards,

        Naimesh Patel

        (0) 
        1. Marcin Pciak Post author

          Hello Naimesh,

          Yes, the Builder itself usually should know the process of building ready to use Product. I agree the Client here should not decide on this. Anyhow I combined a bit of a Builder pattern and Factory Method, where concrete Builder decides on what Parts it want to work with. The Abstract Builder will work with Abstract Parts though. I wanted the Client be able to decide on how it wants the split be done: Letters -> Words -> Chars is just one combination he might pick, but i.e. Letters -> Chars is acceptable too.

          I also agree on the nature of the Parts themselves.

          In my blog, I wanted to focus more on the responsibility break b/w different objects and application layers than on patterns themselves. Obviously they looked though the corner so I used them, but as you may noticed they are customized and tweaked to my fit my purpose.

          Regards

          Marcin

          (0) 

Leave a Reply