
This is a Servoy Tutorial that discusses the factory design pattern and how to build one in Servoy with object-oriented programming. This is an advanced Servoy tutorial, so you may want to familiarize yourself with the other object-oriented Servoy tutorials at this site before reading this one.
The purpose of the factory design pattern is to simplify the repeated creation of similar objects that extend a common constructor. The factory created objects are inheriting from the same parent object and adding specialized properties and methods for a specific subclass.
Let’s imagine for a moment that you need to build a CRM solution which will track various types of contacts; sales reps, customers, employees, etc. Are you going to create a spaghetti of functions to handle all the different properties and methods that each type of contact will need? No, of course not, you already know from my other tutorials that you need to create maintainable and extendable code, and using object-oriented programming is the best way to ensure that.
If you think about it, what you want to do is create a base object, that contains all the properties and methods common for all contact types (I will call this Person in the code), like first name, last name, full name, etc. Of course in your real-world CRM solution, you would have more properties and countless methods as well, but let’s keep it simple, right? You would then want to create another object that inherits everything from the base, but extends it, maybe to meet the specialized needs of the sales rep, including commission percent, sales territory, sales target, etc. A different contact type, like a customer, might again need to track things like annual sales, customer code, etc. By extending from the base object (inheritance), you can quickly build the new specialized objects, and there is only one place where you need to maintain the common properties and methods, and only one place you need to maintain the subclass properties and methods. Does that make sense? Your code will be elegant, maintainable, and easily extended for those inevitable feature requests.
Okay, let’s walk through an example of how to build a factory in Servoy. The first step is to create the base constructor. This is the parent object that all subclasses will inherit from, so it should contain all your “common” properties. This is also the “Servoy” way to build the constructor complete with the necessary JSDoc tags. I limited the number of properties used here, so we don’t lose anyone.
/** * Parent Constructor * @constructor * @extends {personProto} * * @param {String} first * @param {String} last * @param {String} sal * @param {String} type */ function Person(first, last, sal, type) { this.first = first; this.last = last; this.sal = sal; this.type = type; }
The prototype is where I will put all my “common” methods. I am putting them on a prototype so that only one occurrence of them exists, regardless of how many subclass objects are created from this parent object. This conserves memory and is only necessary if you are really going to create a lot of subclass objects from the parent. In this prototype, I just created a few simple methods to demonstrate the idea. Again, you would probably have a very large library of common methods in the base prototype for an actual CRM implementation.
/** * The prototype methods for the parent constructor */ function personProto(){ this.fullName = function(){ return this.first + " " + this.last; }; this.salFullName = function(){ return this.sal + this.fullName(); }; this.salFullNameType = function(){ return this.salFullName() + " is a " + this.type; } }
Then we wave our magic wand and initialize the prototype by wrapping the prototype in a self-invoked function and assigning it to an arbitrary variable so it is held in memory for us. We have to do this because Servoy does not interpret .js files the same way a browser does, so we have to force it to execute our code by evaluating the variable assignment when it loads the .js file. All we are really doing here is assigning the prototype (personProto) to the prototype chain of Person, and then resetting the constructor of the prototype to Person for good measure.
/** * Initialize the prototype */ var initPerson = (function(){ Person.prototype = new personProto(); Person.prototype.constructor = Person; }())
We are now going to take it to the next step, and introduce the factory method. It’s probably easiest to see where we are going by looking at how it will be used. To create a subclass from Person, like for a sales rep, we are going to use code like this:
var Rep1 = Person.factory('SalesRep', oValues);
You can see that we are going to call a factory method on the parent Person object, and pass it a string that will represent the type of subclass we want created. We are also going to pass it an object of values to set our properties, both on the parent and our subclass, all in one step.
We are going to create two subclasses in this factory example; Person.Client and Person.SalesRep. However, before we do that, I should let you know that Servoy will not offer code completion if we use the “.” notation in a JSDoc @type tag. So, to get code completion, we are going to declare two vars that will reference our subclasses, which we will then use for the @type declarations.
var oSalesRep = {}; var oClient = {};
The factory method shown below has also been wrapped in a self-invoked function and assigned to an arbitrary value so that it is evaluated, loaded into memory, and available for us to use in Servoy. Let’s walk through what the factory method is doing step by step:
- Line 9: The start of the factory method which receives the type of subclass that will be created, as well as an object of values.
- Line 10: A check is done to see if the type passed in is a valid subclass. If no such subclass type has been defined, it will return an error object. Normally, this should fire an error event so central error handling can deal with the bad subclass attempt; refer to my Servoy tutorial on Event Driven Architecture to see how to do this.
- Line 19: A check is done to see if the parent object has been inherited, and ensures it is only done once. When it inherits from the parent, you can see that the object of values is passed in to set the parent properties in this step. You could pass in the entire oValues object rather than individual parameters if you wish, as shown in the next bullet point.
- Line 24: Creates the new subclass and passes in the oValues object.
- Line 26: Returns the new subclass.
- Line 31: Start of the Client subclass definition. Notice that we assign it to our oClient var for code completion.
- Line 44: Start of the SalesRep subclass definition. Notice that we assign it to our oSalesRep var for code completion.
/** * The factory */ var initPersonFactory = (function(){ Person.factory = function(type, oValues){ var constr = type, newPerson; if (typeof Person[constr] !== "function"){ return{ name: "Error", message: constr + " is not available" }; } // Now that we know the constructor exists, // inherit the parent but only once if (typeof Person[constr].prototype.fullName !== "function"){ Person[constr].prototype = new Person(oValues.first, oValues.last, oValues.sal, oValues.type); } // Create the new instance newPerson = new Person[constr](oValues); return newPerson; } // Here we define specific Person factories oClient = Person.Client = function(oValues){ this.code = oValues.code; this.sales = oValues.sales; this.company = oValues.company; this.info = function(){ return "Contact: \t" + this.salFullName() + "\n" + "Type: \t\t" + this.type + "\n" + "Company Name: \t" + this.company + "\n" + "Account Code: \t" + this.code + "\n" + "Annual Sales($): " + this.sales; } } oSalesRep = Person.SalesRep = function(oValues){ this.territory = oValues.territory; this.salesTarget = oValues.salesTarget; this.commPrcnt = oValues.commPrcnt; this.info = function(){ return this.salFullNameType() + " and is responsible for " + this.salesTarget + " from the " + this.territory + " with commission = " + this.commPrcnt + "%"; } } }())
As you can see, it’s actually very easy to do, once you know the syntax (which I just gave you). I think you probably can also see how easy it would be to extend by adding more subclasses, properties, and methods.
Finally, we can test our factory and see how it works. Shown below is all that is needed to create the sales rep and customer subclasses.
/** * Testing the factory */ function useFactoryMethod() { var oValues = { first: "Gary", last: "Dotzlaw", sal: "Mr.", type: "SalesRep", territory: "MidWest", salesTarget: 500000, commPrcnt: 25 } // Create a Rep var Rep1 = Person.factory('SalesRep', oValues); application.output(Rep1.info()); // no build marker // Mr.Gary Dotzlaw is a SalesRep and is responsible for 500000 from the MidWest with commission = 25% // Change some settings for a client oValues = { first: "Bill", last: "Smith", sal: "Mr.", type: "Client", company: "ABC Company", code: "A12345", sales: 100000 } // Create the client var Client1 = Person.factory("Client", oValues); application.output(Client1.info()); // no build marker // Contact: Mr.Bill Smith // Type: Client // Company Name: ABC Company // Account Code: A12345 // Annual Sales($): 100000
If you set a breakpoint right after creating the sale rep subclass, and inspect the object in the expressions panel, you will see the object with its subclass properties as well as those inherited from the parent.
That concludes this Servoy tutorial on the factory design pattern using object-oriented programming. I hope you enjoyed it, and I look forward to bringing you more Servoy tutorials in the future.
Please subscribe to receive notification when new articles are published, and share your comments below.