struct Animal* p1=myfactory("cat", "Ofelija");We see that the task of a generic factory is i) to associate the symbolic identifier
cat
with the concrete data type struct Cat
,
and ii) to create a new object of that particular type.
Unfortunately, the implementations of generic factories
are tightly coupled with the details of the
desired programming language.
Therefore we shall have to consider
particular languages separately.
In order to receive credit for this exercise you have to solve the problem in C, and any of Java, Python and C++. Of course, you are welcome to solve the problem in all four languages!
1. We shall first consider the implementation of a generic factory in C. Since C has very limited introspection capabilities, the only portable way to reach the solution of our problem is to pack individual components in dynamic libraries (.dll,.so). However, before delving into details, let's illustrate our goal by the following test example.
#include "myfactory.h" #include <stdio.h> #include <stdlib.h> typedef char const* (*PTRFUN)(); struct Animal{ PTRFUN* vtable; // vtable entries: // 0: char const* name(void* this); // 1: char const* greet(); // 2: char const* menu(); }; // parrots and tigers defined in respective dynamic libraries // animalPrintGreeting and animalPrintMenu similar as in lab 1 int main(void){ struct Animal* p1=(struct Animal*)myfactory("parrot", "Bluebeard"); struct Animal* p2=(struct Animal*)myfactory("tiger", "Prudence"); if (!p1 || !p2){ printf("Creation of plug-in objects failed.\n"); exit(1); } animalPrintGreeting(p1);//"Hoist the Colors!" animalPrintGreeting(p2);//"Meow!" animalPrintMenu(p1);//"brazilian nuts" animalPrintMenu(p2);//"lukewarm milk" free(p1); free(p2); }Follow these instructions to complete the exercise.
void* myfactory(char const* libname, char const* ctorarg);The function has to i) open the dynamic library given by the 1st argument (
libname
),
ii) load from it the function create
,
iii) call that function with the 2nd argument
(ctorarg
), and
iv) return the obtained pointer to the caller.
For simplicity, the function may to assume
that the desired library is in the current working directory
and decorate the name of the library with './'
and standard extension .so
(UNIX)
or .dll
(Windows).
before writing any code, we recommend to study
the documentation of functions
dlopen
ans dlsym
(UNIX), or
LoadLibrary
and GetProcAddress
(Windows).
Also, make sure you fully
understand
the declared typedef PTRFUN
.
Place the function's prtotype into myfactory.h
,
and the implementation into myfactory.c
.
animalPrintGreeting
and animalPrintMenu
.
These implementations shall be very similar to
what we had in exercise 1, however pay attention
that now we obtaine the pet name by invoking
the method name
(index 0 of the virtual table).
Now you should be able to compile the main program
(with gcc: gcc main.c myfactory.c -ldl
).
Running the obtained executable
should terminate with message
Creation of plug-in objects failed
.
parrot.so
and tiger.so
(parrot.dll
i tiger.dll
on Windows).
These implementations shall be very similar
to their counterparts in the first exercise.
The source code has to
i) define the concrete datatype with a struct
containing the pointer to the virtual table,
and the pointer to the pet name.
ii) define functions ("methods"):
name
, greet
and menu
.
iii) define the virtual table,
te iv) define the "constructor" with prototype
void* create(char const* name);
Place the source code of the two libraries into files
tiger.c
and parrot.c
,
and compile them into shared libraries
(with gcc, for parrot.c
:
gcc -shared parrot.c -o parrot.so
)
main.c
, myfactory.c
,
myfactory.h
, parrot.c
, tiger.c
).
Now you can perform the compiling and testing
by pasting the following commands into the terminal
(of course, you are free to write a script).
gcc main.c myfactory.c -ldl gcc -shared tiger.c -o tiger.so gcc -shared parrot.c -o parrot.so ./a.out
2.
Here we consider generic libraries in Python.
In order not to make this exercise too easy,
we consider a more demanding problem.
You need to implement a program
which would perform the following:
i) instantiate one pet from each source file
found in the directory plugins
, and
ii) report all instantiated pets
by their greeting and their favourite meal.
The test program might look as follows:
def test(): pets=[] # visit each file in the directory 'plugins' for mymodule in os.listdir('plugins'): moduleName, moduleExt = os.path.splitext(mymodule) # if it is a file with Python source ... if moduleExt=='.py': # instantiate a pet ... ljubimac=myfactory(moduleName)('The pet '+str(len(pets)))) # ... and add it to the pet list pets.append(ljubimac) # report pets for pet in pets: printGreeting(pet) printMenu(pet)The output should look like so:
The pet 0 greets: Land ahoy! The pet 0 likes brazilian nuts. The pet 1 greets: Meow! The pet 1 likes lukewarm milk.Feel free to follow the instructions:
tiger
and parrot
which you will place into the same named modules
in the directory plugins.
As before, the pet types need to define
a constructor which receives the pet name,
and methods name
,
greet
and menu
.
Individual pets can be described with 9 lines of Python.
myfactory
has to use the functions
find_module
and load_module
of the imp
module,
as well as the builtin function getattr
.
Function myfactory
can be implemented
with 9 lines of Python.
3.
The solution of problem 1 is fully applicable in C++,
however we won't repeat that since we already did that before.
Instead, we shall consider a possibility
to create objects of types which are compiled and linked
into the current executable program,
but without an explicit dependency towards these types.
The key to the solution is to realize
that in C++ we can initialize static variables
by arbitrary functions which are automagically invoked
by the runtime environment before calling main()
.
Our task is to iterate over all available pet types,
to creat an instance of each type, and to report on it
by printing its greeting and favourite meal.
Use the following assumptions in teh implementation.
class Animal{ public: virtual char const* name()=0; virtual char const* greet()=0; virtual char const* menu()=0; };
// parrot.cpp static void* myCreator(const std::string& arg){ return new Parrot(arg); }
static int hreg=myfactory::instance().registerCreator("parrot", myCreator);
creators_
which associates symbolic class names
with the pointers to the corresponding
static constructor function;
registerCreator
create
;
class myfactory{ public: typedef void* (*pFunCreator)(const std::string&); typedef std::map<std::string, pFunCreator> MyMap; public: static myfactory &instance(); public: int registerCreator(const std::string &id, pFunCreator pfn); public: void *create(const std::string &id, const std::string &arg); std::vector<std::string> getIds(); private: myfactory(); static myfactory *instance_; MyMap creators_; };
For simplicity, we have proposed that
the function create
returns void*
.
Due to that, the clients of the factory must cast
the received pointer to the proper base type.
A better solution can be devised by employing templates,
but we leave that as a facultative exercise
to the interested student.
The main program could look like so:
int main(void){ myfactory& fact(myfactory::instance()); std::vector<std::string> vecIds=fact.getIds(); for (int i=0; i<vecIds.size(); ++i){ std::ostringstream oss; oss <<"Ljubimac " <<i; Animal* pa=(Animal*) fact.create(vecIds[i], oss.str()); printGreeting(*pa); printMenu(*pa); delete pa; } }
Recommendation: place all files (main.cpp
, animal.hpp
,
myfactory.cpp
, myfactory.hpp
,
parrot.cpp
, tiger.cpp
)
into the same directory and invoke the compilation
simply with:
g++ *cpp; ./a.out
A remark for those who would like to implement
this solution in C.
Unfortunately, the C standard does not leave a possibility
to invoke user function before main()
is called.
However, there are no substantial obstacles
to realize such solution.
Therefore, particular compilers offer non-standard
additions to the standard
(gcc: constructor attribute
,
msvc: pragma data_seg autostart
).
4.
Here we address the implementation of a generic factory in Java.
We consider the abstract class
hr.fer.zemris.ooup.lab2.model.Animal
with abstract methods:
public abstract String name(); public abstract String greet(); public abstract String menu();and the concrete methods:
public void animalPrintGreeting() { ... } public void animalPrintMenu() { ... }We assume that the concrete implementations of the class
Animal
shall be placed into the package
hr.fer.zemris.ooup.lab2.model.plugins
,
as well as that each pet is going to have
a constructor receiving a string (the pets name).
public class Parrot extends Animal { private String animalName; public Parrot(String name) { ... } }Your job is to write a class
AnimalFactory
with a static method newInstance
which creates an arbitrary pet.
There should be no compile-time dependencies towards components with concrete classes: everything has to be resolved at run time.
The skeleton of that class is as follows:
public class AnimalFactory { public static Animal newInstance(String animalKind, String name) { ... return ...; } }Assuming that the
.class
files of concrete types are available in the classpath of the Java virtual machine, the dynamic loading of the corresponding types can be carried out
by the static name forName
of the class Class
:
Class<Animal> clazz = null; clazz = (Class<Animal>)Class.forName("hr.fer.zemris.ooup.lab2.model.plugins."+animalKind);Once we have a reference to the class, new instances of that class can be default constructed by invoking the method
newInstance()
:
Animal animal = (Animal)clazz.newInstance();Unfortunately, this is not going to work in our case, since our pets do not have the default constructor (remember, their constructors receive the
String
representing the pet name).
Therefore we can rely on Java Reflection API,
look for constructor receiving a String
and finally invoke it as shown below:
Constructor<?> ctr = clazz.getConstructor(String.class); Animal animal = (Animal)ctr.newInstance(name);This approach will not work if the
.class
files corresponding to the concrete types are not available in the JVM classpath. In this case one needs to create an instance of the ClassLoader object receiving a path to the directory with .class
files, and either i) invoke its method loadClass(...)
or ii) a variant of the Class.forName
method receiving the desired ClassLoader. Evohere is an example:
ClassLoader parent = AnimalFactory.class.getClassLoader(); URLClassLoader newClassLoader = new URLClassLoader( new URL[] { // Add a directory (ends with '/') new File("D:/java/plugins/").toURI().toURL(), // Add a JAR (ends with '/') new File("D:/java/animals.jar").toURI().toURL() }, parent);Now we can write:
Class<Animal> clazz = (Class<Animal>)newClassLoader.loadClass("hr.fer.zemris.ooup.lab2.model.plugins."+animalKind);or
Class<Animal> clazz = (Class<Animal>)Class.forName("hr.fer.zemris.ooup.lab2.model.plugins."+animalKind, true, newClassLoader);Please note that if a ClassLoader is used, the Factory class should ensure that the same ClassLoader is used for loading all variants of a particular pet class.