Композитный шаблон проектирования: как передать результаты из одного компонента в другой?


У меня есть следующий код:

interface IService
{
    void Execute();
}

class ServiceA : IService
{
    public void Execute() { ... }
}

class ServiceB : IService
{
    public void Execute() { ... }
}

class ServiceComposite : IService
{
    List<IService> _services = new List<IService>();

    public ServiceComposite()
    {
        _services.Add(new ServiceA());
        _services.Add(new ServiceB());
    }

    public void Execute()
    {
        foreach (IService service in _services)
        {
            service.Execute();
        }
    }
}

Проблема заключается в том, что ServiceB зависит от некоторых результатов ServiceA. Моя идея состоит в том, чтобы создать контейнерный класс для хранения результатов, а затем ввести его в ServiceA и ServiceB:

class ServiceResults
{
    public string SomeProperty {get; set;}
}

public ServiceComposite()
{
    ServiceResults result = new ServiceResults();
    _services.Add(new ServiceA(result));
    _services.Add(new ServiceB(result));
}
Мне интересно, является ли это лучшим возможным способом решения проблемы. Может быть, это нарушает какие-то принципы или правила, о которых я не знаю, или просто "кодовый запах". Каковы лучшие способы сделать это?
1 2

1 ответ:

Если ServiceB нужен результат ServiceA для его правильной работы, то почему бы не иметь ServiceB в зависимости от ServiceA?

public interface IService 
{
    void Execute();
}

public class ServiceA : IService
{
    public void Execute() { ... }
}

class ServiceB : IService
{
    public ServiceB(IService service)
    {
        Service = service;
    }

    public void Execute() { ... }

    public IService Servie { get; set; }
}

Тогда в случае, если вы выполняете все Services в коллекции, вы можете добавить ServiceBase, чтобы убедиться, что эта служба выполняется только один раз: (полный пример)

на эту базовую реализацию можно добавить: асинхронную Execute, потокобезопасную проверку выполнения InnerExecute, Flyweight factory для генерации конкретной IService, Have a ResponseBase с производными ответами для каждый Service....

public class ServiceResponse { }
public interface IService
{
    ServiceResponse Execute();
}

public abstract class ServiceBase : IService
{
    public ServiceResponse Execute()
    {
        if (_response == null)
        {
            _response = InnerExecute();
        }
        return _response;
    }

    public abstract ServiceResponse InnerExecute();

    private ServiceResponse _response;
}

public class ServiceA : ServiceBase
{
    public override ServiceResponse InnerExecute()
    {
        return new ServiceResponse();
    }
}

public class ServiceB : ServiceBase
{
    public ServiceB(IServiceFactory serviceFactory)
    {
        ServiceFactory= serviceFactory;
    }

    public override ServiceResponse InnerExecute()
    {
        return ServiceFactory.GetServices(ServicesTypes.ServiceA).Execute();
    }

    public IServiceFactory ServiceFactory { get; set; }
}

И тот, кто использует эти Services:

public enum ServicesTypes 
{
    ServiceA,
    ServiceB....
}

public interface IServiceFactory
{
    IEnumerable<IService> GetServices();

    IService GetServices(ServicesTypes servicesTypes);
}

public class SomeOtherThatExecuteServices
{
    public SomeOtherThatExecuteServices(IServiceFactory serviceFactory)
    {
        ServiceFactory = serviceFactory;
    }

    public IEnumerable<ServiceResponse> ExecuteServices()
    {
        return ServiceFactory.GetServices()
                             .Select(service => service.Execute());
    }

    public IServiceFactory ServiceFactory { get; set; }
}

возможно, вы захотите получить доступ к фабрике с помощью какого-то ключа отображения, и, вероятно, вам понадобится правильная логика в SomeOtherThatExecuteServices, но все это приведет вас на правильный путь (+используя какой-то правильный контейнер IoC)