Question

I'm using MigraDoc to create a pdf document.

I have business entities similar to the those used in MigraDoc.

    public class Page{
      public List<PageContent> Content { get; set; } 
    }


  public abstract class PageContent {

        public int Width { get; set; }      
        public int Height { get; set; } 
        public Margin Margin { get; set; }
    }

    public class Paragraph : PageContent{
         public string Text { get; set; }    
    }

    public class Table : PageContent{
         public int Rows { get; set; }    
         public int Columns { get; set; }  
        //.... more
    }

In my business logic, there are rendering classes for each type

public interface IPdfRenderer<T>
{
    T Render(MigraDoc.DocumentObjectModel.Section s);
} 

class ParagraphRenderer
    : IPdfRenderer<MigraDoc.DocumentObjectModel.Paragraph>
{
    BusinessEntities.PDF.Paragraph paragraph;

    public ParagraphRenderer(BusinessEntities.PDF.Paragraph p)
    {
     paragraph = p;
    }

    public MigraDoc.DocumentObjectModel.Paragraph Render(MigraDoc.DocumentObjectModel.Section s)
    {
        var paragraph = s.AddParagraph(); // add text from paragraph etc
        return paragraph;
    }
}


public class TableRenderer : IPdfRenderer<MigraDoc.DocumentObjectModel.Tables.Table>
{
    BusinessEntities.PDF.Table table;
    public TableRenderer(BusinessEntities.PDF.Table t)
    {
        table =t;
    }
    public MigraDoc.DocumentObjectModel.Tables.Table Render(Section obj)
    {
        var table = obj.AddTable();
        //fill table based on table
    }
}

I want to create a PDF page as :

            var document = new Document();
            var section = document.AddSection();// section is a page in pdf
            var page = GetPage(1); // get a page from business classes
            foreach (var content in page.Content)
            {
                      //var renderer = createRenderer(content); //
                     // get Renderer based on Business type ??
                     // renderer.Render(section) 
            }

For createRenderer() i can use switch case/dictionary and return type.

How can i get/create the renderer generically based on type ?

How can I use factory or abstract factory here?

Or which design pattern better suits this problem?

Was it helpful?

Solution

You can use a factory where your key would be the type, this article has some examples.

If you can afford to use IoC, is better if you let IoC engine decide which concrete class should be used based on a string, you should configure your containers accordingly. This string can be Type name. I suggest ninject but any other IoC like windsor can work.

Below is an example of how can be done using a factory with generic interfaces, since in your case there is contructor you need to pass is it.

public class Factory<T>
{
    public Factory()
    {
        _Mappings = new Dictionary<string,Func<IInterface<T>>>(2);

        _Mappings.Add("MyClass1", () => new MyClass1() as IInterface<T>);
        _Mappings.Add("MyClass2", () => new MyClass2() as IInterface<T>);
    }
    public IInterface<T> Get(string typeName)
    {
        Func<IInterface<T>> func;
        if (_Mappings.TryGetValue(typeName, out func))
        {
            return func();
        }
        else
            throw new NotImplementedException();
    }

    readonly Dictionary<string, Func<IInterface<T>>> _Mappings;
}

public class MyClass1 : IInterface<int>
{
    public int Method()
    {
        throw new NotImplementedException();
    }
}

public class MyClass2 : IInterface<string>
{
    public string Method()
    {
        throw new NotImplementedException();
    }
}

public interface IInterface<T>
{
    T Method();
}

I still think IoC is better, but this is a valid approach.

OTHER TIPS

Your interface definition can be enhanced. Instead just returning the generic and accepting input as constructor, you can do both accepting the generic as input and return it as well.

public interface IPdfRenderer<T>
{
    T Render(MigraDoc.DocumentObjectModel.Section s, T baseContent);
}

Then expanding arutromonriv's answer,

public class DynamicRenderer : IPdfRenderer<PageContent>
{
    public DynamicRenderer(IPdfRenderer<Paragraph> paragraph
        , IPdfRenderer<Table> table){
        //variable assignment
    }
    public PageContent Render(MigraDoc.DocumentObjectModel.Section s, PageContent baseContent){
        if(baseContent is Paragraph){ return paragraph.Render(s, baseContent as Paragraph); }
        else{ return table.Render(s, baseContent as Table); }
    }
}

With this you can do:

var renderer = createRenderer(); //
foreach (PageContent content in page.Content)
{
    // get Renderer based on Business type ??
    renderer.Render(section, content);
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top