使用多个提供程序的客户端应用程序使用什么设计/模式? [英] What design/pattern to use for a Client application using multiple providers?

查看:23
本文介绍了使用多个提供程序的客户端应用程序使用什么设计/模式?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

这是一个与设计相关的问题.

假设我们有一个名为 ClientAPI 的公共 API,其中包含一些 Web 方法,例如 CreateAccount、GetAccount.根据客户的不同,我们使用许多不同的供应商来满足这些要求.

Lets say we have a public API called ClientAPI with a few web methods like CreateAccount, GetAccount. Depending on the customer, we use a number of different providers to fulfil these requests.

假设我们有 ProviderA、ProviderB 和 ProviderC.

So say we have ProviderA and ProviderB and ProviderC.

ProviderA 有一个 CreateAccount 的方法签名/实现,它只需要(名字、姓氏)并使用 ProviderA 创建一个帐户.

ProviderA has a method signature/implementation of CreateAccount that needs (Firstname, Lastname) only and creates an account with ProviderA.

ProviderB 具有 CreateAccount 的方法签名/实现,需要(名字、姓氏、电子邮件、DOB)并在 ProviderB 中创建帐户.

ProviderB has a method signature/implementation of CreateAccount that needs (Firstname, Lastname, Email, DOB) and creates an account with ProviderB.

ProviderC 具有 CreateAccount 的方法签名/实现,需要(昵称、公司密钥、电子邮件)并使用 ProviderC 创建帐户.

ProviderC has a method signature/implementation of CreateAccount that needs (Nickname, CompanyKey, Email) and creates an account with ProviderC.

客户不需要知道或关心他们是哪个提供者.当调用客户端 API 方法 CreateAccount 时,客户端 API 将计算出它需要调用的提供者并调用该提供者方法.

The Client doesn’t need to know or care about which provider they are. When the Client API method CreateAccount is called, the client api will work out what provider(s) it needs to call and invokes that Providers Method.

所以我在这里有两个问题.

1) 为该模型实现的最佳设计/模式是什么?还要记住,提供者的数量将会增加——我们将增加更多的提供者.

1) What is the best design/pattern to implement for this model? Also bearing in mind that the number of providers will grow – we will be adding more providers.

2) 关于传递参数——目前ClientAPI CreateAccount方法签名是一大行变量,如果新的provider需要一个新值,方法签名会添加另一个变量,这显然打破了旧的实现等.将方法签名中的参数数组/列表/字典传递给下面的提供程序是一种好习惯,还是有更好的方法?

2) Regarding passing parameters – currently the ClientAPI CreateAccount method signature is a big line of variables, and if a new provider needs a new value, the method signature has another variable added to it, which obviously breaks the old implementations etc. Is it a good practice to pass an array/list/dictionary of parameters in the method signature and into the providers below, or is there a better way?

推荐答案

这确实是一个有趣的问题.我在我从事的不同项目中遇到过这样的问题.阅读您的问题后,我注意到您面临两个不同的挑战:

It is indeed an interesting question. I've encountered myself few problems like this in different projects I worked on. After reading your questions, I noticed you have two different challenges:

  1. 通过 ClientAPI
  2. 正确选择提供者
  3. 每个提供程序所需的可变数量和类型的参数.

在设计服务或新功能时,我喜欢通过尽量减少为支持新功能而需要进行的更改数量来对设计进行推理.在您的情况下,这将是添加新的身份验证提供程序.我现在想到了至少三种不同的实现方式.在我看来,没有完美的解决方案.您必须根据权衡选择其中之一.下面,我尝试提出几个解决上述两个痛点的选项以及它们的优缺点.

When I'm designing a service or new feature, I like to reason about design by trying to minimize the number of changes I would need to make in order to support a new functionality. In your case, it would be the addition of new authentication provider. At least three different ways to implement that come to my mind right now. In my opinion, there is no perfect solution. You will have to choose one of them based on tradeoffs. Below, I try to present few options addressing these two pain points listed above along with their advantages and disadvantages.

无论我们做什么,无论我们使用多态性抽象复杂性有多好,总会有不同的类型或组件通过需要不同的信息集来将自己与它的simblings 区分开来.根据您希望在设计中投入多少精力以保持强类型以及多态抽象的不同程度,在添加新功能时需要进行更多更改.下面是一个实现示例,它不对用户提供的所有类型的信息强制类型.

