PR

Interface使用

広告

サンプルコード

InterfaceAnimalController.java

package com.example.demo.controller.animal;

import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import com.example.demo.common.icommon.IAction;
import com.example.demo.common.icommon.IAction.ACTION;
import com.example.demo.entity.AnimalKindsEntity;
import com.example.demo.form.AnimalForm;
import com.example.demo.service.animalinterface.AnimalClearService;
import com.example.demo.service.animalinterface.AnimalCommonService;
import com.example.demo.service.animalinterface.AnimalDisplayService;

import io.micrometer.common.util.StringUtils;

/** interfaceを利用した分岐コントローラー */
@Controller
@RequestMapping("/animal/interface")
public class InterfaceAnimalController
{
	/** 処理を実装するサービスクラスを格納 */
	private final Map<ACTION, IAction> action;
	/** 共通処理クラス */
	private final AnimalCommonService  animalCommonService;

	public InterfaceAnimalController(
			final AnimalCommonService animalCommonService, final AnimalDisplayService displayService,
			final AnimalClearService clearService
			)
	{
		this.animalCommonService = animalCommonService;

		this.action = new HashMap<>();
		this.action.put(ACTION.DISPLAY, displayService);
		this.action.put(ACTION.CLEAR, clearService);
	}

	// ==== ModelAttribute ====

	@ModelAttribute("animals")
	public Map<String, String> animals()
	{
		final List<AnimalKindsEntity> animal_kinds = animalCommonService.getAllAnimalKindData();

		final Map<String, String> animals = new TreeMap<String, String>();
		for (final AnimalKindsEntity animal_kind : animal_kinds)
		{
			animals.put(animal_kind.getId().toString(), animal_kind.getName());
		}

		return animals;
	}

	@ModelAttribute("form")
	public AnimalForm form()
	{
		return new AnimalForm("", null, Collections.emptyList());
	}

	@ModelAttribute("display")
	public String display()
	{
		return ACTION.DISPLAY.getAction();
	}

	@ModelAttribute("clear")
	public String clear()
	{
		return ACTION.CLEAR.getAction();
	}

	@ModelAttribute("action")
	public String action()
	{
		return "/animal/interface/submit";
	}

	// ==========================


	/** 初期表示 */
	@GetMapping("/index")
	public String index(final Model model)
	{
		model.addAttribute("test", "初期表示");
		return "animal";
	}

	@PostMapping("/submit")
	public String execute(
			@RequestParam("action") final String action,
			@ModelAttribute("form") final AnimalForm animalForm,
			final BindingResult error,
			final Model model
			)
	{
		model.addAttribute("form", animalForm);

		// 初期表示ボタン
		if (StringUtils.isBlank(action))
		{
			return "redirect:/animal/interface/index";
		}

		// 押されたボタンにより、ACTIONを抽出
		final ACTION executeAction = ACTION.getACTION(action).get();
		// 抽出したACTIONを元にサービスクラスを取り出し、execute実行
		this.action.get(executeAction).execute(model);

		return "animal";
	}
}

AnimalCommonService.java

package com.example.demo.service.animalinterface;

import java.util.List;
import java.util.Map;

import org.springframework.stereotype.Service;

import com.example.demo.entity.AnimalKindsEntity;
import com.example.demo.form.AnimalForm;
import com.example.demo.repository.AnimalKindsRepository;

import lombok.RequiredArgsConstructor;

/**
 * 初期表示のデータ取得など、条件関係なく共通の処理を記述するクラス
 */
@Service
@RequiredArgsConstructor
public final class AnimalCommonService
{
	private final AnimalKindsRepository animalKindsRepository;

	/**
	 * animal_kindsテーブルのデータをすべて取得
	 *
	 * @return {@code List<AnimalKindsEntity>} animal_kindsテーブルの全データ
	 */
	public List<AnimalKindsEntity> getAllAnimalKindData()
	{
		return animalKindsRepository.findAll();
	}

