2

I have a class with a complex construction process with many parameters. Multiple clients share objects of this class, and the union of these clients parameters are used to instantiate the class. Therefore I have a factory class that stores these requirements, checks consistency of the various clients' requests, and instantiates the class.

Additionally, there are a common set of use models (or sets of parameters) which multiple clients use for multiple factories.

For instance, consider an example. (Note that the actual code is C++, but my experience is in Python so I'll pseudo-code in Python. Yes, I know that this example wouldn't actually work as-is.)

class Classroom:
    def __init__(self, room_size=None, n_desks=None, n_boards=None, 
                       n_books=None, has_globe=False, ... ):
        ...

class ClassroomFactory:
    def __init__(self):
        self._requirements = dict()

    def addRequirement(self, name, value):
        if name.startswith("n_"):
            self._requirements[name] = max(value, self._requirements.get(name, 0))
        ...

    def createClassroom(self):
        return Classroom(**self._requirements)


# instantiate the factory
factory = ClassroomFactory()

# "client 1" is a geography teaacher
factory.addRequirement("n_desks", 10)
factory.addRequirement("n_boards", 1)
factory.addRequirement("has_globe", True)

# "client 2" is a math teacher
factory.addRequirement("n_desks", 10)
factory.addRequirement("n_boards", 1)

# "client 3" is a after-school day-care
factory.addRequirement("room_size",  (20,20))
factory.addRequirement("has_carpet", True)

room = factory.createClassroom()

The common use model is as a teacher, we need 10 desks and a board. I think this is best served by a non-member function/decorator, something like:

def makeTeacherRoom(factory):
    factory.addRequirement("n_desks", 10)
    factory.addRequirement("n_boards", 1)        
    return factory

This seems like a great example of the "prefer non-member/non-friend to member" paradigm.

