使用多个提供程序的客户端应用程序应使用什么设计/模式?

Spo*_*Dev 6 architecture design-patterns strategy-pattern adapter

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

\n\n

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

\n\n

假设我们有 ProviderA、ProviderB 和 ProviderC。

\n\n

ProviderA 具有 CreateAccount 的方法签名/实现,仅需要(名字,姓氏)并使用 ProviderA 创建帐户。

\n\n

ProviderB 具有 CreateAccount 的方法签名/实现,需要(名字、姓氏、电子邮件、出生日期)并使用 ProviderB 创建帐户。

\n\n

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

\n\n

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

\n\n

我这里有两个问题。

\n\n

1)该模型的最佳设计/模式是什么?另请记住,提供程序的数量将会增加 \xe2\x80\x93,我们将添加更多提供程序。

\n\n

2)关于传递参数\xe2\x80\x93,目前ClientAPI CreateAccount方法签名是一大行变量,如果新的提供者需要新值,方法签名会添加另一个变量,这显然破坏了旧的实现等等。将方法签名中的参数数组/列表/字典传递到下面的提供程序中是否是一个好习惯,或者是否有更好的方法?

\n

Tre*_*ein 4

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

  1. 适当选择供应商ClientAPI
  2. 每个提供者所需的参数数量和类型不同。

当我设计服务或新功能时,我喜欢通过尽量减少支持新功能所需的更改数量来推理设计。就您而言,这将是添加新的身份验证提供程序。我现在至少想到了三种不同的实施方法。在我看来,没有完美的解决方案。您必须根据权衡选择其中之一。下面,我尝试提出一些解决上面列出的两个痛点的选项及其优点和缺点。

放松类型

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

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);
Run Code Online (Sandbox Code Playgroud)

优点

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

缺点

  • 很少有参数UserData不会被强类型化。一些AuthProvider需要额外参数的将不得不查找它们,即metadata.get('email')

打字的UserData

我假设负责调用的组件AuthProviderFactory已经对它所需的提供程序类型有所了解,因为它必须填写UserData成功调用所需的所有信息createAccount()。那么,让这个组件创建正确的类型怎么样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);
Run Code Online (Sandbox Code Playgroud)

优点

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

缺点

  • AuthProviderFactory用于instanceof选择正确的AuthProvider.
  • 子类型的爆炸UserData和潜在的代码重复。

UserData重新审视打字

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

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);
Run Code Online (Sandbox Code Playgroud)

优点

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

缺点

  • 除了添加新条目AuthProviderFactory并为其创建新子类型之外UserData,新提供者还需要在枚举中添加新条目AuthType
  • 我们仍然有UserData子类型的爆炸式增长,但现在这些子类型的可重用性已经增加。

概括

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

我今天没有太多灵感,所以如果我想到其他事情,我会继续更新这篇文章。