DRY по сравнению с “предпочитает включение по наследованию”, [закрытому]

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

export const reducer = (state = storeData, action) => {
  switch (action.type) {
     case TEST:
        return {
          ...state, // not needed here, but I add this since your production state will likely have more than just one key
          message: action.message
        };

      default:
        return state
   }
}
14
задан chaos 1 May 2009 в 22:27
поделиться

9 ответов

The cost of virtually anything can outweigh its benefits. Having hard and fast, NO EXCEPTIONS rules will always get you into trouble with development. In general, it's a bad idea to use (if your language/runtime support it) reflection in order to gain access to variables that aren't intended to be visible to your code. Is this a bad practice? As with everything,

it depends.

Can composition sometimes be more flexible or easier to maintain than straight-up inheritance? Sure. That's why it exists. In the same way, inheritance can be more flexible or easier to maintain than a pure composition architecture. Then you have interfaces or (depending on the language) multiple inheritance.

None of these concepts are bad, and people should be conscious of the fact that sometimes our own lack of understanding or resistance to change can cause us to create arbitrary rules that define something as "bad" without any real reason to do so.

19
ответ дан 1 December 2019 в 12:13
поделиться

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

Как всегда: правильный ответ зависит от многих факторов, которые вы не можете сделать жесткими и быстрыми правилами.

3
ответ дан 1 December 2019 в 12:13
поделиться

Yeah, what you're seeing there is a horrific collision of design paradigma from different corners of the universe: the GoF's aggregation/composition leveraging colliding with the "Law of Demeter".

I am on record as believing that, in context of aggregation and composition use, the Law of Demeter is an anti-pattern.

Contrary to it, I believe that constructs like person->brain->performThought() are absolutely right and appropriate.

1
ответ дан 1 December 2019 в 12:13
поделиться

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

1
ответ дан 1 December 2019 в 12:13
поделиться

Возможно, это не ответит на ваш вопрос, но есть кое-что, что всегда беспокоило меня о Java и ее стеке. Насколько мне известно, стек должен быть очень простой (или, возможно, самой простой) структурой данных контейнера с тремя основными открытыми операциями: pop, push и peek. Зачем вам в стек вставлять Att, removeAt, et al. Вид функциональности? (в Java Stack наследуется от Vector).

Можно сказать, ну, по крайней мере, вам не нужно документировать эти методы, но зачем иметь методы, которых не должно быть на первом месте?

0
ответ дан 1 December 2019 в 12:13
поделиться

В некоторой степени это вопрос языковой поддержки. Например, в Ruby мы могли бы реализовать простой стек, который использует массив внутри, например, так:

class Stack
  extend Forwardable
  def_delegators :@internal_array, :<<, :push, :pop
  def initialize() @internal_array = [] end
end

Все, что мы здесь делаем, это объявляем, какое подмножество функциональности другого класса мы хотим использовать. Там действительно не так много повторений. Черт, если бы мы на самом деле хотели повторно использовать все функциональные возможности другого класса, мы могли бы даже указать, что фактически ничего не повторяя:

class ArrayClone
  extend Forwardable
  def_delegators(:@internal_array, 
                  *(Array.instance_methods - Object.instance_methods))
  def initialize() @internal_array = [] end
end

Очевидно (я надеюсь), это не тот код, который я обычно писал бы, но я думаю, что это показывает, что он может быть сделано На языках без простого метапрограммирования, в общем, может быть немного сложнее сохранять СУХОЙ.

0
ответ дан 1 December 2019 в 12:13
поделиться

GoF is old text by now.. Depends on what OO environment you look at (and even then it is obvious you can come across it in OO-unfriendly pieces such as C-with-classes).

For runtime environments you pretty much have no choice, single inheritance. And any workaround attempted to circumvent its limitations is just that, no matter how sophisticated or 'cool' it might seem.

Again you will see this manifested everywhere, inclusive of C++ (most capable one) where it interfaces with C callbacks (which is widespread enough to make anyone pay attention). C++ however offers you mix-ins and policy-based designs with template features so it can occasionally help for hard engineering.

While containment can give you benefits of indirection, inheritance can give you easily accessible composition. Pick you poison.. aggregation ports better but always looks like violation of DRY while inheritance can lead to easier reuse with different set of potential maintainance headaches.

