Методы/шаблоны для достижения принципа единой ответственности в расширяемой иерархии классов

Принцип единственной ответственности гласит, например, что класс Invoice не должен содержать код для печати самого себя. Печать должна быть выделена в другой класс.

Но предположим, что у вас есть иерархия Invoice классов на разных уровнях программного обеспечения:

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() {
            ...
        }
    }
}

Какие методы или шаблоны дизайна можно использовать для разделения ответственности за печать?

Идеи:

  • Отдельный класс с большим оператором if, который проверяет каждый подкласс Invoice и запускает соответствующий код печати. Это кажется неправильным.
  • Образ посетителя. Проблема в том, что интерфейс посетителя должен существовать на основном уровне со ссылками на классы на пользовательском уровне. Я хотел бы иметь возможность добавлять новые подклассы в настраиваемый слой с изменением основного слоя.

person Joe Daley    schedule 02.11.2010    source источник


Ответы (3)


Вы можете рассмотреть ациклический посетитель (PDF).

person Don Roby    schedule 02.11.2010
comment
Интересно читать! Решение представлено на C++ с использованием множественного наследования и dynamic_cast. Я использую C #, но я думаю, что все еще могу использовать это, изменив абстрактные классы посетителей на интерфейсы. - person Joe Daley; 03.11.2010
comment
См. здесь пример C#. - person Jordão; 25.12.2012

Вам действительно нужно создавать подклассы счетов-фактур? Отличаются ли счета-фактуры чем-то еще, кроме печати? Если нет, нет необходимости иметь разные типы Invoice, вам просто нужны разные типы InvoicePrinter, переданные экземпляру Invoice:

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) 
        {
            ...
        }
    }
}

И у вас должен быть какой-то IoC, чтобы предоставить вам правильный экземпляр InvoicePrinter.

person NOtherDev    schedule 02.11.2010

Я думаю, что приведенное ниже решение полезно для C #, оно не имеет экстеры if. Насколько я знаю, использование шаблона visitor не рекомендуется.

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);
    }
}
person Saeed Amiri    schedule 02.11.2010
comment
Я не уверен, что это помогает исключить оператор if, который проверяет каждый подкласс Invoice. Если у меня есть объект Invoice, который я хочу напечатать, как мне получить соответствующий InvoicePrinter для его печати? - person Joe Daley; 03.11.2010
comment
Когда вы ЗНАЕТЕ, где находится звонящий, нет необходимости в дополнительном if, но если вы не знаете звонящего, да, у вас должен быть менеджер, и я не знаю, как предотвратить переключение и отражение, я бы подумал об этом, но я пока не нашел хорошей идеи - person Saeed Amiri; 03.11.2010
comment
@ Джо Дейли, я только что обновил свой ответ, есть несколько дополнительных объектов, которые вы можете удалить. но я думаю, что это лучший способ. - person Saeed Amiri; 03.11.2010