No matter what we do, no matter how good we are abstracting complexity using polymorphism, there is always a different type or component that distinguishes itself from its simblings by requiring a different set of information. Depending on how much effort you want to put in your design to keep it strongly typed and on how different your polymorphic abstractions are, it will require more changes when adding new features. Below there is an example of implementation that does not enforce types for all kinds of information provided by the user.

public class UserData {
    private AuthType type;
    private String firstname;
    private String lastname;
    private Map<String, String> metadata;
}

public enum AuthType {
    FACEBOOK, GPLUS, TWITTER;
}

public interface AuthProvider {
    void createAccount(UserData userData);
    void login(UserCredentials userCredentials);
}

public class AuthProviderFactory {
    public AuthProvider get(AuthType type) {
        switch(type) {
            case FACEBOOK:
                return new FacebookAuthProvider();
            case GPLUS:
                return new GPlusAuthProvider();
            case TWITTER:
                return new TwitterAuthProvider();
            default:
                throw new IllegalArgumentException(String.format('Invalid authentication type %s', type));
        }
    }
}

// example of usage
UserData userData = new UserData();
userData.setAuthType(AuthType.FACEBOOK);
userData.setFirstname('John');
userData.setLastname('Doe');
userData.putExtra('dateOfBirth', LocalDate.of(1997, 1, 1));
userData.putExtra('email', Email.fromString('john.doe@gmail.com'));

AuthProvider authProvider = new AuthProviderFactory().get(userData.getType());
authProvider.createAccount(userData);

