6
\$\begingroup\$

I have an application that allows the user to place a string of text into a PDF document. I have three different ways that they can do this:

  1. Use a Form Field. Then they have four properties to define:
    1. Provide field name
    2. provide instance of the field
    3. provide an X-axis Offset
    4. provide a Y-axis Offset
  2. Search for a string of text. Then they have four properties to define:
    1. Provide string of text to search for
    2. provide instance of that string of text
    3. provide an X-axis Offset
    4. provide a Y-axis Offset
  3. Define Page Coordinates. Then they have three properties to define:
    1. Provide page number
    2. provide an X-axis Offset
    3. provide a Y-axis Offset

In my API, I want the object to be setup intuitively so that it is clear they are choose one of the three different methods and then whichever one they choose, they have the options for each.

In my old API, all of those options are grouped together as a list of properties like this:

Placement.FormField_FieldName
Placement.FormField_Instance
Placement.SearchText_Text
Placement.SearchText_Instance
Placement.PageCoordinates_PageNumber
Placement.XOffset
Placement.YOffset

I find this to be a little confusing for the developer using the API because you could never use all of the properties together.

You would either use Form Field like this:

Placement myPlacement = new Placement();
myPlacement.FormField_FieldName = "MyPdfFormFieldName";
myPlacement.FormField_Instance = ;
myPlacement.XOffset = 0;
myPlacement.YOffset = 0;

Or use Text Search like this:

Placement myPlacement = new Placement();
myPlacement.SearchText_Text = "My String Of Text";
myPlacement.FormField_Instance = 1;
myPlacement.XOffset = 0;
myPlacement.YOffset = 0;

Or use Page Coordinates like this:

Placement myPlacement = new Placement();
myPlacement.PageCoordinates_PageNumber = "My String Of Text";
myPlacement.XOffset = 0;
myPlacement.YOffset = 0;

Is there a better way to setup the class (or sub classes) to make it easier for the developer to understand how to use this functionality?

UPDATE

This is what I currently am using because the abstract class thing didn't work.

public class SignatureBlock
{
    public List<SignatureBlockPlacement> SignatureBlockPlacements = new List<SignatureBlockPlacement>();
}

public class SignatureBlockPlacement
{
    public Enumerations.SignatureBlock.SignatureBlockTypes Type { get; set; }
    public string FormField_FieldName { get; set; }
    public int FormField_InstanceOfField { get; set; }
    public string SearchText_TextToSearch { get; set; }
    public int SearchText_InstanceOfText { get; set; }
    public string PageCoordinates_PageNumber { get; set; }
    public int XOffset { get; set; }
    public int YOffset { get; set; }
}

Which I'm implementing like this:

SignatureBlock sigBlock = new SignatureBlock();

SignatureBlockPlacement sig = new SignatureBlockPlacement();
sig.Type = Enumerations.SignatureBlock.SignatureBlockTypes.Signature;
sig.FormField_FieldName = "SignatureField";
sig.FormField_InstanceOfField = 1;

SignatureBlockPlacement title = new SignatureBlockPlacement();
title.Type = Enumerations.SignatureBlock.SignatureBlockTypes.Title;
title.FormField_FieldName = "SignatureField";
title.FormField_InstanceOfField = 1;
title.XOffset = 0;
title.YOffset = -20;

SignatureBlockPlacement org = new SignatureBlockPlacement();
org.Type = Enumerations.SignatureBlock.SignatureBlockTypes.Organization;
org.FormField_FieldName = "SignatureField";
org.FormField_InstanceOfField = 1;
org.XOffset = 0;
org.YOffset = -40;

sigBlock.SignatureBlockPlacements = new List<SignatureBlockPlacement>() { sig, title, org };

If there's a better way to do this, please let me know.

UPDATE #2

I should have mentioned this is for a WCF SOAP web service.

UPDATE #3

Long story short on this one, I need to allow the developer three different ways to place the signature but they should only be allowed to use one of the three ways . Each way shares the X/Y Offset properties but their other properties are unique to each way.

