blog

まだif elseばかり書いているのか?

テンプレートパターンと戦略パターン - 多様性ログイン\n考えるべき質問\nログインシナリオでは、ユーザー名とパスワードによるログインに加えて\n\nでは、このような要求があったとき、通常はどのように...

Dec 19, 2020 · 5 min. read
シェア

テンプレートモードとストラテジーモード - ダイバーシティログイン

質問は思考を刺激します

ログインシナリオでは、ユーザー名とパスワードによるログインに加え

では、このような要件が与えられた場合、一般的にはどのように実装するのでしょうか?

従来の実装

dto

@Data
public class AuthLoginDto {
 private String username;
 private String password;
 private String phone;
 private String code;
 private String openId;
 @NotNull(message = "ログインタイプはNULLにはできない")
 private Integer loginType;
 
 public void validData(BindingResult bindingResult){
 if(bindingResult.hasErrors()){
 StringBuilder stringBuilder=new StringBuilder();
 for(ObjectError oe:bindingResult.getAllErrors()){
 stringBuilder.append(oe.getDefaultMessage()+"
");
 }
 throw new ValidException(stringBuilder.toString());
 }
 }
}

controller

@PostMapping("/login")
public R loginAuth(@RequestBody @Validated AuthLoginDto authLoginDto, BindingResult bindingResult) {
 authLoginDto.validData(bindingResult);
 // ログインロジック
 Integer loginType = authLoginDto.getLoginType() 
}
private R doLogin(Integer loginType) {
 switch(loginType){
 case 1:
 // ユーザー名とパスワードによるログイン
 case 2:
 // 携帯電話認証コードログイン
 case 3:
 // 携帯電話パスワードログイン
 default:
 // このタイプのログインは当面サポートされない
 }
}

この実装を見れば、機能はすぐに実装できます。また、ログインメソッドの追加はケースを追加するだけで、ビジネスロジックを素早く実装できます。

要件を追加する場合、元のクラス、元のメソッドを修正する必要があり、スイッチ・ブランチが非常に多くなります。コード・クリーナーである人は、このようなコードを見て、どのように修正し、最適化すればよいかを考えます。

では、どのように最適化を進めるべきなのでしょうか?

デザインパターンを解決することができます、最初に戦略パターンとテンプレートパターンを理解し、自由に使用するために、彼が何であるかを知っています。

戦略パターン

戦略パターンは行動パターンであり、多くのif elseに代わる素晴らしいものです。ストラテジーパターンが解決してくれるシナリオは、一般的に、同じような置き換え可能な行動ロジックを持つアルゴリズムシナリオです。

例えば