	/**
	 * 画面で選択された動物によって、使用する動物クラスを選択
	 *
	 * @return Animal
	 */
	public static Animal getSelectedAnimal(final AnimalForm form, final Map<Integer, Animal> animals)
	{
		final int selectedAnimal = form.getAnimalAsInt();
		return animals.get(selectedAnimal);
	}
}

IAction.java

package com.example.demo.common.icommon;

import java.util.Arrays;
import java.util.Optional;

import org.springframework.ui.Model;

/** ボタン押下時のアクションクラス */
public interface IAction {

	public enum ACTION {
		DISPLAY("display"), CLEAR("clear");

		ACTION(final String action) {
			this.action = action;
		}

		private final String action;

		public String getAction() {
			return this.action;
		}

		public static Optional<ACTION> getACTION(final String action) {
			return Arrays.stream(values())
					.filter(a -> a.action.equals(action))
					.findFirst();
		}
	}

	/** 処理実装クラス */
	public void execute(Model model);
}

AnimalDisplayService.java

package com.example.demo.service.animalinterface;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.ui.Model;
import org.springframework.validation.BeanPropertyBindingResult;
import org.springframework.validation.BindingResult;
import org.springframework.validation.Validator;

import com.example.demo.common.icommon.IAction;
import com.example.demo.entity.AnimalKindsEntity;
import com.example.demo.entity.AnimalNameEntity;
import com.example.demo.form.AnimalForm;
import com.example.demo.service.animalinterface.kinds.Cat;
import com.example.demo.service.animalinterface.kinds.Dog;
import com.example.demo.service.animalinterface.kinds.Monkey;

/** 表示ボタン押下時の処理クラス */
@Service
public final class AnimalDisplayService implements IAction
{
	@Autowired
	private Validator springValidator;

	private final Map<Integer, Animal> animals = new HashMap<Integer, Animal>();

	public AnimalDisplayService(final Cat cat, final Dog dog, final Monkey monkey)
	{
		animals.put(cat.kinds_id(), cat);
		animals.put(dog.kinds_id(), dog);
		animals.put(monkey.kinds_id(), monkey);
	}

	@Override
	public void execute(final Model model)
	{
		final AnimalForm form              = (AnimalForm) model.getAttribute("form");
		final Animal     selected_animal   = AnimalCommonService.getSelectedAnimal(form, this.animals);
		final String     animal_kinds_name = selected_animal == null ? "" : selected_animal.name();

		model.addAttribute("test", animal_kinds_name + "表示");

		final BindingResult bindingResult = new BeanPropertyBindingResult(form, "form");

		if (!validation(model, form, bindingResult))
		{
			return;
		}


		final Optional<AnimalKindsEntity> kinds_data = getSelectedKindsData(model, bindingResult, selected_animal);
		if (kinds_data.isEmpty())
		{
			return;
		}

		final List<AnimalNameEntity> name_data = selected_animal.getAnimalNameDataByKindsId();

		form.setKinds_data(kinds_data.get());
		form.setName_data(name_data);
		model.addAttribute("form", form);
	}

	private Optional<AnimalKindsEntity> getSelectedKindsData(
			final Model model, final BindingResult bindingResult, final Animal selected_animal
			)
	{
		final Optional<AnimalKindsEntity> kinds_data = selected_animal.getAnimalKindsDataByPrimaryKey();
		if (kinds_data.isEmpty())
		{
			bindingResult.rejectValue("animal", "form.animal.notexist");
			model.addAttribute("org.springframework.validation.BindingResult.form", bindingResult);
			return Optional.empty();
		}

		return kinds_data;
	}

	private boolean validation(final Model model, final AnimalForm form, final BindingResult bindingResult)
	{
		springValidator.validate(form, bindingResult);

		if (bindingResult.hasErrors())
		{
			model.addAttribute("org.springframework.validation.BindingResult.form", bindingResult);
			return false;
		}

		return true;
	}

}

AnimalClearService.java

package com.example.demo.service.animalinterface;

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

import org.springframework.stereotype.Service;
import org.springframework.ui.Model;

