Внедрение зависимостей, класс, требующий словаря интерфейсов того же типа, хорошая идея?

Хотя я задал этот вопрос в Code Review, но оригинальный код теперь ползет. Да, я также большой поклонник разговоров о чистом коде, только что посмотрел эти потрясающие видео, я также видел еще один вопрос. Это та же проблема, что и у меня изначально.

У меня есть класс, скажем, человек. Человек, основанный на каком-либо решении в своем методе Путешествия, может либо вызвать Лошадь, Верблюда или Корабль, чтобы Путешествовать, либо Он также может попросить всех их (в некоторых ситуациях), чтобы Путешествовать.

Все Лошадь, Верблюд, Корабль имеют интерфейс ITransport и, конечно, этот интерфейс имеет метод Travel.

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

Поэтому я не могу просто передать их в конструктор как корабль ITransport, лошадь ITransport..... и так далее, так как мой параметр конструктора будет продолжать набухать.

Таким образом, я пришел к решению, как предполагалось (я думаю), что у HumanFactory должно быть событие, и это событие должно быть передано в конструкторе класса Human.

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

Я даже пытался решить эту проблему, создав класс Human Mapper, единственной обязанностью которого является сопоставление правильного транспорта, вызов правильного события. Это работает!

Теперь, поскольку это воображаемый пример, в реальном примере методы интерфейса принимают параметры, так как я буду с этим справляться?

Я думаю, что направление, в котором я иду, - это создание Кошмара Обслуживания.

Я вставляю код для быстрого ознакомления.

interface ITransport
{
    void Travel();
}

Мой Транспортный Завод - это как:

public class TransportFactory
{
....
    internal ITransport ProvideTransport(TransportTypes transportType)
    {
        switch (transportType)
        {
            case TransportTypes.Camel: return new Camel();
            case TransportTypes.Horse: return new Horse();
            case TransportTypes.Ship: return new Ship();
            default:
                return null;
        }
    }
}

Мой класс Человека после предложения стал следующим:

public class Human
{
    Action<Human, string> _transportRequested;

    public Human(Action<Human, string> transportRequested)
    {
        _transportRequested = transportRequested;
    }

    public void Travel()
    {
        if (_transportRequested != null)
        {
            var ev = _transportRequested;
            ev.Invoke(this, GroundTypes.Plains.ToString());
        }
    }
}

У меня есть фабрика человеческого класса, как предложено:

public class HumanFactory
{
    ITransport camel;
    ITransport ship;
    ITransport horse;
    Human _human;
    Dictionary<string, ITransport> _availableTransports;
    event Action<Human, string> transportRequested;

    public HumanFactory(TransportFactory tFactory)
    {

        horse = tFactory.ProvideTransport(TransportTypes.Horse);
        camel = tFactory.ProvideTransport(TransportTypes.Camel);
        ship = tFactory.ProvideTransport(TransportTypes.Ship);
    }

    public Human ConfigureHuman()
    {
        if (_availableTransports == null)
        {
            _availableTransports = new Dictionary<string, ITransport>();
            _availableTransports.Add(GroundTypes.Desert.ToString(), camel);
            _availableTransports.Add(GroundTypes.Sea.ToString(), ship);
            _availableTransports.Add(GroundTypes.Plains.ToString(), horse);
        }

        transportRequested += new Action<Human, string>(_human_transportRequested);
        _human = new Human(transportRequested);

        return _human;
    }

    void _human_transportRequested(Human human, string groundType)
    {
        if (_availableTransports.ContainsKey(groundType))
        {
            ITransport suitableTransport = _availableTransports[groundType];
            suitableTransport.Travel();
        }
        else
        {
            //code for handling below conditions goes here
            //I don't know what to do for this type of plain?
        }
    }
}

Я говорил о классе Mapper, который отображает правильный транспорт на правильные методы следующим образом (выглядит уродливо, но это было лучшее, что я придумал:)):

