Reputation: 2848
Please pardon my poor knowledge of design patterns.
Sometimes methods have many parameters and introducing a Parameter Object is the right way to refactor your code according to refactoring-guru article.
Imagine a situation when we have a service which handles some sort of documents.
public class FinancialStatementService : IFinancialStatementService
{
public Document Create(Options input)
{
// creating basic document content and adding optional data below:
if (input.HasAccountNo)
{
// include account number
}
if (input.HasPaymentDetails)
{
// include payment details
}
if (input.HasZeroBalances)
{
// include zero balances
}
if (input.HasTotal)
{
// include total
}
// and then return the document
}
}
public class Options
{
public DateRange DateRange { get; set; }
public bool HasAccountNo { get; set; }
public bool HasPaymentDetails { get; set; }
public bool HasZeroBalances { get; set; }
public bool HasTotal { get; set; }
}
The document consists of many parts, some are optional. Sometimes we need the document to contain all possible details.
But imagine a case when a certain organization doesn't want some of the details.
Ideally I'd like to have a class which handles options creation and has methods containing organization name e.g.
public class OptionsCreator
{
// tax officials want to see all possible details
public static Options GetTaxOfficeOptions(DateRange dateRange)
{
return new Options() { HasAccountNo = true, HasPaymentDetails = true, HasZeroBalances = true, HasTotal = true, DateRange = dateRange };
}
// in some other organization they DO NOT NEED zero balances & payment details
public static Options GetSomeOtherOgranizationOptions(DateRange dateRange)
{
return new Options() { HasAccountNo = true, HasTotal = true, DateRange = dateRange };
}
}
But I'm afraid the above example is an anti-pattern.
Another thing I can think of is Builder Pattern.
Would Builder Pattern be the most optimal solution for the Parameter Object?
Upvotes: 3
Views: 976
Reputation: 29028
I don't know details of your requirements. But from what I see my suggestion would be to split the process into clean independent peaces or responsibilities:
I think Print(Options)
must only take care of sending documents to a specified output. Print(Options)
must understand all documents but not assemble them. The main issue right now is that you have to modify to many places when e.g. adding new documents, roles or more document data, because there are no clean boundaries between the responsibilities.
Consider this: the very moment you are creating or configuring the options argument for a specific document, you already know how the document would look like. Why bother the Print(Options)
to start over from scratch again. The moment you know the options you can create the proper document. This way you already eliminated the need for the Options
type and more important the ugly and hard to maintain state checks of the Options
object.
So your flow begins with a call of the proper method that is specific to a role. This method will create a document that follows the requirements of this certain role. So OptionsCreator
becomes DocumentCreator
.
Once the document is created you handle it over to the print method. So Print(Options)
becomes Print(Document)
. It is important that Print(Document)
can output a document without knowing anything about roles or document details, only how and where to output the document. Now when a new role or document content is introduced, the Print(Document)
is safe from modification. You would only add a new factory method to the DocumentCreator
to create the new document that is specific to the new role.
I don't think that you can benefit from the Builder right here.
Upvotes: 1
Reputation: 1044
I usually use smth like that:
Constant class with - (in this example I use first parameter for string for xml parsing and second parameter as default value)
public static readonly Dictionary<string, Tuple<string, object>> fieldNamesWithOptions = new Dictionary<string, Tuple<string, object>>() {
{ "number", new Tuple<string, object>("PONumber","") },
{ "shipDate", new Tuple<string, object>("ShipDate", new DateTime(1900, 1, 1)) },
{ "detailID", new Tuple<string, object>("DetailID", -1) },
}
Process you options like that (pseudocode, imagine that third parameter in Tuple is preparing function):
for every field F in object O:
if(fieldNamesWithOptions.ContainsKey(F.name))
{
result += fieldNamesWithOptions[F].third(O.F);
}
I use pseudocode because in C# it will look rather complex.
This refactoring will clear you code, split logic into small pieces that can be understood/tested. On the other hand we have unusual cycle for every field F in object O:
, but being debugged once, it can be used in some helper in any other parts of project.
Upvotes: 1