这确实是一个有趣的问题。在我从事的不同项目中,我遇到过这样的几个问题。阅读您的问题后,我注意到您面临两个不同的挑战:
-
ClientAPI 正确选择提供程序
- 每个提供程序所需的参数数量和类型不定。
在设计服务或新功能时,我喜欢通过尽量减少为支持新功能而需要进行的更改来进行设计推理。在您的情况下,这将是添加新的身份验证提供程序。我现在想到了至少三种不同的实现方式。在我看来,没有完美的解决方案。您将不得不根据权衡选择其中之一。下面,我尝试提出一些解决上述两个痛点的选项以及它们的优缺点。
类型放松
无论我们做什么,无论我们使用多态性抽象复杂性有多好,总会有一个不同的类型或组件通过需要不同的信息集来区别于它的同类。根据您希望在设计中投入多少精力以保持强类型以及多态抽象的不同程度,在添加新功能时需要进行更多更改。下面是一个不强制用户提供的各种信息类型的实现示例。
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);
优势
- 只需向
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);
优势
- 包含强类型属性的
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);
优势
- 包含强类型属性的
UserData 的特殊形式。
- 每个
AuthProvider 都确切地知道它需要什么才能执行公开的操作(createAccount() 等)。逻辑和复杂性得到了很好的封装。
缺点
- 除了向
AuthProviderFactory 添加新条目并为UserData 创建新子类型之外,新提供者还需要在枚举AuthType 中添加新条目。
-
UserData 子类型仍然激增,但现在这些子类型的可重用性有所提高。
总结
我很确定这个问题还有其他几种解决方案。正如我上面提到的,也没有完美的解决方案。您可能必须根据他们的权衡和您想要实现的目标来选择一个。
我今天的灵感不是很好,所以如果我有其他想法,我会继续更新这篇文章。