Tek-Tips is the largest IT community on the Internet today!

Members share and learn making Tek-Tips Forums the best source of peer-reviewed technical information on the Internet!

  • Congratulations gkittelson on being selected by the Tek-Tips community for having the most helpful posts in the forums last week. Way to Go!

Builder Pattern

Status
Not open for further replies.

sepiaRabbit

Programmer
Jul 18, 2017
4
US
Hey guys,
I am pretty new to Foxpro and I was trying to make a simple class that implements the builder pattern. Everything seems to work fine except when I try to chain methods together. Is this something that is not supported by Foxpro? or maybe I am missing something?

Here is an example of where my program errors
Code:
sandwich_builder.set_lettuce().set_tomatoes().set_meat("turkey")

Thanks!
 
Not real familiar with this, but have you tried it this way:

WITH This.SandWichbulder
.Set_Lettus()
.Set_tomato()
.Set_meat = "Turkey"
ENDWITH


Best Regards,
Scott
MIET, MASHRAE, CDCP, CDCS, CDCE, CTDC, CTIA, ATS

"Everything should be made as simple as possible, and no simpler."[hammer]
 
That did work, kind of. I guess the end goal was to put this in as a parameter when a sandwich object is created and Foxpro didn't like when I used the with statement in there. anyway around that?
 
You will only be able to chain methods, if they return an object, which has the "chained" method.

Can you give a reference of what you think of in any other language?

Bye, Olaf.

 
Yup each method returns "this", so it should return the current object. It is kind of weird because I can set the result of one method to new builder object, but cannot chain them.

Here is a pretty close example of what I am looking for written in java that I found.

Code:
public class User {
  private final String firstName; // required
  private final String lastName; // required
  private final int age; // optional
  private final String phone; // optional
  private final String address; // optional

  private User(UserBuilder builder) {
    this.firstName = builder.firstName;
    this.lastName = builder.lastName;
    this.age = builder.age;
    this.phone = builder.phone;
    this.address = builder.address;
  }

  public String getFirstName() {
    return firstName;
  }

  public String getLastName() {
    return lastName;
  }

  public int getAge() {
    return age;
  }

  public String getPhone() {
    return phone;
  }

  public String getAddress() {
    return address;
  }

  public static class UserBuilder {
    private final String firstName;
    private final String lastName;
    private int age;
    private String phone;
    private String address;

    public UserBuilder(String firstName, String lastName) {
      this.firstName = firstName;
      this.lastName = lastName;
    }

    public UserBuilder age(int age) {
      this.age = age;
      return this;
    }

    public UserBuilder phone(String phone) {
      this.phone = phone;
      return this;
    }

    public UserBuilder address(String address) {
      this.address = address;
      return this;
    }

    public User build() {
      return new User(this);
    }

  }
}
 
Hi Olfaf, unless I've overlooked something all this time, no object method chaining is allowed in vfp.. It would be awesome if we could create fluent interfaces. I was interested on bringing some "Jquery" to vfp. Are you sure it can be done? for example:

Code:
clear
oo = createobject('test')

? ' this works:'
ii =  oo.forms()
ii.printnames()

? 'this ( sadly ) does not:'

try
	oo.forms().printnames()
catch
	? 'fail ',message()
endtry

define class test as custom

	retobject = .f.


	function printnames

	for each form in this.retobject.forms
		? form.name
	endfor



	function forms

	this.retobject = createobject('empty')
	addproperty(this.retobject,'forms(1)')

	dimension this.retobject.forms(_vfp.forms.count)

	for x = 1 to _vfp.forms.count

		this.retobject.forms(m.x) = _vfp.forms.item(m.x)

	endfor

	return this && or any other object...

enddefine

Marco Plaza
@vfp2nofox
 
Ok, darn looks like it is not possible. Thank you though for all of your help guys!
 
You can do something like that in VFP, but it's never occurred to me as a helpful pattern. From what I read about it, its main intent is to prevent, that an increase of object constructor parameter combination leads to an exponential list of constructors. That can't happen in VFP, as every VFP class only has one constructor called INIT. You only have one parameterization of the init, anyway.

What you can do in VFP to create a user from data for example is this:
Code:
* placeholder for a table with user data:
Create Cursor crsUsers (cFirstname V(30), cLastname V(30), dBirthdate D)
Insert Into crsUsers Values ("Sepia","Rabbit",{^1950-12-31})

* Direct data access:
? "directly from the table"
? crsUsers.cFirstname
? crsUsers.cLastname

* Simple user "Type" object usage:
? "scattered to a simple 'type' object"
Scatter Name poUser
? poUser.cFirstname
? poUser.cLastname

* User class getting its properties set at init from a poUser object:
? "instance of an 'intelligent' class"
Local loUser
loUser = Createobject("cUser","crsUsers")
? loUser.getFirstname()
? loUser.getLastname()
? loUser.getAge()

