Κυριακή 12 Ιουλίου 2020

Builder (OOP)

Another pattern for OOP is the Builder pattern. See how it works:

We have an abstract class TextConverter, a class ASCIItext (has a string name as ASCIIText$ because has a return value of a string), and a class ASCIIConverter which inherits from TextConverter and has a composed object, as ASCIIText$. The AsciiConverter handle the ASCIIText$ as a TextConverter using final methods for convertCharacter and for convertParagraph (which these consist a TextConverter).

The second part has a Class Document, which is the input object for applying a conversion (we have a simple class for this example). This Document supposed is an rtf coded document. So we need an RTFReader class to make a proper object to parse the rtf document, with a reference or pointer (as we see in two variants next) to a Builder class object.

The Builder is the ASCIIConverter. The RTFRead has no clue what the builder does with the components of Document. So the Builder has a state, an ASCIIText object, which hold the final converted text. We need to pass the Builder and fill it with data, then after the parsing we use Builder to get the result.


class TextConverter {
      module convertCharacter (c) {
            Error "Abstract module "+module.name$
      }
      module convertParagraph {
            Error "Abstract module "+module.name$            
      }
}
class ASCIIText$ {
Private:
      val$=""
Public:
      module append (c$) {
            .val$+=c$
      }
      Value {
            =.val$
      }
}
class ASCIIConverter as TextConverter {
      \\ Inner object
Private:
      ASCIIText$ asciiTextObj
Public:
      Module final convertCharacter(c)       {
            .asciiTextObj.append Chr$(c)
      }
      module final convertParagraph {
            .asciiTextObj.append {
            }   ' new line
      }
      Function GetResult$ {
            =.asciiTextObj$
      }
}
class Document{
private:      
      EndToken=0
      teststring$="ccpcc", cursor=1
Public:      
      function getNextToken() {
            if .cursor>len(.teststring$) then
                  =.EndToken
            else
                  =asc(mid$(.teststring$, .cursor, 1))
                  .cursor++
            end if
      }
}
class RTFReader{
Private:
      builder$
Public:
      module parseRTF (doc as Document) {
            enum marks {
                  EOF=0 'Delimitor for End of File
                  CHAR=Asc("c")
                  PARA=Asc("p")
            }
            Def t as long
            \\ This.Builder$ is a weak reference
            \\ For object {} can be used as For weakreference$ { } too
            For .Builder$ {
                  t=doc.getNextToken()
                  if t=EOF then exit else loop
                  Select case t
                  case CHAR
                        .convertCharacter t
                  case PARA
                        .convertParagraph
                  End Select
            }
            Print "Parsing End"
      }
Class:
      Module RTFReader(.Builder$) {
            \\ just a check if the weak reference is good and for the specific type
            Link weak .Builder$ to Builder
            if not Builder is type TextConverter Then Error "Need a TextConverter"
      }
}
Class Client {
      module createASCIIText(doc as Document){
            asciiBuilder=ASCIIConverter()
            rtfReader = RTFReader(&asciiBuilder)
            rtfReader.parseRTF doc
            asciiText$ = asciiBuilder.GetResult$()
            Report asciiText$
      }
}
client=Client()
doc=Document()
client.createASCIIText doc
Print "This is an example of Builder Pattern"

We can change the way RTFReader register the asciiBuilder, using a pointer not a weak reference. We need to change two classes. First we use a private member builder with initial value a null pointer (a pointer to a Null class object). In the constructor of RTFReader we check if we get a proper pointer checking that it is a TextConverter. So now we have a pointer to specific type.


class RTFReader{
Private:
      builder=Pointer()
Public:
      module parseRTF (doc as Document) {
            enum marks {
                  EOF=0 'Delimitor for End of File
                  CHAR=Asc("c")
                  PARA=Asc("p")
            }
            Def t as long
            \\ using a pointer to TextConverter
            For .Builder {
                  t=doc.getNextToken()
                  if t=EOF then exit else loop
                  Select case t
                  case CHAR
                        .convertCharacter t
                  case PARA
                        .convertParagraph
                  End Select
            }
            Print "Parsing End"
      }
Class:
      Module RTFReader(obj as *TextConverter){
      .builder<=obj
      }
}
Class Client {
      module createASCIIText(doc as Document){
            asciiBuilder->ASCIIConverter()
            rtfReader = RTFReader(asciiBuilder)
            rtfReader.parseRTF doc
            asciiText$ = asciiBuilder=>GetResult$()
            Report asciiText$
      }
}


Or using a pointer to a named group (a named group is  like a static group in other languages), which is a reference inside: Notice that asciiBuilder now isn't a pointer. When we make RTFReader we pass a pointer to asciiBuilder. In previous example asciiBuilder  was a pointer of the object which AsciiConverter() made. Then we pass the pointer as a copy of asciiBuilder pointer. Look the asciiBuilder=>GetResult$(), the use of => means that asciiBuilder is pointer. Look the final example, we use asciiBuilder.GetResult$() with a dot, because asciiBuilder isn't a pointer.

So now check the For  obj {} in all variants including the original. The three variants are the same; This is because the For statement works with named groups. A group by a pointer attach to system as a named group.inside For. So we use dot not the arrow  =>.



class RTFReader{
Private:
      builder=Pointer()
Public:
      module parseRTF (doc as Document) {
            enum marks {
                  EOF=0 'Delimitor for End of File
                  CHAR=Asc("c")
                  PARA=Asc("p")
            }
            Def t as long
            \\ using a pointer to TextConverter
            For .Builder {
                  t=doc.getNextToken()
                  if t=EOF then exit else loop
                  Select case t
                  case CHAR
                        .convertCharacter t
                  case PARA
                        .convertParagraph
                  End Select
            }
            Print "Parsing End"
      }
Class:
      Module RTFReader(obj as pointer){
            If not obj is type TextConverter Then Error "Need a TextConverter"
            .builder<=obj
      }
}
Class Client {
      module createASCIIText(doc as Document){
            asciiBuilder=ASCIIConverter()
            rtfReader = RTFReader(Pointer(asciiBuilder))
            rtfReader.parseRTF doc
            asciiText$ = asciiBuilder.GetResult$()
            Report asciiText$
      }
}





Δεν υπάρχουν σχόλια:

Δημοσίευση σχολίου

You can feel free to write any suggestion, or idea on the subject.