在返回具体类

时间:2016-11-18 14:39:22

标签: c# asynchronous design-patterns factory-pattern

我正在使用一段代码,并且可以使用一些指针。

我正在为前端网站创建API。此API可以基于租户调用各种后端系统。要调用的系统保存在数据库的租户条目中。

我的API项目包含几个不同的项目:

  • API
  • BusinessLogic
  • 数据
  • DTO

我的API调用我的逻辑层,例如检索客户对象。在此调用中,传递了tenantId(取自授权身份声明)。我的逻辑做了一些检查 然后想要调用租户特定的后端系统。为此,我需要执行以下操作:

  • 从数据库中检索租户设置
  • 找出配置的后端,并创建该数据类的实例
  • 调用此数据类,并检索客户

数据类实现了一个接口IBackendConnector。

因为我的所有逻辑类都可能调用特定于租户的数据,所以我想创建一个接受tenantId的工厂,并返回一个实现IBackendConnector的类。 调用数据库以检索租户是异步的,API调用本身是异步的。

我创建了一个名为' DataFactory'的抽象类。具有(公共和私有)属性IBackendConnector,让它命名为Connector。它还有一个构造函数,它需要一个tenantid,它保存为 私人财产。每个逻辑类都实现此DataFactory。我需要调用后端的那一刻,我可以简单地调用一些漂亮而干净的代码:

Connector.MethodIWhishToCall();

在公共属性的getter中,我检查私有属性中是否有值,如果没有,则需要检索租户设置,并创建正确的数据类,因此我们只创建 我们需要的时候上课。这是我失败的地方,因为我无法在此属性getter中调用异步方法(没有死锁)。

我更改了这段代码,并且对我使用的当前解决方案不满意,这是一个在每个逻辑类(需要它)中实现的抽象类,带有静态其中的异步方法。

public abstract class ExternalDataFactory
{
    public static async Task<IBackendDataConnector> GetDataHandler(string tenantId)
    {
        var logic = new TenantLogic();
        var tenant = await logic.GetTenantById(tenantId);  
        var settings = tenant.Settings;

        if (settings == null)
            throw new ArgumentNullException("Settings");

        switch (settings.EnvironmentType)
        {
            case Constants.EnvironmentType.ExampleA:
                return new DataHandlerClass(settings);
            case Constants.EnvironmentType.ExampleB:
                throw new NotImplementedException();
            default:
                throw new Exception("Invalid EnvironmentType");
        };
    }
}

使用此处理程序的每个方法都调用以下代码:

var handler = await GetDataHandler(tenantId);
return await handler.DoSomething();

或者在一行中:

return await (await GetDataHandler(tenantId)).DoSomething();

最终目标:所有逻辑类必须能够调用正确的数据类,而不必担心哪个数据类,或者必须先手动检索设置并将其传递给工厂。工厂 应该能够检索设置(如果设置为null)并返回正确的IBackendConnector实现。什么是最佳实践/最佳模式?

TL / DR:尝试实现需要调用异步方法的工厂模式,以决定返回哪个具体类。最好的方法是什么?

1 个答案:

答案 0 :(得分:1)

我没有看到代码中有任何理由来定义抽象类,只有它定义的方法是静态的。 所以我建议的第一件事就是让你的课堂不抽象。

让我们将GetDataHandler重命名为CreateDataHandlerAsync,以清楚地向您的工厂的消费者表明此方法为async。您还可以看到我从方法签名中删除了static。 对我而言,它做了太多事情,让TenantLogic成为这个工厂的依赖。

public interface ITenantLogic
{    
   Task<Tenant> GetTenantByIdAsync(int tenantId);
}

public class TenantLogic: ITenantLogic
{
   public async Task<Tenant> GetTenantByIdAsync(int tenantId)
   {
      // logic to get the tenant goes here
   }
}

现在让我们在工厂中将其定义为依赖

public class ExternalDataFactory
{
   private readonly ITenantLogic _tenantLogic;
   public ExternalDataFactory(ITenantLogic tenantLogic)
   {
       if(tenantLogic == null) throw new ArgumentNullException("tenantLogic");
       _tenantLogic = tenantLogic;
   }