\$\endgroup\$
3
  • \$\begingroup\$ Is Placement a subclass of something like Form or is it just a class? Do those properties need to be available to an IDE designer, or do they just need to be available to a programmer who is writing code? \$\endgroup\$
    – ChrisW
    Commented Feb 21, 2014 at 3:22
  • \$\begingroup\$ Yes, Placement would either be a sub class of a "Task" class or a property of Task - I haven't decided. \$\endgroup\$
    – RichC
    Commented Feb 21, 2014 at 5:06
  • \$\begingroup\$ I think that your original code is clear. It's a simple POCO with 4 well-named properties, this shouldn't be hard for any developer to use. IF you had to make it even clearer I guess you could incorporate ChrusWue's answer as an AddPlacement(...) function in your SignatureBlock class which just adds the new placement to the SignatureBlockPlacement list. \$\endgroup\$
    – Max
    Commented Mar 4, 2014 at 12:59

2 Answers 2

4
\$\begingroup\$

not very sure about your placement class, whether it's a pure class designed to encapsulate options, or a win-form.

If it's the former, i would suggest you use Polymorphism to have certain abstract layer, a quick sample:

At your side:

public abstract class Placement
{
    public int XOffset { get; set; }
    public int YOffset { get; set; }

    public class FrmField:Placement
    {
        public string FieldName { get; set; }
        public int Instance { get; set; }
    }

    public class TextSearch:Placement
    {
        public TextSearch(){}
        public string Text { get; set; }
        public int Instance { get; set; }
    }

    public class PageCoord : Placement
    {
        public string PageNumber { get; set; }
    }
}

public class SomeObjRequriePlacement
{
    public void DoSomething(Placement pla)
    {
        // this class and method may be a winform that do something based on given options
    }
}

Other dev using your api:

public class TestAtDev
{
    public void TestGeneric()
    {
        Placement pla = null;
        if (false) //some condition
        {
            pla = new Placement.FrmField() { FieldName = "", Instance = 1, XOffset = 1, YOffset = 2 };
        }
        else if (true) // some condition
        {
            pla = new Placement.PageCoord() { PageNumber = "xxx", XOffset = 1, YOffset = 2 };
        }
        else
        {
            pla = new Placement.TextSearch() { Text = "", XOffset = 1, YOffset = 2 };
        }

        var someObj = new SomeObjRequriePlacement();
        someObj.DoSomething(pla);        
    }
}

if your Placement class is already an UI components, i would then suggest you separate the options away and wrap them as PlacementOption and use it in your placement class.

and if necessary, you may define an interface to make the code more independent. And Generic (like Type constraint) could make your code even tidier.

EDIT:

use type constraint to have strong typed object where necessary - but the basic implementation of the placement is still the same.

public abstract class HandlePlacement<T> where T:Placement
{
    public virtual void DoWork(T thePlacement) { }
}

public class HandleTextSearch : HandlePlacement<Placement.TextSearch>
{
    public override void DoWork(Placement.TextSearch thePlacement)
    {
        // do some specific work for text search option
    }
}

/*implementations for other placement options*/
/* ... */

public class TestAtDev
{
    public void DoTextSearch()
    {
        Placement.TextSearch ts = new Placement.TextSearch() { XOffset = 0, YOffset = 1, Instance = 2, Text = "" };
        HandlePlacement<Placement.TextSearch> hanlder = new HandleTextSearch();
        hanlder.DoWork(ts);
    }
}

another sample:

public abstract class SomeHandler
{
    public virtual void DoWork() { }
}

public abstract class SomeHandler<T>:SomeHandler where T : Placement
{
    protected SomeHandler(T thePlacement)
    {
        this.Placement = thePlacement;
    }

    protected T Placement { get; set; }
}

public class SomeHandlerForTextSearch : SomeHandler<Placement.TextSearch>
{
    public SomeHandlerForTextSearch(Placement.TextSearch ts):base(ts){}

    public override void DoWork()
    {
        // some specific handling for text search
    }
}

