Question

The Single Responsibility Principle says, for example, that an Invoice class should not contain the code to print itself. Printing should be separated out into a different class.

But suppose you have a hierarchy of Invoice classes in different layers of the software:

namespace CoreLayer {
    public class Invoice {
        public virtual void Print() {
            ...
        }
    }
}

namespace CustomizedLayer {
    public class LaborInvoice : Invoice {
        public override void Print() {
            ...
        }
    }

    public class AccountInvoice : Invoice {
        public override void Print() {
            ...
        }
    }
}

What techniques or design patterns can be used to separate out the printing reponsibility?

Ideas:

  • A separate class with a great big if statement that tests for each subclass of Invoice and runs appropriate printing code. This seems wrong.
  • Visitor pattern. The problem is that a visitor interface would need to exist in the Core layer with references to the classes in the Customized layer. I would like to be able to add new subclasses in the Customized layer with modifying the Core layer.
Was it helpful?

Solution

You might want to consider the Acyclic Visitor (PDF).

OTHER TIPS

Do you really need to subclass invoices? Do invoices differ in other things than printing? If no, there's no need to have different Invoice types, you just need different InvoicePrinter types passed to the Invoice instance:

namespace CoreLayer
{
    public class IInvoicePrinter
    {
        void Print(Invoice invoice);
    }

    public class Invoice
    {
    }
}

namespace CustomizedLayer
{
    public class LaborInvoicePrinter : IInvoicePrinter 
    {
        public void Print(Invoice invoice) 
        {
            ...
        }
    }

    public class AccountInvoicePrinter : IInvoicePrinter 
    {
        public void Print(Invoice invoice) 
        {
            ...
        }
    }
}

And you should have some kind of IoC to provide you with proper InvoicePrinter instance.

I think the bellow solution is useful for C#, it has no extera if, As I know, use of visitor pattern is not recommanded.

public class InvoicePrinterManager
{
     public void Print(AccountInvoice invoice)
     {
         AccountInvoicePrinter p1 = new AccountInvoicePrinter(invoice);
         p1.print();
     }

     public void Print(LaborInvoice invoice)
     {
         LaborInvoicePrinter p1 = new LaborInvoicePrinter(invoice);
         p1.print();
     }
}

public class InvoicePrinter<T> where T : Invoice, new()
{
    T instance;

    public InvoicePrinter(T invoice)
    {
        if (invoice != null)
        {
            this.instance = invoice;
        }
        else
            instance = new T();
    }

    public virtual void Print()
    {
        /// Arrange objects as you want and print them.
    }
}

public class AccountInvoicePrinter : InvoicePrinter<AccountInvoice>
{
    public AccountInvoicePrinter(AccountInvoice invoice)
        : base(invoice)
    { 
    }

    public override void Print()
    {
       /// todo
    }
}

public class LaborInvoicePrinter : InvoicePrinter<LaborInvoice>
{
    public LaborInvoicePrinter(LaborInvoice invoice)
        : base(invoice)
    { 
    }
    public override void Print()
    {
        /// todo: use instance
    }
}

public class Test
{
    public void TestPrint()
    {
        LaborInvoice li = new LaborInvoice();
        InvoicePrintManager printerManager = new InvoicePrintManager();
        printerManager.Print(li);
    }
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top