   public async Task<IBackendDataConnector> CreateDataHandlerAsync(string tenantId)
        {
            var tenant = await _tenantLogic.GetTenantByIdAsync(tenantId);  
            var settings = tenant.Settings;

            if (settings == null)
                throw new ArgumentException("Specified tenant  has no settings defined");

            switch (settings.EnvironmentType)
            {
                case Constants.EnvironmentType.ExampleA:
                    return new DataHandlerClass(settings);
                case Constants.EnvironmentType.ExampleB:
                    throw new NotImplementedException();
                default:
                    throw new Exception("Invalid EnvironmentType");
            };
        }
    }

我建议您更改DoSomething的方法名称,因为它正在运行async使其成为DoSomethingAsync()。根据经验,我建议将Async postfix到你的所有异步方法。

现在是客户端代码。

var factory = new ExternalDataFactory(new TenantLogic());
IBackendDataConnector dataConnector =
   await factory.CreateDataHandlerAsync(tenantId);
return await dataConnector.DoSomethingAsync();

最后但同样重要的是,我还会考虑如何在switch/case方法中摆脱CreateDataHandlerAsync

我认为这个版本可以是一个很好的起点 - 它将达到你的最终目标 - 在支持新环境后,你将实现具体的数据类,添加一个新的case语句(严肃的说,我会删除它)并且您的所有客户都可以享受工厂支持新环境的好处。

希望这有帮助。

修改

进一步研究这个我想再补充一点。 我真的不认为ExternalDataFactory应该了解房客,如何找房客等。

我认为工厂的消费者代码应该处理租户的检索并将环境传递到factory,这反过来会创建一个具体的数据类并将其返回。

正如我之前所说,我们可以使工厂更加灵活。优雅(IMO),如果我们可以摆脱switch/case

让我们首先重新定义与数据连接器相关的类/接口

public interface IBackendDataConnector
{
    public Task<Something> DoSomethingAsync(Settings settings);
    public bool CanHandleEnvironment(EnvironmentType environment);
}

public abstract class DataHandlerAbstractBase: IBackendDataConnector
{
   protected abstract EnvironmentType Environment { get; }

   // interface API
   public abstract async Task<Something> DoSomethingAsync(Settings settings);

   public virtual bool CanHandleEnvironment(EnvironmentType environment)
   {
       return environment == Environment;
   }
}

public class DataHandlerClass: DataHandlerAbstractBase
{
   protected override EnvironmentType Environment 
   {
     get { return EnvironmentType.ExampleA; }
   }

   public override async Task<Something> DoSomethingAsync(Settings settings)
   {
     // implementation goes here
   }
}

现在有了上述内容,让我们重新审视ExternalDataFactory

 public class ExternalDataFactory
 {
    private readonly IEnumerable<IBackendDataConnector> _dataConnectors =
          new [] {new DataHandlerClass() /* other implementations */}

    public IBackendDataConnector CreateDataHandler(Settings setting)
    {                
       IBackendDataConnector connector = _dataConnectors
.FirstOrDefault(
     c => c.CanHandleEnvironment(setting.EnvironmentType));

       if (connector == null)
       {
           throw new ArgumentException("Unsupported environment type");
       }

       return connector;
    }
 }

很少有关于重构的话。

如前所述,工厂方法无法通过它的id等知道/打扰租户等所有它能做的就是根据传递的环境创建具体的数据类(它从传入的环境中读取) )。

现在每个具体的数据类都可以说它是否可以处理给定的环境,所以我们只需从工厂委托调用它。每个具体的数据类实现都必须定义Environment属性(由于继承自抽象基类DataHandlerAbstractBase

通过这些更改,现在客户将需要照顾租户并将其传递给工厂

var tenantLogic = new TenantLogic();
var tenant = await tenantLogic.GetTenantByIdAsync(tenantId); 
var factory = new ExternalDataFactory();
IBackendDataConnector dataConnector =
        factory.CreateDataHandler(tenant.Settings);
return await dataConnector.DoSomethingAsync(tenant.Settings);