Шаблонный метод проектирования шаблона с использованием 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

Другие вопросы по тегам