Например, предположим, у меня есть класс для расширения других классов:
public class LoginPage {
public String userId;
public String session;
public boolean checkSessionValid() {
}
}
и некоторые подклассы:
public class HomePage extends LoginPage {
}
public class EditInfoPage extends LoginPage {
}
На самом деле, у подкласса нет никаких методов для переопределения, также я бы не стал обращаться к HomePage в общем виде, то есть: я бы не делал что-то вроде:
for (int i = 0; i < loginPages.length; i++) {
loginPages[i].doSomething();
}
Я просто хочу повторно использовать страницу входа. Но согласно https://stackoverflow.com/a/53354 , я бы предпочел композицию здесь, потому что мне не нужен интерфейс LoginPage, поэтому я не использую здесь наследование:
public class HomePage {
public LoginPage loginPage;
}
public class EditInfoPage {
public LoginPage loginPage;
}
но проблема возникает здесь, в новой версии, код:
public LoginPage loginPage;
дублируется при добавлении нового класса. И если LoginPage нужны сеттер и геттер, нужно скопировать больше кодов:
public LoginPage loginPage;
private LoginPage getLoginPage() {
return this.loginPage;
}
private void setLoginPage(LoginPage loginPage) {
this.loginPage = loginPage;
}
Таким образом, мой вопрос заключается в том, нарушает ли «композиция через наследование» «сухой принцип»?
inheritance
composition
dry
ocomfd
источник
источник
extends LoginPage
повсюду дубликаты . МАТ!LoginPage
- декоратором. Нет больше дублирования, простоpage = new LoginPage(new EditInfoPage())
и все готово. Или вы используете принцип open-closed для создания модуля аутентификации, который можно динамически добавлять на любую страницу. Существует множество способов справиться с дублированием кода, в основном с помощью поиска новой абстракции.LoginPage
скорее всего, это плохое имя, и вы хотите убедиться, что пользователь проходит аутентификацию при просмотре этой страницы, перенаправляется наLoginPage
или показывает правильное сообщение об ошибке, если это не так.Ответы:
Э-э, подождите, вы обеспокоены тем, что повторение
в двух местах нарушает СУХОЙ? По этой логике
теперь может существовать только в одном объекте во всей базе кода. BLEH.
СУХОЙ это хорошая вещь, чтобы иметь в виду, но давай. Кроме
дублируется в вашей альтернативе, так что даже анальное отношение к СУХОЙ не будет иметь смысла в этом.
Действительные проблемы с СУХОЙ имеют тенденцию фокусироваться на идентичном поведении, необходимом в нескольких местах, определяемых в нескольких местах, так что необходимость изменить это поведение в конечном итоге требует изменения в нескольких местах. Принимайте решения в одном месте, и вам нужно будет только изменить их в одном месте. Это не значит, что только один объект может содержать ссылку на ваш LoginPage.
СУХОЙ не следует слепо следовать. Если вы дублируете, потому что копировать и вставлять проще, чем придумывать хороший метод или имя класса, то вы, вероятно, ошибаетесь.
Но если вы хотите поместить один и тот же код в другое место, потому что это другое место подвержено разной ответственности и, вероятно, потребуется изменить независимо, то, вероятно, целесообразно ослабить применение вами DRY и позволить этому идентичному поведению иметь другую идентичность , Это тот же тип мышления, что и запрещающие магические числа.
DRY - это не только то, как выглядит код. Речь идет о том, чтобы не распространять детали идеи с помощью бессмысленного повторения, заставляющего сопровождающих исправлять вещи, используя бессмысленное повторение. Это когда вы пытаетесь сказать себе, что бессмысленное повторение - это просто ваше соглашение, что дела идут очень плохо.
То, на что вы действительно хотите пожаловаться, называется стандартным кодом. Да, использование композиции, а не наследования требует стандартного кода. Ничто не раскрывается бесплатно, вы должны написать код, который разоблачает это. Этот шаблон обеспечивает гибкость состояния, способность сужать открытый интерфейс, давать разные имена, соответствующие их уровню абстракции, хорошую перенаправленность, и вы используете то, из чего состоите извне, а не внутри, так что вы сталкиваетесь с нормальным интерфейсом.
Но да, это очень много дополнительной клавиатуры. Пока я могу предотвратить проблему йо-йо, связанную с отскоком вверх и вниз по стеку наследования, пока я читаю код, он того стоит.
Теперь я не отказываюсь использовать наследство. Одно из моих любимых применений - давать исключениям новые имена:
источник
int x;
может существовать не более нуля раз в кодовой базеint a; int b; int x;
я буду настаивать на том, чтобы удалить вас.Распространенное недоразумение с принципом DRY состоит в том, что оно каким-то образом связано с неповторяющимися строками кода. Принцип СУХОГО: «Каждое знание должно иметь одно, однозначное, авторитетное представление в системе» . Речь идет о знаниях, а не о коде.
LoginPage
знает, как нарисовать страницу для входа в систему. Если быEditInfoPage
знал, как это сделать, то это было бы нарушением. ВключениеLoginPage
через композицию ни в коем случае не является нарушением принципа СУХОЙ.Принцип СУХОЙ, пожалуй, является наиболее неправильно используемым принципом в разработке программного обеспечения, и его всегда следует рассматривать не как принцип, не дублирующий код, а как принцип, не дублирующий знание абстрактной области. На самом деле, во многих случаях, если вы правильно примените DRY, вы будете дублировать код , и это не обязательно плохо.
источник
Краткий ответ: да, в некоторой степени приемлемо.
На первый взгляд, наследование иногда может сэкономить вам несколько строк кода, так как оно дает эффект «мой класс повторного использования будет содержать все открытые методы и атрибуты в формате 1: 1». Поэтому, если в компоненте есть список из 10 методов, нет необходимости повторять их в коде унаследованного класса. Когда в композиционном сценарии 9 из этих 10 методов должны быть открыты для публичного использования через повторно используемый компонент, то необходимо записать 9 делегирующих вызовов и выпустить оставшийся, и нет никакого способа обойти это.
Почему это терпимо? Посмотрите на методы, которые реплицируются в сценарии композиции - эти методы исключительно делегируют вызовы интерфейса компонента, поэтому не содержат никакой реальной логики.
Суть принципа СУХОЙ состоит в том, чтобы в коде не было двух мест, где кодируются одни и те же логические правила - потому что когда эти логические правила изменяются, в не-СУХОМ коде легко адаптировать одно из этих мест и забыть о другом, который вводит ошибку.
Но поскольку делегирующие вызовы не содержат логики, они обычно не подлежат такому изменению, поэтому не создают реальной проблемы, когда «предпочитают композицию наследованию». И даже если интерфейс компонента изменится, что может вызвать формальные изменения во всех классах, использующих этот компонент, на скомпилированном языке компилятор сообщит нам, когда мы забыли изменить одного из вызывающих.
Примечание к вашему примеру: я не знаю, как вы
HomePage
и вашиEditInfoPage
выглядите, но если у них есть функция входа в систему, аHomePage
(илиEditInfoPage
) - этоLoginPage
, то наследование может быть правильным инструментом здесь. Менее спорный пример, где композиция будет лучшим инструментом в более очевидной форме, вероятно, прояснит ситуацию.Предполагая, что ни то,
HomePage
ни другое неEditInfoPage
являетсяLoginPage
, и кто-то хочет повторно использовать последнее, как вы написали, то, скорее всего, вам потребуются только некоторые частиLoginPage
, а не все. Если это так, лучшим подходом, чем использование композиции указанным способом, может бытьизвлекать повторно используемую часть
LoginPage
в свой компонентповторно использовать этот компонент
HomePage
и такEditInfoPage
же, как он используется сейчас внутриLoginPage
Таким образом, обычно становится намного яснее, почему и когда составление над наследованием является правильным подходом.
источник