The real problem is that the computer language or modelling tool should give you an option what it ought to do independent of choice and as such make it less human error-prone; but not many people model before letting computers write programs for them + no good tools are around (Osla certainly isn't one) or their environment is pushing something as silly as reflection, IoCs and what not.. which is very popular and that in itself tells a lot.

There was once a piece done for an ancient COM tech that was called Universal Delegator by one of the best Doom players around, but it isn't the kind of development anyone would adopt these days.. It required interfaces sure (and not a real-hard-requirement for general case). The idea is simple, it predates all the way to interrupt handling.. Only similar aproaches give you the best of both worlds and they are somewhat evident in scripted pieces suches as JavaScript and functional programming (although far less readable or performing).

-1
ответ дан 1 December 2019 в 12:13
поделиться

Хотя я согласен со всеми, кто сказал, что «это зависит» - и что это также зависит от языка в определенной степени - я удивлен, что никто не упомянул об этом (знаменитыми словами Аллена Голуба) " распространяется зло ". Когда я впервые прочитал эту статью, я должен признать, что меня это немного оттолкнуло, но он прав: независимо от языка, отношения «есть-а» - это самая тесная форма связи из всех существующих . Высокие цепочки наследования являются отличным антипаттерном. Поэтому, хотя говорить о том, что всегда следует избегать наследования, нельзя, его следует использовать с осторожностью (для классов рекомендуется наследование интерфейса). Моя объектно-ориентированная тенденция заключалась в том, чтобы моделировать все как цепочку наследования, и да, это действительно уменьшает дублирование кода, но с очень реальной ценой тесной связи, что означает неизбежную головную боль при обслуживании где-то в будущем.

Его статья намного лучше объясняет, почему наследование тесно связано, но основная идея состоит в том, что is-a требует, чтобы каждый дочерний класс (и внук и т. д.) зависел от реализация классов-предков. «Программирование через интерфейс» - хорошо известная стратегия уменьшения сложности и помощи в гибкой разработке. Вы не можете программировать интерфейс родительского класса, потому что экземпляр является этим классом.

С другой стороны, использование агрегации / композиции приводит к хорошей инкапсуляции, делая систему намного менее жесткой. Сгруппируйте этот повторно используемый код в служебный класс, свяжите его с помощью отношения has-a, и ваш клиентский класс теперь использует службу, предоставляемую в соответствии с контрактом. Теперь вы можете выполнить рефакторинг служебного класса, как вам нравится; пока вы соответствуете интерфейсу, ваш клиентский класс может оставаться в блаженном неведении об изменении, и (что важно) его не нужно перекомпилировать.

Я не предлагаю это как религию, а просто рекомендую , Конечно, они предназначены для того, чтобы их ломали, когда это необходимо, но, как правило, есть веская причина, по которой в этом термине заключено слово «лучший».

1
ответ дан 1 December 2019 в 12:13
поделиться

Вы можете рассмотреть возможность реализации кода «декоратора» в абстрактном базовом классе, который (по умолчанию) перенаправляет все вызовы методов в содержащийся объект. Затем создайте подкласс абстрактного декоратора и переопределите / добавьте методы по мере необходимости.

abstract class AbstractFooDecorator implements Foo {
    protected Foo foo;

    public void bar() {
        foo.bar();
    }
}

class MyFoo extends AbstractFooDecorator {
    public void bar() {
        super.bar();
        baz();
    }
}

Это, по крайней мере, устраняет повторение кода "пересылки", если у вас есть много классов, обертывающих определенный тип.

Что касается того, является ли он Руководство является полезным, я полагаю, следует сделать акцент на слове «предпочитать». Очевидно, что будут случаи, когда будет иметь смысл использовать наследование. Вот пример того, когда должно использоваться наследование , а не :

Класс Hashtable был расширен в JDK 1.2 и теперь включает новый метод entrySet, который поддерживает удаление записей из Хеш-таблица. Класс Provider не был обновлен для переопределения этого нового метода. Этот контроль позволил злоумышленнику обойти проверку SecurityManager, выполняемую в Provider.remove, и удалить сопоставления поставщиков, просто вызвав метод Hashtable.entrySet.

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

0
ответ дан 1 December 2019 в 12:13
поделиться