Define Class cUser As Custom && VFP specific, you can't define a root class, any class must be based on a native VFP class, "custom" is for custom self made classes.
   Protected cFirstname as String
   Protected cLastname  as String
   Protected dBirthdate as Date

   Procedure Init()
      Lparameters tcWorkareaAlias
      Select (tcWorkareaAlias)
      Scatter Name This Additive&& setting object properties via Scattering equally named fields of a table
   Endproc

   Procedure getFirstname()
      Return This.cFirstname
   Endproc

   Procedure getLastname()
      Return This.cLastname
   Endproc

   Procedure getAge()
      LOCAL lnYears
      lnYears = Year(Date())-Year(This.dBirthdate)
      Return lnYears-IIF(GOMONTH(This.dBirthdate,12*lnYears)>DATE(),1,0)
   Endproc
Enddefine

You know, VFP is a very data centric language, it works best if you don't create the impedance mismatch objects are to data. The first few lines already show how you can get at data in the simplest way I know in any programming language, scatter is legacy code you'd not use today, though scatter name is a variant introduced in VFP9 to be able to create simply type/struct like object with just the properties the table has as fields, you can also use that to set fields/properties of an object as shown in the init code here.

Passing an object to a constructor, which has its fields set to the values you want to set the fields of the new object instance to looks very odd to me and most probably to any VFP developer. You also fail on another aspect before anything else: VFP has no static classes, no static scope at all.

Bye, Olaf.
 
Marco, you are coming up with something, that's even more complicated than asked.

Code:
Local loUser
loUser = Createobject("cUser")
* Fails: loUser.setFirstname("Marco").setLastname("Plaza")
* Works:
WITH loUser
   WITH .setFirstname("Marco")
      .setLastname("Plaza")
   ENDWITH
   ? .getFirstname()
   ? .getLastname()
ENDWITH 

Define Class cUser As Custom
   Protected cFirstname As String
   Protected cLastname  As String

   Procedure setFirstname()
      Lparameters tcFirstname
      This.cFirstname = tcFirstname
      Return This
   Endproc

   Procedure setLastname()
      Lparameters tcLastname
      This.cLastname = tcLastname
      Return This
   Endproc

   Procedure getFirstname()
      Return This.cFirstname
   Endproc

   Procedure getLastname()
      Return This.cLastname
   Endproc
Enddefine

Scotts idea wasn't that bad, but you have to nest WITH...ENDWITH to work with the anonymous object reference you get back, you can't chain calls directly, even if the class methods return THIS, the VFP compiler doesn't understand, that you want to call a method of the returned object. So you got to take kind of pitstops of saying you want to do something WITH the returned object. This most probably is related to the fact VFP is not strictly typed and you can't infer what happens at runtime in every way you can in a strictly typed language.

It is obviously much simpler to do the following:

Code:
Local loUser
loUser = Createobject("cUser")
WITH loUser
   .setFirstname("Marco")
   .setLastname("Plaza")
   ? .getFirstname()
   ? .getLastname()
ENDWITH 

Define Class cUser As Custom
   Protected cFirstname As String
   Protected cLastname  As String

   Procedure setFirstname()
      Lparameters tcFirstname
      This.cFirstname = tcFirstname
   Endproc

   Procedure setLastname()
      Lparameters tcLastname
      This.cLastname = tcLastname
   Endproc

   Procedure getFirstname()
      Return This.cFirstname
   Endproc

   Procedure getLastname()
      Return This.cLastname
   Endproc
Enddefine

Not chain methods, simply call one after the other.

Bye, Olaf.
 


Hi Olaf, that's what I meant. There's no way to chain object methods in vfp.


Marco Plaza
@vfp2nofox
 
If you don't accept WITH...ENDWITH nesting, then that's it. But the more important lack of VFP is about static classes, you can't define a construct like a builder existing statically.

You don't need that concept because VFP does not offer multiple constructors with different parameterization, you may define an Init with parameters having different meaning depending on the count and type, as you can't strictly type parameters anyway, so you can pass 1,2 and "one","two" to an Init event anyway and make the meaning of the parameters depend on count and type you detect via Pcount() and Vartype() and thus have "multiple" constructors. It surely has limits in comparison with real override.

You can take the main idea of this and define an init having one object parameter, which accepts objects with individual sets of properties, the closest to that I did here:
On the aspect of the builder to create an object which finally is passed to User constructor that enables you to define any properties with types and values you need. You may extend this to be part of a class or helper (builder) class to create such parameter objects to then pass into the main class Init. One property of such a parameter object might define what constructor variant to do with itself as some meta data. Defining an infrastructure for such ways could make VFP classes more flexible, too. Though you also lose a consistency and coherence, if all your Inits are just having a builder object input parameter.

That may still not get you what you want, then explain in more detail how and why you would like to use that pattern for which goal.

Last, not least I often enough tell the days of VFP are over and other languages offer much more than VFP ever will. If you need VFP for some reason, you can't expect to impose your programming style upon it, you have to learn a way of limited OOP VFP supports. It surely is much more than Access with its class modules, but less than Java or C#. But it also makes it easier to comprehend and dive into fully to the extent it works. So far, you can get your user object with whatever fields or (let's keep it at VFP terminology) properties as you like, the base custom class and almost any class also offer AddProperty to add new properties at runtime, you also can add properties via AddProperty(object,"PropertyName", value) function, which you already have seen, when you read the linked thread with my CreateParameterObject function definition.

