#java #oop #design-patterns #strategy-pattern
#java #ооп #шаблоны проектирования #стратегия-шаблон
Вопрос:
У меня есть следующий пример кода (код ниже).
У меня есть интерфейсы OnlyReverse
, OnlySplit
которые определяют операции над классом Data
. Иногда у меня будет класс, доступный только для реверсирования, а иногда я смогу выполнять реверсирование и разделение.
В коде у меня есть 2 подхода.
1-й подход заключается в использовании 2 отдельных классов для этих 2 отдельных вариантов использования ReverseAndSplitImpl
и OnlyReverseImpl
. Здесь мне не нравится, что мне нужен дополнительный класс, и что мне нужно дублировать часть кода между этими 2 классами.
2-й подход заключается в использовании 1 класса для обоих вариантов использования, SingleClassForReverseAndSplitImpl
а затем использовании стратегии для введения либо NormalSplit
или NoSplit
. Здесь мне не нравится этот дополнительный NoSplit
класс, который в основном искусственный.
В соответствии с принципом разделения интерфейса — нужно ли мне иметь ReverseAndSplit
интерфейс присоединения, или я всегда должен использовать оба интерфейса отдельно (like SingleClassForReverseAndSplitImpl implements OnlyReverse, OnlySplit
и not SingleClassForReverseAndSplitImpl implements ReverseAndSplit
)?
Какой из этих подходов лучше в долгосрочной перспективе (более гибкий в будущем)?
class Data{
String a;
}
interface OnlyReverse{
Data getData();
OnlyReverse reverse();
}
interface OnlySplit{
OnlySplit split();
}
interface ReverseAndSplit extends OnlyReverse, OnlySplit{
@Override
ReverseAndSplit reverse();
@Override
ReverseAndSplit split();
}
//------------------------- USE DISTINCT CLASSES; ONE HAS SPLIT OTHER NO
class ReverseAndSplitImpl implements ReverseAndSplit{
Data data;
public ReverseAndSplitImpl(Data data) {
this.data = data;
}
@Override
public Data getData() {
return data;
}
@Override
public ReverseAndSplit reverse() {
//here reverse and return
return new ReverseAndSplitImpl(data);
}
@Override
public ReverseAndSplit split() {
//here split and return
return new ReverseAndSplitImpl(data);
}
}
class OnlyReverseImpl implements OnlyReverse{
Data data;
public OnlyReverseImpl(Data data) {
this.data = data;
}
@Override
public Data getData() {
return data;
}
@Override
public OnlyReverse reverse() {
return new OnlyReverseImpl(data);
}
}
//------------------------- USE DISTINCT CLASSES; ONE HAS SPLIT OTHER NO
//------------------------- USE STRATEGY TO CHOOSE TO HAVE SPLITTING OR NO
interface SplitStrategy{
Data split(Data data);
}
class NormalSplit implements SplitStrategy{
@Override
public Data split(Data data) {
return new Data();
}
}
//NullObject pattern
class NoSplit implements SplitStrategy{
@Override
public Data split(Data data) {
return data;
}
}
class SingleClassForReverseAndSplitImpl implements ReverseAndSplit{
Data data;
SplitStrategy splitStrategy;
public SingleClassForReverseAndSplitImpl(Data data, SplitStrategy splitStrategy) {
this.data = data;
this.splitStrategy = splitStrategy;
}
@Override
public Data getData() {
return data;
}
@Override
public ReverseAndSplit reverse() {
//here reverse and return
return new SingleClassForReverseAndSplitImpl(data, splitStrategy);
}
@Override
public ReverseAndSplit split() {
//here split and return
SingleClassForReverseAndSplitImpl s = new SingleClassForReverseAndSplitImpl(data, splitStrategy);
s.data = splitStrategy.split(data);
return s;
}
}
//------------------------- USE STRATEGY TO CHOOSE TO HAVE SPLITTING OR NO
public class Decorator {
public static void main(String[] args) {
ReverseAndSplit s11 = new SingleClassForReverseAndSplitImpl(new Data(), new NoSplit());
s11 = s11.reverse();
s11 = s11.split(); //has split operation, but NoSplit will do nothing
OnlyReverse s12 = new OnlyReverseImpl(new Data());
s12 = s12.reverse();
//has no split operation present
//Going from NoSplit to SplitAndReverse
ReverseAndSplit s21 = new SingleClassForReverseAndSplitImpl(s11.getData(), new NormalSplit());
s21 = s21.reverse();
s21 = s21.split(); //has split and now it is using NormalSplit
ReverseAndSplit s22 = new ReverseAndSplitImpl(s12.getData());
s22 = s22.reverse();
s22 = s22.split();
}
}
Комментарии:
1. Предоставление альтернативы /
ReverseAndSplit
не обязательно плохо , плохо то, что ваш код зависит от этих более конкретных интерфейсов. Для целей реализации обычно проще объединить и использовать единый интерфейс
Ответ №1:
Я думаю, разумно использовать ваш первый подход (два более простых интерфейса), но не вызывать их OnlyReverse
и OnlySplit
, но Reverse
и Split
. Поэтому разумно их объединить, и в будущем вы также можете просто использовать один из них.
Но это также зависит от объектов вашего домена, поэтому вам сложно сделать выбор. Но я бы не стал создавать вызываемый интерфейс NoSplit
, потому что вы все равно могли бы реализовать ReverseAndSplit
и NoSplit
который не имел бы для вас никакого смысла.
Ответ №2:
В соответствии с принципом разделения интерфейса, если у вас есть клиент, который никогда не разделяется, то вы не должны предоставлять этому клиенту интерфейс, включающий split
метод. Подход Strategy / Null Object подходит для одного клиента, который иногда требует разделения, а иногда нет. Подход с отдельными интерфейсами подходит для двух разных клиентов, у которых разные требования.
Комментарии:
1.
split
это дополнительная функциональность, которая может быть выполнена для внутренних данных (или нет, если для стратегии установлено значение null object). все остальное, касающееся логики, должно оставаться неизменным. Я предполагаю, что это подход с одним клиентом…2. По сути, используя этот подход, я хочу запретить некоторые из
SingleClassForReverseAndSplitImpl
split
них, а для некоторых других разрешить это — например, на основеData
того, с помощью чего я его инициализирую. Если для данных установлено значение «1», «2», «3» — затем разрешитеsplit
, в противном случае используйтеNoSplit
.3. @MarkoKraljevic Как клиент узнает, могут ли они вызывать split или нет? Как бы они справились с невозможностью разделить / отменить? Неясно, как клиенты будут использовать эти классы и интерфейсы.
4. @plalx Я думаю, они не будут. Поскольку у меня может быть логика для инициализации стратегии внутри моего класса, но метод split() всегда будет там. Просто иногда он ничего не будет делать, если его вызвать. Есть ли какой-нибудь лучший подход, подобный 1-му, но не дублировать один и тот же код? Например, делегировать все те же методы родительскому, и внутри обрабатывается только один split(). Но тогда — как получить доступ
Data
к делегированному классу?5. @MarkoKraljevic Как бы вы отнеслись к вызову «string».split() и к тому, чтобы он ничего не делал? Будут ли клиенты писать алгоритмы для обработки данных? Если мы не знаем, как клиенты будут использовать эти интерфейсы и классы, мы не сможем помочь в разработке.