Rob
Rob

Reputation: 1708

Class constructor with multiple parameters

What's the best way to call a class constructor that can have lots of parameters associated with it?

For instance, if I want to develop a component to automatically log exceptions in an application; let's call it 'ExceptionLogger'

ExceptionLogger has 3 ways of of writing the errors generated by the application that references it.

ToLogFile              (takes 2 parameters)
ToDatabase             (takes 2 parameters)
ToEmail                (take 4 parameters)

Each of these 3 methods are private to ExceptionLogger and the calling application needs to 'turn on' these methods through the class constuctor; also supplying the parameters if required.

The calling app would simply use a 'publish' method to have ExceptionLogger write the information to the relevant storage.

To add a clarification; it's my intenttion for a single ExceptionLogger instance to be able to do multiple writes

Upvotes: 1

Views: 5862

Answers (6)

tpdi
tpdi

Reputation: 35141

  1. Write several ctors taking different arguments.

  2. Make the ctor private or protected, and add three differently named static functions (e.g, email, logger, database) that take the right args and pass them to the ctor, along with information as to which sort of EceptionLogger to contruct, and then return the newly contructed object. This is the Named constructor Pattern. http://www.parashift.com/c++-faq-lite/ctors.html#faq-10.8

  3. As 1, but pass in a dummy argument to distinguish ctors that would otherwise have the same signature. One useful dummy would be Marker Interfaces defined within the class. A Marker Interface is a class type that does nothing but signal something by being passed or used as a base class. e.g.

    public class ExceptionLogger {
      private class AsEmail {} ;
      public static AsEmail asEmail;
      private class AsLogFile {} ;
      public static AsLogFile asLogFile {};
      ExceptionLogger( const AsEmail&, const char* const ) ;
      ExceptionLogger( const AsLogFile&, const char* const ) ;
  4. As 3, but distinguish ctors by an enum, and branch within the ctor. (This one is ugly.)

  5. Add setters to the class that "chain" by returning *this, the "Named Parameter Idiom": http://www.parashift.com/c++-faq-lite/ctors.html#faq-10.18 Given that you have a disjunction of arguments, not a union of them, three is probably a bad choice for ExceptionLogger.

  6. Pass in another class that holds the parameters, a Traits class.

  7. Keep the common logic in your class, pass in the variant logic as a (single) GOF ConcreetStrategy sublcass of a GOF AbstractStrategy. Similar to 3, but now the logic is in the argument.

  8. Keep the common logic in your class, but make it a base class of three distinct subclasses that separate out the various strategies and then call common functionality in the base class. Each subclas has a single ctor, but they can have the same arguments, as ctors aren't inherited.

  9. Keep the common logic in your class, but delegate to it from three nonrelated classes, each holding it by containment or as a private base class. Again, each can have its own ctor. No good, as you probably want ExceptionLoggers to be of the same (super) type.

Upvotes: 0

Ward Werbrouck
Ward Werbrouck

Reputation: 1462

Use seperate classes that are able to write data to logfile/database/email and pass the one you want to use in the constructor. And make them all implement the same interface

example:

LogDatabaseWriter writer = new LogDatabaseWriter(param1, param2, param3);
Logger log = new Logger(writer);

edit: Some more code

So you have an example interface:

interface ILogWriter
{
    public void Write(string s);
}

And several implementations of the interface

class LogDatabaseWriter : ILogWriter
{
    //constructor
    // ...

    //implement the required interface methods
    public void Write(string s)
    {
        //Do your thing
    }
}

And your Logger class has a constructor like this:

class Logger
{
    private ILogWriter _writer;

    public Logger(ILogWriter writer)
    { 
        _writer = writer;
        //Do your thing 
    }
}

Upvotes: 0

Florian Greinacher
Florian Greinacher

Reputation: 14786

If the storage types aren't to supposed to be extended, I would recommend using an enum, like this

public enum StorageLocation
{
   None,
   File,
   Database,
   Email      
}

public class ExceptionLogger
{
   private StorageLocation m_Storage;       

   public StorageLocation Storage
   {
      get { return m_Storage; }
      set { m_Storage = value; }
   }

   // ...
}

If you think that storage locations need to be extended in the future then I would create an interface IStorageLocation and use this to get/set the Storage property in ExceptionLogger. Will be a bit more effort but you will gain far more flexibility.

Upvotes: 0

D.Shawley
D.Shawley

Reputation: 59553

You might want to consider using the Named Constructor paradigm or three separate classes in a hierarchy. It sounds like you have three different classes each with it's own set of requirements on the constructor. If the data in the constructor parameters are required for the instance to operate, then they have to be parameters of the constructor or else you end up with an instance that is missing data.

The other approach is to use Named Parameters to represent truly optional parameters. I believe that boost offers a framework for implementing named parameters as well.

Upvotes: 2

JaredPar
JaredPar

Reputation: 754505

Clarification, I read your question and assumed it was possible for a single ExceptionLogger instance to write via multiple types of communication.

For this particular example, I would encapsulate the parameters needed to enable each of the three writing methods into a separate class. Say

  • LogFileConstructionInfo
  • DatabaseConstructionInfo
  • EmailConstructionInfo

I would then create 4 different constructors. One for each of the above types accepting only that type. This allows for quick and easy creation of ExceptionLogger instances which only record in a single way. It also makes the callsite code very clear as to which method it's using.

In order to allow for multiple methods of writing, I would define a fourth construct which has three parameters, one for each of the above types. Null, or some other lack of a value indicator such as option, would be allowed for the arguments. This would allow for any combination of writers to be created.

Upvotes: 1

kvb
kvb

Reputation: 55184

It seems like this might be a good place to use inheritance instead. You could have a FileLogger, DatabaseLogger, and EmailLogger each of which derives from a base ExceptionLogger class and has a single appropriate constructor.

Upvotes: 3

Related Questions