public class SomeHandlerForPageCoord : SomeHandler<Placement.PageCoord>
{
    public SomeHandlerForPageCoord(Placement.PageCoord pc) : base(pc) { }

    public override void DoWork()
    {
        // some specific handling for page coordinates
    }
}

public class TestAtDev2
{
    public void DoSomeTest()
    {
        SomeHandler h = null;
        if (true) // some condition
        {
            h = new SomeHandlerForTextSearch(new Placement.TextSearch() { XOffset = 1, YOffset = 2, Instance = 3, Text = "test" });
        }
        else if (true) // some condition
        {
            h = new SomeHandlerForPageCoord(new Placement.PageCoord() { XOffset = 1, YOffset = 2, PageNumber = "PageNo." }); 
        }
        h.DoWork();
    }
}
\$\endgroup\$
11
  • \$\begingroup\$ Yes, I like this very much. I think this is exactly what I'm looking for. \$\endgroup\$
    – RichC
    Commented Feb 21, 2014 at 5:06
  • \$\begingroup\$ I worry about this breaking the Law of Demeter, any thoughts on that? \$\endgroup\$
    – RichC
    Commented Feb 21, 2014 at 14:46
  • \$\begingroup\$ Also, don't you lose the FieldName = "", Instance = 1 values the second you assign FrmField into pla? \$\endgroup\$
    – RichC
    Commented Feb 21, 2014 at 15:30
  • \$\begingroup\$ @RichC I don't see how this could break the Law of Demeter. Can you clarify? \$\endgroup\$
    – ANeves
    Commented Feb 21, 2014 at 17:28
  • \$\begingroup\$ @ANeves The bigger issue is that if you assign Placement pla = new Placement.FrmField() { FieldName = "", Instance = 1, XOffset = 1, YOffset = 2 }; you lose the .FieldName and .Instance properties when you try to do work with pla. At least, in my testing, I did. \$\endgroup\$
    – RichC
    Commented Feb 21, 2014 at 17:44
1
\$\begingroup\$

One possibility is to use a factory class (which contains static methods to create the Placement); or put the static factory methods in the Placement class itself.

The factory methods can:

  • Be well-named so that they're self-documenting
  • Name their parameters
  • Include XML documentation

For example:

/// <summary>
/// Describe the method here.
/// </summary>
/// <param name="fieldName">Describe the parameter here</param>
/// <param name="instance">Describe the parameter here</param>
/// <param name="xOffset">Describe the parameter here</param>
/// <param name="yOffset">Describe the parameter here</param>
/// <returns>Describe the return value here</returns>
static Placement CreateFormField(string fieldName, int instance, int xOffset, int yOffset)
{
    Placement placement = new Placement();
    placement.FormField_FieldName = fieldName;
    placement.FormField_Instance = instance;
    placement.XOffset = xOffset;
    placement.YOffset = yOffset;
    return placement;
}
\$\endgroup\$
6
  • \$\begingroup\$ Will this cause problems when used in WCF SOAP? (I probably should have mentioned this in my original post. Looks like I just mentioned API and not SOAP) Since the SOAP client won't be able to get the return for that method. \$\endgroup\$
    – RichC
    Commented Feb 21, 2014 at 17:47
  • \$\begingroup\$ What is the SOAP client doing to use the existing code? In what way is the code in the question SOAP-specific: what/where is the SOAP API? \$\endgroup\$
    – ChrisW
    Commented Feb 21, 2014 at 20:16
  • \$\begingroup\$ nothing different than I posted in my first update. \$\endgroup\$
    – RichC
    Commented Feb 21, 2014 at 20:23
  • \$\begingroup\$ @RichC I see no SOAP in your first update. IMO if the client can do new Placement() and assign to FormField_FieldName, then they could alternatively call a CreateFormField method which does the same thing. \$\endgroup\$
    – ChrisW
    Commented Feb 21, 2014 at 20:32
  • 1
    \$\begingroup\$ should be return placement; \$\endgroup\$
    – Max
    Commented Mar 4, 2014 at 12:55

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.