2016-03-31 4 views
3

У меня есть алгоритм, и у меня есть две различные реализации алгоритма. Эти реализации следует вызывать из многих мест, в зависимости от выбранного пользователем режима. Я бы не хотел писать условные утверждения во всех местах, где вызывались реализации. Итак, я создаю абстрактный класс, а реализация наследует его. Я могу установить нужный режим в одном месте, как это:Как избавиться от наследства?

if(firstMode){ 
    list = new ListForm1(); 
} 
else{ 
    list = new LiastForm2(); 
} 

И после того, что во всех других местах, где я могу пользоваться всеми преимуществами полиморфизма. Это работает хорошо, но я хочу, чтобы избавиться от наследования по следующим причинам:

  1. Я слышал, что композиция гораздо лучше, чем наследование.
  2. Первая форма алгоритма намного проще, чем вторая форма. В первой форме у меня всего 3 метода, а во второй форме у меня 15 методов. В абстрактный класс должны были входить все 15 (и 5 общих методов). Оказывается, что 12 методов не используются первой формой.
  3. Теоретически, может быть новая форма алгоритма, которая будет иметь еще меньше общего с другими двумя, но она принесет 10 новых методов, и все они должны будут добавить абстрактный класс.

Шаблон стратегии, как я понимаю, не имеет смысла использовать здесь. Вот пример стратегии Pattern:

//abstract strategy 
    interface Strategy { 
     int execute(int a, int b); 
    } 

// concrete strategy1 
class ConcreteStrategyAdd implements Strategy { 

    public int execute(int a, int b) { 
     return a + b; 
    } 
} 

// concrete strategy2 
class ConcreteStrategySubtract implements Strategy { 

    public int execute(int a, int b) { 
     return a - b; 
    } 
} 

//concrete strategy3 
class ConcreteStrategyMultiply implements Strategy { 

    public int execute(int a, int b) { 
     return a * b; 
    }  
} 

class Context { 

    private Strategy strategy; 

    public Context() { 
    } 

    // Set new concrete strategy 
    public void setStrategy(Strategy strategy) { 
     this.strategy = strategy; 
    } 

    // use strategy 
    public int executeStrategy(int a, int b) { 
     return strategy.execute(a, b); 
    } 
} 

Он имеет те же проблемы. Стратегии должны быть связаны друг с другом. Если я свяжу их с интерфейсом вместо абстрактного класса, это будет еще хуже. Интерфейс будет содержать множество методов, но многие из них не понадобятся для первой формы алгоритма. Кроме того, общие методы должны дублироваться во всех конкретных стратегиях. Я не могу обеспечить реализацию по умолчанию в интерфейсе.

Более того, я не понимаю, как использовать композицию здесь. Насколько я понимаю, Strategy Pattern уже использовал композицию. Контекст класса включает в себя экземпляр стратегии как области. Но, возможно, это делегирование.

Итак, вот мой вопрос:

Могу ли я избавиться от всех вышеперечисленных проблем (слишком много методов абстрактного класса, сильной связи, из-за чего будет трудно добавить новую форму алгоритма), но все же использовать условные операторы только в одном месте, а не во всех случаях, когда мне нужен какой-то алгоритм.

UPD: Я хочу, чтобы показать, как я назвал некоторые методы, которые реализованы в ВТОРЫХ виде алгоритма, но не нужен для первой формы алгоритма:

if (list.getCurrentLeaders().contains(ballIdx)) 

реализации по умолчанию способ getCurrentLeaders() возвращение null. Итак, если я назвал его с экземпляром FIRST-формы алгоритма, то я получу ошибку. Я понимаю, что это плохо. Но как я могу это решить?

+3

Что вы подразумеваете под «Стратегии должны быть связаны друг с другом»?? На данный момент ваш вопрос довольно неясен. Похоже, вам может понадобиться интерфейс для публичного API (т. Е. С учетом того, что должен видеть вызывающий * *), но тогда вам может потребоваться, чтобы абстрактный класс выступал в качестве базы для реализаций, которые разделяют множество деталей. Трудно сказать, пока все это настолько абстрактно. Обратите внимание, что «предпочтение композиции» не означает, что «наследование всегда отстой». –

+0

_I слышал, что композиция намного лучше, чем наследование. Наследование имеет свое место. Вы не должны просто выбросить его, потому что _Y_ лучше (для случая _Z_). Не могли бы вы прояснить, что вы подразумеваете под алгоритмом №1, есть методы X, а # 2 - методы Y? Можно ли его изменить так, чтобы вызывающий в обоих случаях просто вызывал один метод и независим от реализации? –

+0

Я не понимаю, как вы используете свою форму - если у вас 15 методов, а у второго есть 5 - (поэтому я предполагаю, что во втором - 10 пустых методов) - что происходит, когда они вызываются в приложении? – AdamSkywalker

ответ

1

С самого начала в случае вам нужно вызвать другой алгоритм, основанный на другом режиме, выбранном пользователем, вы можете создать своего рода заводский класс для подачи алгоритма на весь ваш код.Я думаю, что если это только алгоритм, и если вы на Java 8 вы можете использовать Function или Predicate или Supplier в сочетании с картой, чтобы избежать, если заявление, например:

