Παρασκευή 10 Ιουλίου 2020

Factory Pattern (OOP)

The factory pattern use a function to create a specific object from an id.


\\ Factory Pattern I
      \\ creates objects without exposing the instantiation logic to the client.
      \\ refers to the newly created object through a common interface
class Product {

}
class OneProduct as Product {

}
class AnotherProduct as Product {

}
Group ProductFactory {
      Enum KnownProducts {ID1=101, ID2=102}
      Function createProduct {
          Try Ok {
                Read id as .KnownProducts
                if id=ID1 then
                      =OneProduct()
                Else.if id=ID2 Then
                      =AnotherProduct()
                End if
          }
          if Not Ok Then =Pointer() ' Null object
      }
}
P1=ProductFactory.createProduct(101)
Print P1 is type Product
Print P1 is type OneProduct
P2=ProductFactory.createProduct(ProductFactory.ID2)
Print P2 is type Product
Print P2 is type AnotherProduct
P3=ProductFactory.createProduct(0)
Print P3 is type Null

Another way is by registering to Factory any number of classes. One way is by using function references, which are code (anonymous functions) from a function.
The list m_RegisteredProducts save strings, but each string is a function. How this can happen. A string contained this "{=number**2}" has an Anonymous function. Print Function("{=number**2}", 2)  show 4, because 2 send to function's stack, number get 2, and 2**2 return 4. So when we place a function reference, we place an anonymous function. For this reason if we want recursion for anonymous function we use Lambda() or Lambda$() for string return type function.
A Class is a function, So if we do this Report &Product() we render as text the code of function to console. We use Report because this handle text (using word wrapping) and also has a page mode which display 3/4 of consols rows, and stop for reading then we press space or mouse button to get the next page.
\\ Factory Pattern II
\\ need to be global
Global Group ProductFactory {
Private:
        m_RegisteredProducts=List
Public:
      \\  we get a reference of a class but we make it a string
      \\ because a reference of a function is the code of function
      Module Register (id$, func$) {
            id$=lcase$(id$)
            if exist(.m_RegisteredProducts, id$) then Error "Id used"
            Append .m_RegisteredProducts, id$:=func$
      }
      Function createProduct(id$) {
            id$=lcase$(id$)
            if exist(.m_RegisteredProducts, id$) then
                  Push eval$(.m_RegisteredProducts)
                  Read &Class()
                  =Class()
            Else
                  =Pointer() ' Null object
            End if
      }
}
Module SetUp {
      class Product {
            
      }
      class OneProduct as Product {
            
      }
      class AnotherProduct as Product {
            
      }
      ProductFactory.Register "101", &OneProduct()
      ProductFactory.Register "102", &AnotherProduct()
}
\\ call setup to register classes to ProductFactory
\\ original classes definitions erased
Setup
Class Product {
Class:
      Module Product(id$) {
            This<=ProductFactory.createProduct(id$)
      }
}
P1=Product("101")
Print P1 is type Product
Print P1 is type OneProduct
P2=Product("102")
Print P2 is type Product
Print P2 is type AnotherProduct

Using Lambda functions to the list m_RegisterProducts. Classes are global, as long as the module or function where created is running these classes exist. After the end of run these classes erased. So the function part of a lambda has to run when the classes exist.
A lambda is a variable and a function. We can pass the function or the variable by reference or the variable by value. Here we pass the variable by value.
\\ Factory Pattern III - Using Lambda Functions

class Product {
      
}
class OneProduct as Product {
      
}
class AnotherProduct as Product {
      
}
\\ if the OneProduct() and AnotherProduct() erased
\\ then these lambda can't call them
OneProduct=lambda ->OneProduct()
AnotherProduct=lambda ->AnotherProduct()
Group ProductFactory {
Private:
        m_RegisteredProducts=List
Public:
      \\  we get a reference of a class but we make it a string
      \\ because a reference of a function is the code of function
      Module Register (id$, b as lambda) {
            id$=lcase$(id$)
            if exist(.m_RegisteredProducts, id$) then Error "Id used"
            Append .m_RegisteredProducts, id$:=b
      }
      Function createProduct(id$) {
            id$=lcase$(id$)
            if exist(.m_RegisteredProducts, id$) then
                  \\ =.m_RegisteredProducts(id$)() is the same as these two lines:
                  Class=eval(.m_RegisteredProducts)
                  =Class()
            Else
                  =Pointer() ' Null object
            End if
      }
}
ProductFactory.Register "101", OneProduct
ProductFactory.Register "102", AnotherProduct
P1=ProductFactory.createProduct("101")
Print P1 is type Product
Print P1 is type OneProduct
P2=ProductFactory.createProduct("102")
Print P2 is type Product
Print P2 is type AnotherProduct


