Векторизация операций в отдельном классе без нарушения структуры класса - PullRequest
3 голосов
/ 22 октября 2011

Мой текущий проект разделен на несколько классов, которые правильно представляют то, как мы думаем о сущностях в системе. Однако это требует снижения производительности, поэтому я пытаюсь найти способ повышения производительности при сохранении существующей структуры классов.

Я думаю, что пример лучше всего проиллюстрирует проблему: допустим, у меня есть класс Order, который содержит несколько Item с. Наивная реализация для доставки Order отправляет все свои Item s:

class Order 
{
    private List<Item> m_items;

    public void Ship()
    {
        foreach (Item item in m_items)
        {
            item.Ship();
        }
    }
}

Доставка Item - это многоэтапный процесс:

class Item
{
    public void Ship()
    {
        var receipt = m_boat.ShipToDestination(this);
        ProcessReceipt(receipt);
    }
}

Как оказалось, доставка Item на Boat занимает много времени. К счастью, Boat может отправлять несколько Item одновременно (List<Receipt> Boat.ShipToDestination(List<Item> items)). Однако, чтобы использовать этот метод, мне нужно будет переписать Order.Ship() следующим образом:

class Order 
{
    public void Ship()
    {
        var receipts = m_boat.ShipToDestination(m_items);
        foreach (Receipt receipt in receipts)
        {
            ProcessReceipt(receipt);
        }
    }
}

Это означает, что ProcessReceipt теперь является частью Order (а не Item), а также что Item больше не заботится обо всей своей логистике доставки.

В моем реальном коде есть много методов с этой проблемой, поэтому эффективное использование этого решения означает, что большая часть логики Item перейдет в класс Order. Тем не менее, это кажется неправильным местом для этой логики - она ​​действительно должна быть в Item.

Я подумал об использовании нескольких потоков (например, Task на Item), но это кажется слишком расточительным, тем более что Order может иметь десятки Items.

Другой подход, который я рассмотрел, - это создание QueuedBoat с помощью метода QueueForShipping(Item item) и метода ShipQueuedItems(). Теперь Order может звонить ShipQueuedItems, а Item может звонить QueueForShipping. Проблема с этим подходом заключается в том, что в моем реальном коде Receipt возвращается только после доставки Item, поэтому код должен выглядеть следующим образом:

class Order 
{
    private List<Item> m_items;

    public void Ship()
    {
        foreach (Item item in m_items)
        {
            item.Ship();
        }

        m_queuedBoat.ShipQueuedItems();

        foreach (Item item in m_items)
        {
            item.FinalizeShipping();
        }
    }
}


class Item
{
    public void Ship()
    {
        m_queuedBoat.QueueForShipping(this);
    }

    public void FinalizeShipping()
    {
        ProcessReceipt(m_queuedBoat.GetReceiptForItem(this));
    }
}

Такое ощущение, что он в правильном направлении (поскольку Order знает, сколько у него Item s, это должен быть компонент, который знает, когда все находится на Boat), но он разбил Ship на два этапа. В моем реальном коде у меня есть несколько этапов (каждый может быть векторизован отдельно), поэтому метод будет разбит на 3-4 части, что не очень приятно.

Может кто-нибудь предложить способ векторизации Item.Ship() при сохранении бизнес-логики внутри класса Item, или мне придется отказаться от разделения Order / Item, чтобы воспользоваться преимуществами векторизации?

Edit:

Другой подход может быть таким:

class Order 
{
    private List<Item> m_items;

    public void Ship()
    {
        for (int i = 0; i < m_items.Count; ++i)
        {
            bool isLast = (i == (m_items.Count - 1));
            item.Ship(isLast);
        }
    }
}

class Item
{
    public void Ship(bool isLast)
    {
        m_queuedBoat.QueueForShipping(this, FinalizeShipping);
        if (isLast) m_queuedBoat.ShipQueuedItems();
    }

    private void FinalizeShipping(Receipt receipt)
    {
        ProcessReceipt(receipt);
    }
}

Где класс QueuedBoat вызывает обратный вызов для каждого элемента со своим Receipt.

1 Ответ

2 голосов
/ 22 октября 2011

Ну, в любом случае Ship - это своего рода асинхронная операция: там должно выполняться несколько одновременно. Таким образом, он должен быть двухэтапным: PrepareShipping (void) и PerformShipping (ожидает окончания фактической доставки, а возвращает обрабатывает ее получение).

Я бы так примерно так:

class Item
{
    public void PrepareShiping()
    {
        m_queuedBoat.QueueForShipping(this);
    }

    public void PerformShipping()
    {
        m_queuedBoat.StartActualShippingIfNotAlreadyStarted();
        m_queuedBoat.WaitForShippingEnd();
        ProcessReceipt(m_queuedBoat.GetReceiptForItem(this));
    }
}

Так что Орден может быть примерно таким:

class Order 
{
    public void PrepareShipping()
    {
        foreach (Item item in m_items)
            item.PrepareShiping();
    }

    public void PerformShipping()
    {
        foreach (Item item in m_items)
            item.PerformShipping();
    }

    // and for those who don't need asynchronity
    public void Ship()
    {
        PrepareShipping();
        PerformShipping();
    }
}

Программа может выполнять дополнительные действия в процессе доставки, то есть между Order.PrepareShipping() и Order.PerformShipping() (например, отправлять и другие заказы, возможно, на той же лодке).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...