Map<String, Predicate<Whatever>> map = new HashMap<>(); 
map.put("mode_one", (w) -> true); 
map.put("mode_two", (w) -> false); 

Тогда называют алгоритм, просто:

map.get("mode_one").test() 

в случае вам нужно поставить другую форму, как в примере, вы в курсе, вы можете использовать поставщик вместо предиката. Основываясь на вашем простом требовании, я думаю, что функционирование будет лучшим ...

0

Почему бы не просто использовать IStrategy как тип?

interface IStrategy { 
    int execute(int a, int b); 
} 

class Strategy1 implements IStrategy {} 
class Strategy2 implements IStrategy {} 

static class StrategyFactory { 
    IStrategy Create(bool first) { 
     return first ? new Strategy1() : new Strategy2(); 
    } 
} 

А потом в коде пользователя:

void doStuff() 
{ 
    IStrategy myStrategy = StrategyFactory.Create(true); 
    myStrategy.execute(1, 2); 
} 
1

Если вы не реализует все методы (например, если у вас есть 15 методов в абстрактном классе должны быть реализованы, и вам нужно всего лишь. 10), то вы нарушаете Принцип замены Лискова:

https://en.wikipedia.org/wiki/Liskov_substitution_principle 

В принципе, это плохо.

Попробуйте преобразовать неосновные методы в какой-либо другой объект, который передается в конструктор (на абстрактном).

+0

Нарушение других принципов SOLID. –

1

Я слышал, что композиция намного лучше, чем наследование.

Не всегда - много раз наследование является правильной конструкцией. Вы должны думать об этом в has a и is a условиях. Футбольная команда имеет игроков pf. У него также есть тренер, расписание, имя и т. Д. Таким образом, Team : List<Player> не является правильной конструкцией.

A CarявляетсяVehicle, так что наследование является правильным конструкт.

Так что думайте о вашем дизайне так:

ли мои классы имеют общую базу? Существует ли базовый класс, который имеет смысл сказать ListForm1являетсяListBase и ListForm2являетсяListBase. Какие методы и свойства являются общими для тех типов, которые должны быть в типе case? Какие методы и свойства должны быть виртуальными, чтобы я мог их переопределить?

Первый вид алгоритма намного проще, чем вторая форма. В первой форме у меня всего 3 метода, а во второй форме у меня 15 методов. В абстрактный класс должны были входить все 15 (и 5 общих методов). Оказывается, что 12 методов не используются первой формой.

Так что, возможно, ваш базовый тип всего 3 метода, и при необходимости вы добавляете методы в подтипы.Помните, что вы можете иметь несколько базовых типов в цепочке, но это цепочка, а не дерево, то есть вы можете иметь одного родителя, у которого есть другой родитель, но у вас не может быть двух родителей.

Возможно, у вас есть ортогональные интерфейсы, так как вы можете реализовать несколько интерфейсов.

Теоретически, может существовать новая форма алгоритма, которая будет иметь еще меньшее сходство с другими двумя, но она принесет 10 новых методов, и все они должны будут добавить абстрактный класс.

Почему? Почему новый алгоритм не может определить свои собственные методы, которые ему нужны, до тех пор, пока клиенты выбирают соответствующий уровень в цепочке наследования (или соответствующий интерфейс (интерфейсы)), чтобы он знал, какие методы должны быть выполнены :.

if (list.getCurrentLeaders().contains(ballIdx))

Реализация по умолчанию метода getCurrentLeaders() возврата нуль. Итак, если я назвал его с экземпляром FIRST-формы алгоритма, то я получу ошибку. Я понимаю, что это плохо. Но как я могу это решить?

Так что вам нужно проверить, что этот конкретный list реализует интерфейс (или наследует базовый класс), что делает реализации этого метода?

1

Вы можете реализовать какой-то Цепь ответственности.

interface IStrategy { 
    void Run(); 
    bool CanHandle(IContext context); 
} 

class StrategyChecker { 
    IStrategy GetStrategy(IContext context) { 
    foreach(var strategy in strategies) { 
     if(strategy.CanHandle(context) 
     return strategy; 
    } 

    return defaultStrategy; 
    } 
}  

class Director { 
    void Run() { 
    strategyChecker.AddStrategy(strategy1); 
    strategyChecker.AddStrategy(strategy2); 

    var strategy = strategyChecker.GetStrategy(someContext); 
    strategy.Run(); 
    } 
} 

Извините за псевдокод C#.

+1

Хорошее применение этой схемы ответственности. Пожалуйста, добавьте NoneStrategy (Null Pattern) в конец цепочки, чтобы избежать исключения. –

+1

strategyChecker.AddStrategy (strategyNone); –

+0

Да, вы правы! Но это всего лишь быстрый эскиз моей идеи, вы должны адаптировать этот «код» для своих нужд. BTW, defaultStrategy имеет стратегию NullPattern –

Смежные вопросы