The thing that I'm struggling with is, within the framework of a much bigger OO code, where should these types of non-member functions/decorators live, both in terms of namespace and in terms of actual file?

  1. Should they live in the factory's file/namespace? They are closely related to the factory, but they're limitations on the general factory, and need not be used to use the factory.

  2. Should they live in the client's file/namespace? The client understands these use models, but this would limit re-use amongst multiple clients.

  3. Should they live with a common base class of the clients (for instance, one could imagine a "teacher" class/namespace which would also provide the non-member function makeTeacherRoom(), which would be inherited by MathTeacher and GeographyTeacher.

  4. Should they live somewhere else completely, in a "utils" file? And if so in which namespace?

Naftali
  • 144,921
  • 39
  • 244
  • 303
Ethan Coon
  • 751
  • 5
  • 16
  • @harper this is not C++, this seems to be python.... – Naftali Jun 26 '12 at 17:56
  • 2
    Sorry, I thought I was clear -- my pseudo-code is python, but the actual application is C++. I've specifically tried to make the python prototype not use "python but not C++" stuff like actual decorator classes. Note that implications for namespaces would be very different... – Ethan Coon Jun 26 '12 at 18:00
  • This may not relate to your question, but you could probably make your many-parameter C++ constructor more Python-like if you use the [Named Parameter Idiom](http://stackoverflow.com/a/2700976/10077). – Fred Larson Jun 26 '12 at 18:04
  • @Ethan Coon Most stuff may work similar both in Python, or C++, but, non member functions are the few cases where are handled different in Python and C++ – umlcat Jun 26 '12 at 23:06

3 Answers3

1

This is primarily a personal decision. Most of your options have no technical negative effects. For example:

  1. They could, because of locality of use, but it's not necessary.
  2. They could, because of locality of data, but again...
  3. They could, although this one does seem like it could make things a bit messier. Making utility classes, you may have to end up inheriting them, or making parts virtual to override later, which will get ugly pretty quick.
  4. This is my personal favorite, or a variant of this.

I typically make a relevantly-named util file (or class with static methods) and put it in the same namespace as the classes it utilates (the more helpful version of mutilate). For a Education::Teacher class, you could have a Education::TeacherUtils file or class containing the functions that operate on Teacher. This keeps a pretty obvious naming tie-in, but also puts the util functions in their own area, so they can be included from whatever needs them (in the Teacher.cpp or similar would prevent that). In the case of a class, you can make the util and base classes friends, which is occasionally helpful (but something to use rarely, as it may be a smell).

I've seen a naming variation, Education::Utils::Teacher, but that's somewhat harder to translate to files (unless you put things into a utils dir) and can also cause name resolution oddness (in some contexts, the compiler may try to use Education::Utils::Teacher instead of Education::Teacher when you didn't mean to). Because of this, I prefer to keep utils as a suffix.

ssube
  • 47,010
  • 7
  • 103
  • 140
0

You may want to handle non-member functions in a singleton class for your application. A factory maybe executed from the program, or another object.

C++ supports global functions (non member functions), but, using a single object for the application, "does the trick".

Additionally, since the "Classroom" object may be instantiated with many optional parameters, you may want to assign it, after calling the constructor ( "init" in python ).

// filename: "classrooms.cpp"

class ClassroomClass
{
  protected:
    int _Room_Size;
    int _N_Desks;
    int _N_Boards;
    int _N_Books;
    bool _Has_Globe;

  public:
    // constructor without parameters,
    // but, can be declared with them
    ClassroomClass()
    {
      _Room_Size = 0;
      _N_Desks = 0;
      _N_Boards = 0;
      _N_Books = 0;
      _Has_Globe = false;
    } // ClassroomClass()

    public int get_Room_Size()
    {
      return _Room_Size;
    }

    public void set_Room_Size(int Value)
    {
      _Room_Size = Value;
    }

    // other "getters" & "setters" functions
    // ...

} // class ClassroomClass

class ClassroomFactoryClass
{
  public:
    void addRequirement(char[] AKey, char[] AValue);
} // class ClassroomFactoryClass    

class MyProgramClass
{
  public:
    ClassroomFactoryClass Factory;
  public:
    void makeTeacherRoom();

    void doSomething();
} // class MyProgramClass

void MyProgramClass::addRequirement(char[] AKey, char[] AValue)
{
  ...
}  // void MyProgramClass::addRequirement(...)

void MyProgramClass::makeTeacherRoom()
{
  Factory.addRequirement("n_desks", "10")
  Factory.addRequirement("n_boards", "1")   
}  // void MyProgramClass::makeTeacherRoom(...)

void MyProgramClass::doSomething()
{
  ...
}  // void MyProgramClass::doSomething(...)

int main(char[][] args)
{
   MyProgramClass MyProgram = new MyProgramClass();

   MyProgram->doSomething();

   delete MyProgram();

  return 0;
}  // main(...)

Cheers

umlcat
  • 4,091
  • 3
  • 19
  • 29
  • This is actually the old form of the application, but the result was a very complex ClassroomClass. The setters all had to have significant logic to manage unions of various clients requests. Then, we needed a "Finalize()" method to let the object know it was done getting client requests and could create the actual data. Basically the object (Classroom) had to manage a bunch of client requests, which had nothing to do with the actual object, and everything to do with creating the object. Yanking this out and putting it in a factory made things much cleaner (IMO). – Ethan Coon Jun 27 '12 at 03:53
0

Personally I would make them static members of the class.

class File
{
    public:

    static bool load( File & file, std::string const & fileName );

    private:

    std::vector< char > data;
};

int main( void )
{
    std::string fileName = "foo.txt";
    File myFile;

    File::load( myFile, fileName );
}

With static methods they have access to the private data of the class while not belonging to a specific instance of the class. It also means the methods aren't separated from the data they act on, as would be the case if you put them in a utility header somewhere.

PeddleSpam
  • 418
  • 3
  • 10