Bye, Olaf.
 

Hi Olaf,
Don't get too complicated. It's not that the fluent interface solves a particular problem for vfp ( with-endwith serves the same purpose ) I was interested on it because I thought it would be helpful to replicate some functionality found in other frameworks and libraries like Angular and Jquery , and implement using the same interface, so no new documentation would be needed. As a said you made me doubt about the impossibility to do it when you stated that it was possible to chain object methods... I wanted to know if there was a way, and there is none - at least not using pure vfp... wich makes me think that a Transpiler is definitely the tool VFP needs to evolve.



Marco Plaza
@vfp2nofox
 
Marco Plaza said:
Don't get too complicated.

Well, I took the idea a bit further already years ago and used Parameter Objects to enable the concept of default values and named parameters. When you let a class create a parameter object you can obviously set obj.property2, obj.property6 and thus a) only set some parameter values, b) let the method creating this parameter object set default values, and c) set the parameter values by using their names.

This is not in the direction of what you need, but still was very useful as code documenting itself better than by normal parameterization especially in cases lot of parameters could be used and many of them were boolean.

Like many things, this is a complication in one aspect to win one or several advantages in other aspects. You only get to local optimums, if you never climb a hill to get to a greener valley.

You don't get too concrete, but so you're saying if I would like to implement some AngularJ aspects in VFP I would need that chaining concept?

Bye, Olaf.
 

Well.. is not that you need it.. But it would be more convenient if we can use vfp to get js/jquery concepts in practice, and have a same way to write similar code.


Marco Plaza
@vfp2nofox
 
Would faq184-4257 help?
In short, the MSScriptcontrol.scriptcontrol

Bye, Olaf.
 
I though about it too as a possible solution.. but did not wanted to mention until doing some tests. I'll see if I can make it work and share the results. - The only thing that bothers me is the obscure support of the scriptcontrol, though I have it working in W10-64 creators update with no problem.





Marco Plaza
@vfp2nofox
 
Well, I got it working. Using the next ( basic ) technique, we can create fluent interfaces using vfp and the ms Script Control (I have very interesting uses for this I'll try to share soon ;-) ):

Code:
* Marco Plaza, 2017 - @vfp2nofox
* fluent interface using msScriptControl

clear
oo = createobject('test')

? ' this works:'
ii =  oo.forms()
? ii.names()

? 'pure vfp: this ( sadly ) does not:'

try
	? 	oo.forms().names()
catch
	? 'fail ',message()
endtry

* now using a mirror Js Object: 

sc = createobject("MSScriptControl.ScriptControl")

ojs = jsProxy( m.sc, m.oo )

? "Hey I'm working:"

? ojs.forms('').names('') && must pass at least one parameter, no matter if our function does not need it.- now using '' .


**************************************
define class test as custom
**************************************
	retobject = .f.

function names
	local rn
	rn = ''
	for each form in this.retobject.forms
		rn = m.rn+ form.name+','
	endfor

	return rtrim(m.rn,1,',')

function forms

	this.retobject = createobject('empty')
	addproperty(this.retobject,'forms(1)')

	dimension this.retobject.forms(_vfp.forms.count)

	for x = 1 to _vfp.forms.count

		this.retobject.forms(m.x) = _vfp.forms.item(m.x)

	endfor

	return this && or any other object...

enddefine
****************************************************

*------------------------------------------------
function jsProxy( sc, vfpobject )
*-----------------------------------------------

text to js noshow

function jsProxy(vfpObject) {

     return { 
     forms: function () { vfpObject.forms(); return this; },
     names: function () { return vfpObject.names; }
     }
}

ENDTEXT

with sc as msscriptcontrol.scriptcontrol

	.language = "JScript"
	.addcode( m.js )
	ojs = .run( 'jsProxy',m.vfpobject)

endwith

return m.ojs



Marco Plaza
@vfp2nofox
 
Doesn't work here:
jscript_ayjgyh.png

My thought rather is to do the method chaining within JS.

On the VFP side, you can do without ii as in
Code:
WITH  oo.forms()
   ? .names()
ENDWITH
It doesn't make it much better, but you don't need to invent any variable names. Transpiling might be an idea, preprocessing something like oo.forms()->names() to WITH..ENDWITH nesting. Using -> is making use of something, which is supported by VFP instead of dot, so the normal inline compilation will not mark that as error, but since -> is not used we can make a difference of this and translate it to WITH..ENDWITH nesting.

A project hook BeforeBuild can be used to do this and revert to the original code in AfterBuild, too.

Bye, Olaf.
 

Hi Olaf, it was a minor bug ( My interactive test worked fine because I had a live scriptcontrol reference ) You can try it now.



Marco Plaza
@vfp2nofox
 
OK, and that one parameter necessary is a flaw familiar, I also don't know another solution to that.

So the way it works is your object now is not a native VFP object but a COM object. Tricky.

Bye, Olaf.
 
Status
Not open for further replies.

Part and Inventory Search

Sponsor

Back
Top