这确实是一个有趣的问题。我在从事的不同项目中很少遇到这样的问题。阅读您的问题后,我注意到您面临两个不同的挑战:
- 适当选择供应商
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('[email protected] /cdn-cgi/l/email-protection'));
AuthProvider authProvider = new AuthProviderFactory().get(userData.getType());
authProvider.createAccount(userData);
优点
- 只需添加新条目即可支持新的提供商
AuthType
and AuthProviderFactory
.
- Each
AuthProvider
确切地知道执行公开操作需要什么(createAccount()
, ETC)。逻辑和复杂性都得到了很好的封装。
缺点
- 中的几个参数
UserData
不会是强类型的。一些AuthProvider
需要额外参数的必须查找它们,即metadata.get('email')
.
Typed 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('[email protected] /cdn-cgi/l/email-protection'));
AuthProvider authProvider = new AuthProviderFactory().get(userData);
authProvider.createAccount(userData);
优点
- 专门形式
UserData
包含强类型属性。
- 只需创建新的即可支持新的提供商
UserData
类型并添加新条目AuthProviderFactory
.
- Each
AuthProvider
确切地知道执行公开操作需要什么(createAccount()
, ETC)。逻辑和复杂性都得到了很好的封装。
缺点
-
AuthProviderFactory
uses instanceof
用于选择合适的AuthProvider
.
- 爆炸式
UserData
子类型和潜在的代码重复。
Typed 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('[email protected] /cdn-cgi/l/email-protection'));
AuthProvider authProvider = new AuthProviderFactory().get(userData.getType());
authProvider.createAccount(userData);
优点
- 专门形式
UserData
包含强类型属性。
- Each
AuthProvider
确切地知道执行公开操作需要什么(createAccount()
, ETC)。逻辑和复杂性都得到了很好的封装。
缺点
- 除了添加新条目之外
AuthProviderFactory
并创建新的子类型UserData
,新的提供者将需要在枚举中添加一个新条目AuthType
.
- 我们仍然有爆炸
UserData
子类型,但现在这些子类型的可重用性已经增加。
Summary
我很确定这个问题还有其他几种解决方案。正如我上面提到的,也没有完美的解决方案。您可能必须根据他们的权衡和您想要实现的目标来选择一个。
我今天没有太多灵感,所以如果我想到其他事情,我会继续更新这篇文章。