import com.example.demo.common.icommon.IAction;
import com.example.demo.form.AnimalForm;
import com.example.demo.service.animalinterface.kinds.Cat;
import com.example.demo.service.animalinterface.kinds.Dog;
import com.example.demo.service.animalinterface.kinds.Monkey;

/** 画面クリアボタン押下時の処理クラス */
@Service
public final class AnimalClearService implements IAction
{
	private final Map<Integer, Animal> animals = new HashMap<Integer, Animal>();

	public AnimalClearService(final Cat cat, final Dog dog, final Monkey monkey)
	{
		animals.put(cat.kinds_id(), cat);
		animals.put(dog.kinds_id(), dog);
		animals.put(monkey.kinds_id(), monkey);
	}

	@Override
	public void execute(final Model model)
	{
		final AnimalForm form              = (AnimalForm) model.getAttribute("form");
		final Animal     selected_animal   = AnimalCommonService.getSelectedAnimal(form, this.animals);
		final String     animal_kinds_name = selected_animal == null ? "" : selected_animal.name();

		model.addAttribute("test", animal_kinds_name + "画面クリア");
		form.setKinds_data(null);
		form.setName_data(Collections.emptyList());
	}
}

IAnimal.java

package com.example.demo.service.animalinterface.ianimal;

import org.springframework.stereotype.Component;

/** 選択された動物による処理分岐のインターフェース */
@Component
public interface IAnimal
{
	/** animal_kindsのid */
	public int id();

	/** animal_kindsのname */
	public String name();

	/** animal_nameのkinds_id */
	default int kinds_id()
	{
		return this.id();
	}
}

Animal.java

package com.example.demo.service.animalinterface;

import java.util.List;
import java.util.Optional;

import com.example.demo.entity.AnimalKindsEntity;
import com.example.demo.entity.AnimalNameEntity;
import com.example.demo.repository.AnimalKindsRepository;
import com.example.demo.repository.AnimalNameRepository;
import com.example.demo.service.animalinterface.ianimal.IAnimal;

import lombok.RequiredArgsConstructor;

/**
 * <pre>
 * 選択された動物による処理分岐のクラス。このクラスを継承して使用
 * DIコンテナ内の共通したリポジトリ―を使用するため、抽象クラス作成
 * </pre>
 */
@RequiredArgsConstructor
public abstract class Animal implements IAnimal
{
	private final AnimalKindsRepository animalKindsRepository;
	private final AnimalNameRepository  animalNameRepository;

	public List<AnimalKindsEntity> getAnimalKindsData()
	{
		return this.animalKindsRepository.findAll();
	}

	public List<AnimalNameEntity> getAnimalNameData()
	{
		return this.animalNameRepository.findAll();
	}

	public Optional<AnimalKindsEntity> getAnimalKindsDataByPrimaryKey()
	{
		return animalKindsRepository.findById(this.id());
	}

	public List<AnimalNameEntity> getAnimalNameDataByKindsId()
	{
		return animalNameRepository.findByPrimaryKeysKindsId(kinds_id());
	}
}

Cat.java

package com.example.demo.service.animalinterface.kinds;

import org.springframework.stereotype.Component;

import com.example.demo.repository.AnimalKindsRepository;
import com.example.demo.repository.AnimalNameRepository;
import com.example.demo.service.animalinterface.Animal;

@Component
public final class Cat extends Animal
{
	public Cat(final AnimalKindsRepository animalKindsRepository, final AnimalNameRepository animalNameRepository)
	{
		super(animalKindsRepository, animalNameRepository);
	}

	@Override
	public int id()
	{
		return 1;
	}

	@Override
	public String name()
	{
		return "ねこ";
	}

}

Dog.java

package com.example.demo.service.animalinterface.kinds;

import org.springframework.stereotype.Component;

import com.example.demo.repository.AnimalKindsRepository;
import com.example.demo.repository.AnimalNameRepository;
import com.example.demo.service.animalinterface.Animal;

@Component
public final class Dog extends Animal
{
	public Dog(final AnimalKindsRepository animalKindsRepository, final AnimalNameRepository animalNameRepository)
	{
		super(animalKindsRepository, animalNameRepository);
	}