  • さまざまな種類の取引
  • 異なるログインメソッド
  • 一意なIDポリシーの生成

上記のシナリオはすべて、strategyパターンを使用してラップし、外部で使用できるようにすることができます。

テンプレートパターン

テンプレート・パターンの中心的な設計思想は、抽象クラス内で抽象メソッドの実行順序を定義し、抽象メソッドはサブクラスでのみ実装されるように設定することであり、独立してアクセスできるように設計されているわけではありません。簡単に言えば、わかりやすく並べるということです。

リファクタリング

コード構造

LoginTypeEnum列挙型を定義します。

public enum LoginTypeEnum {
 NORMAL(0, "アカウント パスワード ログイン"),
 PHONE_PWD(1, "携帯電話番号とパスワードによるログイン "),
 PHONE_CODE(2, "携帯電話認証コードログイン "),
 (3, "認証ログイン");
 private int code;
 private String memo;
 LoginTypeEnum(int code, String memo) {
 this.code = code;
 this.memo = memo;
 }
 // set get 
 ...
}

ログインインターフェイス Login

public interface Login {
 R doLogin(AuthLoginDto authLoginDto) throws BizException;
}

ログイン抽象クラス AbstractLogin

@Slf4j
public abstract class AbstractLogin implements Login{
	// クラスが初期化されると、サブクラスのオブジェクトがマップに格納される。
 public static ConcurrentHashMap<Integer, AbstractLogin> loginMap = new ConcurrentHashMap<>();
 @PostConstruct
 public void init(){
 loginMap.put(getLoginType(),this);
 }
 @Override
 public R doLogin(AuthLoginDto authLoginDto) throws BizException {
 log.info("begin AbstractLogin.doLogin:"+authLoginDto);
 // 最初のステップは、パラメーターのバリデーションを完了させることである
 validate(authLoginDto);
 // ログインチェック
 TbMember member = doProcessor(authLoginDto);
 // jwtトークンを生成する
 Map<String,Object> payLoad = new HashMap<>();
 payLoad.put("uid", member.getId());
 payLoad.put("exp", DateTime.now().plusHours(1).toDate().getTime()/1000);
 String token= JwtGeneratorUtil.generatorToken(payLoad);
 return new R.Builder().setData(token).buildOk();
 }
 /**
 * サブクラスで独自のログインタイプを宣言する。
 * @return
 */
 protected abstract int getLoginType();
 /**
 * バリデーションを完了するサブクラス
 * @param authLoginDto
 */
 protected abstract void validate(AuthLoginDto authLoginDto);
 /**
 * ログインチェック
 * @param authLoginDto
 */
 protected abstract TbMember doProcessor(AuthLoginDto authLoginDto);
}

実装クラス - ユーザー名 パスワード ログイン

@Slf4j
@Service
public class NormalLoginProcessor extends AbstractLogin{
 @Autowired
 TbMemberMapper tbMemberMapper;
 @Override
 public int getLoginType() {
 return LoginTypeEnum.NORMAL.getCode();
 }
 @Override
 public void validate(AuthLoginDto authLoginDto) {
 if(StringUtils.isBlank(authLoginDto.getUsername()) || StringUtils.isBlank(authLoginDto.getPassword())){
 throw new ValidException("アカウントやパスワードが空であってはならない ");
 }
 }
 @Override
 public TbMember doProcessor(AuthLoginDto authLoginDto) {
 log.info("begin NormalLoginProcessor.doProcessor:" + authLoginDto);
 TbMemberExample tbMemberExample = new TbMemberExample();
 tbMemberExample.createCriteria()
 .andStateEqualTo(1)
 .andUsernameEqualTo(authLoginDto.getUsername());
 List<TbMember> members=tbMemberMapper.selectByExample(tbMemberExample);
 // データベース検証
 if(members == null || members.size() == 0){
 throw new BizException("ユーザー名またはパスワードのエラー");
 }
 // パスワード検証
 if(!DigestUtils.md5DigestAsHex(authLoginDto.getPassword().getBytes())
 .equals(members.get(0).getPassword())){
 throw new BizException("ユーザー名またはパスワードのエラー");
 }
 return members.get(0);
 }
}

controllerレイヤーログインインターフェース

@RestController
public class LoginController {
 @PostMapping("/login")
 public R loginAuth(@RequestBody @Validated AuthLoginDto authLoginDto, BindingResult bindingResult){
 authLoginDto.validData(bindingResult);
 // ログインロジックは、ログインのタイプに応じて、対応するクラスの実装を見つける
 Login login= AbstractLogin.loginMap.get(authLoginDto.getLoginType());
 if(login==null){
 throw new BizException("このタイプのログインは当面サポートされない");
 }
 // ログインロジックを実行する
 return login.doLogin(authLoginDto);
 }
}

概要

戦略モデル:

  • ログインロジックはloginメソッドで指定されたクラスに引き渡され、異なるログインタイプを通してログインロジックを実行します。

  • new ConcurrentHashMap<>(); AbstractLoginクラスがロードされたら、すべてのテンプレート・オブジェクトを

テンプレートモデル:

  • まず、Loginインターフェースが定義され、ログイン関連の抽象メソッドが定義されています。

  • 外部呼び出し用に抽象クラスAbstractLoginでログイン関連メソッドを実装します

  • ログイン方法は3つのステップに分かれています:1、フロントエンドのログインデータの検証、2、doProcessorのログインロジック、3、トークンの生成。

  • パラメータ・バリデーションとログインは抽象メソッドとして定義され、サブクラスからのみアクセス可能です。

Read next

エンタープライズ・デジタル・インテリジェンス変革の機会

今日、世界はVUCAの時代に突入しました。ボラティリティ(変動性)、不確実性、複雑性、曖昧性に満ちた世界に直面しています。 ボラティリティ」とは、物事が非常に速く変化すること、「不確実性」とは、次にどこに問いかければよいのかわからないこと、そして「複雑性」とは、アプリケーションのあらゆる部分が、「曖昧」であることを意味します。...

Dec 19, 2020 · 2 min read