Another way. We use two Groups, OneProduct and AnotherProduct. We want to pass these groups in the Factory  Registry. Because we use the Value member we have to use Group() to get a copy of group. So we pass copies of  Groups, OneProduct and AnotherProduct. Each group has own class and use it when we return the value from group. We use parameters for values, one parameter, and we pass it to class constructor, as .serialnum, a private variable inherit from Product.

class Product {
Private:
      serialnum
}
Group OneProduct {
      class OneProduct as Product {
      class:
                 Module OneProduct (.serialnum) {
                        Print "One Product,"+Str$(.serialnum)
                 }
      }
      Value (s) {
            =.OneProduct(s)
      }      
}
Group AnotherProduct {
      class AnotherProduct as Product {
      class:
                 Module AnotherProduct (.serialnum) {
                        Print "Another Product,"+Str$(.serialnum)
                 }
      }
      Value (s) {
             =.AnotherProduct(s)
      }
}
Group ProductFactory {
Private:
        m_RegisteredProducts=List
        serialnum=10001
Public:
      Module Register (id$, b as group) {
            id$=lcase$(id$)
            if exist(.m_RegisteredProducts, id$) then Error "Id used"
            Append .m_RegisteredProducts, id$:=Group(b)
      }
      Function createProduct(id$) {
            id$=lcase$(id$)
            if exist(.m_RegisteredProducts, id$) then
                  \\ =.m_RegisteredProducts(id$)() is the same as these two lines:
                  Class=eval(.m_RegisteredProducts)
                  =Class(.serialnum)
                  .serialnum++
            Else
                  =Pointer() ' Null object
            End if
      }
}
ProductFactory.Register "101", Group(OneProduct)
ProductFactory.Register "102", Group(AnotherProduct)
P1=ProductFactory.createProduct("101")
Print P1 is type Product
Print P1 is type OneProduct
P2=ProductFactory.createProduct("102")
Print P2 is type Product
Print P2 is type AnotherProduct


Without using classes. We use Type:NameOfType [, Nameoftype2...] for setting a type. We use the with operator Group1 with Group2 which merge and return the merging of two or more groups. We use globser group like a lambda function

Global globser=Lambda sernum=10001 (s$) ->{
      Print s$+","+Str$(sernum)
      =sernum
      sernum++      
}

So this is the final version:

Global Group globser{
Private:      
      sernum=10001
Public:
      Value (s$){
                        Print s$+","+Str$(.sernum)
                        =.sernum
                        .sernum++
      }
}
Global Group Product {
Type: Product
}
Group OneProduct {
      Function Class {
                  Group OneProduct {
                  Type: OneProduct
                        Final serialnum=globser("One Product")
                  }
                  =OneProduct with Product
      }      
}
Group AnotherProduct {
     Function Class {
                  Group AnotherProduct {
                  Type: AnotherProduct
                        Final serialnum=globser("Another Product")
                  }
                  =AnotherProduct with Product
      }      
}
Group ProductFactory {
Private:
        m_RegisteredProducts=List
Public:
      Module Register (id$, b as group) {
            id$=lcase$(id$)
            if exist(.m_RegisteredProducts, id$) then Error "Id used"
            Append .m_RegisteredProducts, id$:=Group(b)
      }
      Function createProduct(id$) {
            id$=lcase$(id$)
            if exist(.m_RegisteredProducts, id$) then
                  Class=eval(.m_RegisteredProducts)
                 =Class.Class()
            Else
                  =Pointer() ' Null object
            End if
      }
}
ProductFactory.Register "101", Group(OneProduct)
ProductFactory.Register "102", Group(AnotherProduct)
P1=ProductFactory.createProduct("101")
Print P1 is type Product
Print P1 is type OneProduct
P2=ProductFactory.createProduct("102")
Print P2 is type Product
Print P2 is type AnotherProduct




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

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

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