Шаблонный метод проектирования шаблона с использованием Java 8
Я хочу реорганизовать метод шаблона, используя java 8 новый метод по умолчанию. Скажем, у меня есть поток определения процесса в абстрактном классе:
public abstract class FlowManager{
public void startFlow(){
phase1();
phase2();
}
public abstract void phase1();
public abstract void phase2();
}
и у меня есть несколько подклассов, которые расширяют вышеупомянутый диспетчер потока, и каждый подкласс реализует свой собственный phase1
а также phase2
Mathod. Интересно, имеет ли смысл рефакторинг кода в интерфейс, подобный этому:
public interface FlowManager{
public default startFlow(){
this.phase1();
this.phase2();
}
public void phase1();
public void phase2();
}
Как вы думаете?
5 ответов
Использование интерфейса с методом по умолчанию для реализации шаблона метода шаблона кажется мне подозрительным.
Метод по умолчанию обычно (хотя и не всегда) предназначен для переопределения разработчиками. Если в качестве метода шаблона используется метод интерфейса по умолчанию, метод переопределения будет подвержен ошибкам программирования, таким как не вызов super
метод, вызывающий его в неподходящее время, изменяющий порядок вызова фаз и т. д. Это все ошибки программирования, которых шаблон шаблонного метода должен избегать.
Обычно шаблонный метод не предназначен для переопределения. В Java-классах это можно сигнализировать, сделав метод final
, Интерфейсы не могут иметь окончательных методов; см. этот вопрос для обоснования. Таким образом, предпочтительно реализовать шаблон шаблона шаблона, используя абстрактный класс с конечным методом в качестве шаблона.
В дополнение к более ранним ответам отметим, что есть и другие возможности. Во-первых, это разделить метод шаблона на его собственный класс:
public interface Flow {
void phase1();
void phase2();
}
public final class FlowManager {
private final Flow flow;
public FlowManager(Flow flow) {
this.flow = flow;
}
public void startFlow() {
flow.phase1();
flow.phase2();
}
}
Если вы уже используете FlowManager.phaseX
методы, которые вы можете сделать, реализуя Flow
интерфейс также:
public final class FlowManager implements Flow {
private final Flow flow;
public FlowManager(Flow flow) {
this.flow = flow;
}
public void startFlow() {
flow.phase1();
flow.phase2();
}
@Override
public void phase1() {
flow.phase1();
}
@Override
public void phase2() {
flow.phase2();
}
}
Таким образом, вы явно указываете, что пользователи должны реализовать Flow
интерфейс, но они не могут изменить startFlow
метод шаблона, как он объявлен в последнем классе.
Java 8 добавляет новый функциональный шаблон для решения вашей проблемы:
public final class FlowManager {
private final Runnable phase1;
private final Runnable phase2;
public FlowManager(Runnable phase1, Runnable phase2) {
this.phase1 = phase1;
this.phase2 = phase2;
}
public void startFlow() {
phase1.run();
phase2.run();
}
public void phase1() {
phase1.run();
}
public void phase2() {
phase2.run();
}
}
Ну, этот код работает даже до Java 8, но теперь вы можете создать FlowManager
используя лямбды или ссылки на методы, что гораздо удобнее.
Вы также можете комбинировать подходы: определить интерфейс и предоставить способ для его построения из лямбд:
public interface Flow {
void phase1();
void phase2();
static Flow of(Runnable phase1, Runnable phase2) {
return new Flow() {
@Override
public void phase1() {
phase1.run();
}
@Override
public void phase2() {
phase2.run();
}
};
}
}
Collector
Интерфейс в Java 8 реализован аналогичным образом. Теперь в зависимости от предпочтений пользователей они могут либо реализовать интерфейс напрямую, либо использовать Flow.of(...)
и передайте там лямбды или ссылки на методы.
// дизайн шаблона класса
public class Template {
protected interface MastSuppler{
List<Mast> apply(int projectId);
}
protected interface Transform<T>{
List<T> apply(List<Mast> masts);
}
protected interface PropertiesConsumer<T>{
void apply(List<T> properties);
}
public <T> void template(int projectId, MastSuppler suppler, Transform<T> transform, PropertiesConsumer<T> consumer){
System.out.println("projectId is " + projectId);
//1.List<Mast> masts = step1(int projectId);
List<Mast> masts = suppler.apply(projectId);
//2.List<T> properties = step2(List<Mast> masts)
List<T> properties = transform.apply(masts);
//3.use or consume these properties(print to console ,save to datebase)
consumer.apply(properties);
}
}
// использовать с клиентом
public class Mast {
public static void main(String[] args) {
//1.save to db
new Template().template(1,
projectId->getMastsfromMongo(projectId),
masts-> masts.stream().map(mast->mast.getName()).collect(Collectors.toList()),
names->System.out.println("save names to db "+ names));
//new Template(1, id->Arrays, );
//2.print to console
new Template().template(2,
projectId->getMastsSomewhere(projectId),
masts-> masts.stream().map(mast->mast.getLat()).collect(Collectors.toList()),
names->System.out.println("print lons to console "+ names));
}
private static List<Mast> getMastsfromMongo(int projectId){
Mast m1 = new Mast("1", 110, 23);
Mast m2 = new Mast("2", 111, 13);
return Arrays.asList(m1, m2);
}
private static List<Mast> getMastsSomewhere(int projectId){
Mast m1 = new Mast("3", 120, 53);
Mast m2 = new Mast("4", 121, 54);
return Arrays.asList(m1, m2);
}
private String name;
private double lon;
private double lat;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getLon() {
return lon;
}
public void setLon(double lon) {
this.lon = lon;
}
public double getLat() {
return lat;
}
public void setLat(double lat) {
this.lat = lat;
}
public Mast(String name, double lon, double lat) {
super();
this.name = name;
this.lon = lon;
this.lat = lat;
}
}
Потребовалось некоторое время, чтобы осмыслить реализацию метода Template в Java 8, это похоже на магию:) Существуют некоторые различия в том, как мы реализуем его в Java 8.
1- Родительский класс не определяет методы (которые будут реализованы позже в дочернем классе) в своем теле, он определяет их как параметры в окончательной сигнатуре метода.
2. Исходя из вышеизложенного, дочерний класс не обязан предоставлять реализацию в своем теле, он будет предлагать реализацию во время создания экземпляра.
import java.util.function.Consumer;
public abstract class FlowManager<T> {
public final void startFlow(T t,
Consumer<T> phase1,
Consumer<T> phase2){
phase1.accept(t);
phase2.accept(t);;
}
}
Реализация
public class FlowManager2<T>
extends FlowManagerJava8<String>{
}
Основной класс
import java.util.function.Consumer;
public class Main {
public static void main(String args[]){
new FlowManager2<String>().startFlow("Helo World",
(String message)->System.out.println("Phase 1 : "+ message),
(String message)->System.out.println("Phase 2 : "+ message));
Consumer<String> phase1 =
(String message)-> System.out.println("Phase 1 : "+ message);
Consumer<String> phase2 =
(String message)-> System.out.println("Phase 2 : "+ message);
new FlowManager2<String>().startFlow("Helo World",
phase1,
phase2);
}
}
Оба подхода будут работать.
Какой из них использовать, во многом зависит от того, какие другие функции FlowManager
будет и как вы будете использовать его позже.
Абстрактный класс позволил бы вам определять нестатические поля, если затем вам нужно смоделировать также некоторое состояние. Это также позволит иметь частные или защищенные методы.
С другой стороны, интерфейс облегчит реализацию несвязанными классами, поскольку вы не будете ограничены одним наследованием.
Учебное руководство по Java довольно хорошо описывает его здесь, в разделе "Абстрактные классы по сравнению с интерфейсами":
http://docs.oracle.com/javase/tutorial/java/IandI/abstract.html