class Human_Transport_MethodMapper
{
    Dictionary<GroundTypes, ITransport> _availableTransports;
    List<EventTypes> _availableEvents;
    event Action<Human, GroundTypes, EventTypes> transportRequested;
    internal Action<Human, GroundTypes, EventTypes> transportRequesteddel;
    public Human_Transport_MethodMapper(Dictionary<GroundTypes, ITransport> availableTransports, List<EventTypes> availableEvents)
    {
        _availableEvents = availableEvents;
        _availableTransports = availableTransports;
        transportRequested += human_OnAnyEventReceived;
        transportRequesteddel = transportRequested;
    }
    internal void human_OnAnyEventReceived(Human human, GroundTypes groundType, EventTypes eventType)
    {
        if (_availableTransports.ContainsKey(groundType))
        {
            ITransport suitableTransport = _availableTransports[groundType];
            switch (eventType)
            {
                case EventTypes.Travel: suitableTransport.Travel();
                    break;
                default:
                    break; //meaning interface's correct method has not been mapped.
            }

        }
        else
        {
            //code for handling below conditions goes here
            //I don't know what to do for this type of plain?
        }
    }
}

Теперь посмотрим, что в этом случае, для метода "Путешествия", если бы было два аргумента, тогда сигнатура делегата изменилась бы. Если бы в интерфейсе ITransport было четыре или пять методов, тогда Бог поможет мне.

Я надеюсь, что объяснил мою проблему здесь. Спасибо

Изменить: я удаляю некоторый очевидный код из этого вопроса, чтобы сделать его более читабельным, а также становится более многословным

1 ответ

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

Кроме того, как я уже упоминал в предыдущем разделе, общий способ борьбы со сложной сетью событий в больших приложениях заключается в реализации шаблона EventsAggregator ( http://codebetter.com/jeremymiller/2009/07/22/braindump-on-the-event-aggregator-pattern/). В Интернете доступны миллионы различных реализаций, поэтому я оставляю вам выбор. Я буду использовать этот интерфейс для примера:

interface IEventsAggregator
{
    //sends message to network
    void Publish(object message);
    //adds object to the list of handlers
    void Subscribe(object listener);
    //removes object from the list of handlers
    void Unsubscribe(object listener);
} 

//listeners should implement this interface
interface IListener<TMessage>
{
    //handling logic for particular message
    void Handle(TMessgae message);
}

Тогда ваш код может быть реорганизован:

//you do not need human factory in this example
public class Human
{  
    private readonly IEventsAggregator _events;

    //see Handle implementation for details
    //public ITransport Transport { get; set; }

    public Human(IEventsAggregator events)
    {
        _events = events;
    }

    public void Travel(GroundTypes type)
    {
        _events.Publish(new TransportRequest(this, type));
        //see Handle implementation for details
        //if (Transport != null) Transport.Travel();
    }
}

public class TransportRequest
{
    public Human Sender { get; set; }
    public GroundTypes Ground { get; set; }

    public TransportRequest(Human sensder, GroundTypes ground)
    {
        Sender = sender;
        Ground = ground;
    }
}

public class TravelAgency : IListener<TransportRequest>, IDisposable
{
    private readonly IEventsAggregator _events;
    private readonly TransportFactory _tFactory;

    public TravelAgency(IEventsAggregator events, TransportFactory tFactory)
    {
        _events = events;
        _events.Subscribe(this);
        _tFactory = tFactory;
    }

    public void Handle(TransportRequest request)
    {
        var transort = _tFactory.ProvideTransport(...);
        //insert the handling logic here
        //there are two ways to handle this message:
        //1) you give human no control over (and no knowledge of) Transport 
        //and simply call transport.Travel(request.Sender); here
        //2) or you add ITransport property to Human class
        //and do only the assignation here 
        //request.Sender.Transport = transport;
        //and then let the human manage Transport object
    }

    public void Dispose()
    {
        _events.Unsubscribe(this);
    }
}

Вероятно, именно так я и поступил бы, если бы по какой-то причине мне нужно было строго разделить логику. Это может быть слишком сложно для такой тривиальной задачи, но я думаю, что это хороший фундамент для более крупного приложения.:) Есть, вероятно, другие подходы, хотя.

Другие вопросы по тегам