	@Override
	public int id()
	{
		return 2;
	}

	@Override
	public String name()
	{
		return "いぬ";
	}

}

Monkey.java

package com.example.demo.service.animalinterface.kinds;

import org.springframework.stereotype.Component;

import com.example.demo.repository.AnimalKindsRepository;
import com.example.demo.repository.AnimalNameRepository;
import com.example.demo.service.animalinterface.Animal;

@Component
public final class Monkey extends Animal
{
	public Monkey(final AnimalKindsRepository animalKindsRepository, final AnimalNameRepository animalNameRepository)
	{
		super(animalKindsRepository, animalNameRepository);
	}

	@Override
	public int id()
	{
		return 3;
	}

	@Override
	public String name()
	{
		return "さる";
	}

}

詳細説明

画面から、何かボタンが押された際、
Controllerにて設定された(71~81行目)値が、actionというキーで飛んできます。

それをもとに、117行目で列挙型のACTIONを取得し、このACITONをもとに、
使用するServiceクラスを119行目で取得、処理実行をしています。

Serviceクラス取得用のMapは、コンストラクター(37~47行目)で設定し、
ACTIONをキーにすることで、バグを防ぐ設計にしています。
(キーをStringやintなどにすると、タイポなどでバグを起こす可能性が出る)

Serviceクラス(AnimalDisplayService、AnimalClearService)は、IActionインターフェースを実装して作成することで、
Controllerクラスのコンストラクターで指定するaction(33行目)の型をIActionと統一できつつ、
サービスクラスの分岐することを可能にしています。

また、Serviceクラス内では画面で選択された動物により、処理を分岐するために、
コンストラクターでanimalsという名前のMapをセットしています。
(AnimalDisplayService32~37行目、AnimalClearService22~27行目)

animalsにセットするvalueは、それぞれAnimalクラスを継承することにより、
型をAnimalに統一しつつ、処理の分岐を可能にしています。

Animalクラスは抽象クラスで、IAnimalインターフェースを実装しています。

これにより、Animalクラスを継承するクラスは
必要な設定情報を指定されつつ、共通の処理も同時に実装できています。

このAnimalクラスを継承したクラスをvalueに持つanimalsにより、
取得するデータを分岐(AnimalDisplayService56行目、69~82行目)できたり、
タイトルに表示する文言を分岐(AnimalDisplayService44~46行目、AnimalClearService34~36行目)できたりしています。

interface使用のメリット

  • 冗長にならない
  • 修正に強い

interface使用のデメリット

  • ファイル数が増える

修正に強いとはどういうことか、先にSwitch文使用のサンプルのほうをご覧になった方が、わかりやすいかと思います。

サンプルコードの場合に、もし修正やデータの追加で、例えば「とり」が追加されたとしましょう。

その場合は、単純にAnimalクラスを継承したBirdクラスを作成し、id()に設定するid、nameに”とり”を設定、サーブしクラスのコンストラクターにBirdクラスを追加、
あとは、animal_kindsテーブルとanimal_nameテーブルにデータを追加すれば、修正は終わりです。

Birdクラスの作成の際は、Animalクラスを継承しているため、必要情報を設定する際に漏れることはありません。
(もし漏れていれば、コンパイルエラーでアプリ自体起動しない)

そのため、コンストラクターに追加さえ漏れなければ、修正は終了です。

また、コンストラクターで追加する型もBirdクラスと特有のクラスのため、バグも起こりにくい設計になっています。

これはswitch文を使用していた場合にはできない設計です。

デメリットとしては、ファイル数が増えてしまうというのがありますが、
ファイル数が増えると、どこに何があるのかが分からなくなってくる可能性があるのがデメリットのため、
フォルダ構成をしっかりしておけば、大きなデメリットとはならないと思います。

仕様変更があったり、複数個所で同じ条件の分岐をする際は、interfaceの使用を検討することをおすすめできます。

広告
タイトルとURLをコピーしました