优势

  • 只需向 AuthTypeAuthProviderFactory 添加新条目即可支持新的提供者.
  • 每个 AuthProvider 都确切地知道它需要什么来执行公开的操作(createAccount() 等).逻辑和复杂性得到了很好的封装.
  • Advantages

    • New providers can be supported by simply adding new entries to AuthType and AuthProviderFactory.
    • Each AuthProvider knows exactly what it needs in order to perform the exposed operations (createAccount(), etc). The logic and complexity are well encapsulated.
      • UserData 中的少数参数不会是强类型的.一些需要额外参数的 AuthProvider 将不得不查找它们,即 metadata.get('email').
      • Few parameters in UserData won't be strongly typed. Some AuthProvider that require additional parameters will have to lookup them i.e. metadata.get('email').

      我假设负责调用 AuthProviderFactory 的组件已经知道它需要的提供程序的类型,因为它必须用所有的内容填写 UserData成功的 createAccount() 调用所需的信息.那么,让这个组件创建正确类型的 UserData 怎么样?

      I assume that the component in charge of invoking AuthProviderFactory already knows a little bit about the type of provider it needs since it will have to fill out UserData with all the information needed for a successful createAccount() call. So, what about letting this component create the correct type of UserData?

      public class UserData {
          private String firstname;
          private String lastname;
      }
      
      public class FacebookUserData extends UserData {
          private LocalDate dateOfBirth;
          private Email email;
      }
      
      public class GplusUserData extends UserData {
          private Email email;
      }
      
      public class TwitterUserData extends UserData {
          private Nickname nickname;
      }
      
      public interface AuthProvider {
          void createAccount(UserData userData);
          void login(UserCredentials userCredentials);
      }
      
      public class AuthProviderFactory {
          public AuthProvider get(UserData userData) {
              if (userData instanceof FacebookUserData) {
                  return new FacebookAuthProvider();
              } else if (userData instanceof GplusUserData) {
                  return new GPlusAuthProvider();
              } else if (userData instanceof TwitterUserData) {
                  return new TwitterAuthProvider();
              }
              throw new IllegalArgumentException(String.format('Invalid authentication type %s', userData.getClass()));
          }
      }
      
      // example of usage
      FacebookUserData userData = new FacebookUserData();
      userData.setFirstname('John');
      userData.setLastname('Doe');
      userData.setDateOfBirth(LocalDate.of(1997, 1, 1));
      userData.setEmail(Email.fromString('john.doe@gmail.com'));
      
      AuthProvider authProvider = new AuthProviderFactory().get(userData);
      authProvider.createAccount(userData);
      

      优势

      • 包含强类型属性的 UserData 的特殊形式.
      • 只需创建新的 UserData 类型并添加新条目 AuthProviderFactory,即可支持新的提供者.
      • 每个 AuthProvider 都确切地知道它需要什么来执行公开的操作(createAccount() 等).逻辑和复杂性得到了很好的封装.
      • Advantages

        • Specialized forms of UserData containing strongly typed attributes.
        • New providers can be supported by simply creating new UserData types and adding new entries AuthProviderFactory.
        • Each AuthProvider knows exactly what it needs in order to perform the exposed operations (createAccount(), etc). The logic and complexity are well encapsulated.
          • AuthProviderFactory 使用 instanceof 来选择合适的 AuthProvider.
          • UserData 子类型的爆炸式增长和潜在的代码重复.
          • AuthProviderFactory uses instanceof for selecting the proper AuthProvider.
          • Explosion of UserData subtypes and potentially duplication of code.

          我们可以尝试通过在我们之前的设计中重新引入枚举 AuthType 并使我们的 UserData 子类更通用来消除代码重复.

          We can try removing code duplication by reintroducing the enum AuthType to our previous design and making our UserData subclasses a little bit more general.

          public interface UserData {
              AuthType getType();
          }
          
          public enum AuthType {
              FACEBOOK, GPLUS, TWITTER;
          }
          
          public class BasicUserData implements UserData {
              private AuthType type:
              private String firstname;
              private String lastname;
          
              public AuthType getType() { return type; }
          }
          
          public class FullUserData extends BasicUserData {
              private LocalDate dateOfBirth;
              private Email email;
          }
          
          public class EmailUserData extends BasicUserData {
              private Email email;
          }
          
          public class NicknameUserData extends BasicUserData {
              private Nickname nickname;
          }
          
          public interface AuthProvider {
              void createAccount(UserData userData);
              void login(UserCredentials userCredentials);
          }
          
          public class AuthProviderFactory {
              public AuthProvider get(AuthType type) {
                  switch(type) {
                      case FACEBOOK:
                          return new FacebookAuthProvider();
                      case GPLUS:
                          return new GPlusAuthProvider();
                      case TWITTER:
                          return new TwitterAuthProvider();
                      default:
                          throw new IllegalArgumentException(String.format('Invalid authentication type %s', type));
                  }
              }
          }
          
          // example of usage
          FullUserData userData = new FullUserData();
          userData.setAuthType(AuthType.FACEBOOK);
          userData.setFirstname('John');
          userData.setLastname('Doe');
          userData.setDateOfBirth(LocalDate.of(1997, 1, 1));
          userData.setEmail(Email.fromString('john.doe@gmail.com'));
          
          AuthProvider authProvider = new AuthProviderFactory().get(userData.getType());
          authProvider.createAccount(userData);
          

          优势

          • 包含强类型属性的 UserData 的特殊形式.
          • 每个 AuthProvider 都确切地知道它需要什么来执行公开的操作(createAccount() 等).逻辑和复杂性得到了很好的封装.
          • Advantages

            • Specialized forms of UserData containing strongly typed attributes.
            • Each AuthProvider knows exactly what it needs in order to perform the exposed operations (createAccount(), etc). The logic and complexity are well encapsulated.
              • 除了向 AuthProviderFactory 添加新条目并为 UserData 创建新子类型之外,新的提供者还需要在枚举 AuthType 中添加一个新条目.
              • 我们仍然有 UserData 子类型的爆炸式增长,但现在这些子类型的可重用性已经提高.
              • Besides adding new entries to AuthProviderFactory and creating new subtype for UserData, new providers will require a new entry in the enum AuthType.
              • We still have explosion of UserData subtypes but now the reusability of these subtypes has increased.

              我很确定这个问题还有其他几种解决方案.正如我上面提到的,也没有完美的解决方案.您可能需要根据他们的权衡和您想要实现的目标来选择一个.

              Im pretty sure there are several other solutions for this problem. As I mentioned above, there are no perfect solution either. You might have to choose one based on their tradeoffs and the goals you want to achieve.

              今天我的灵感不是很好,所以如果我想到其他事情,我会继续更新这篇文章.

              I'm not very well inspired today, so I will keep updating this post if something else comes to my mind.

              这篇关于使用多个提供程序的客户端应用程序使用什么设计/模式?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