Является ли перегрузка примером принципа Open / Closed?

12

Википедия говорит

«программные объекты (классы, модули, функции и т. д.) должны быть открыты для расширения, но закрыты для модификации»

Слово функции привлекло мое внимание, и теперь я задаюсь вопросом, можем ли мы предположить, что создание перегрузки для метода может рассматриваться как пример принципа Open / closed или нет?

Позвольте мне объяснить пример. Учтите, что у вас есть метод на уровне сервиса, который используется почти в 1000 местах. Метод получает userId и определяет, является ли пользователь администратором или нет:

bool IsAdmin(userId)

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

public bool IsAdmin(string username)
{
    int userId = UserManager.GetUser(username).Id;
    return IsAdmin(userId);
}

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

Это открытый / закрытый принцип?

Саид Нямати
источник

Ответы:

5

Я бы лично интерпретировал утверждение вики таким образом:

  • для класса: не стесняйтесь наследовать класс и переопределять или расширять его функциональность, но не взламывайте исходный класс, тем самым изменяя то, что он делает.
  • для модуля (например, библиотеки): не стесняйтесь писать новый модуль / библиотеку, которая упаковывает оригинал, объединяет функции в более простые в использовании версии или расширяет оригинал дополнительными функциями, но не изменяет оригинальный модуль.
  • для функции (т. е. статической функции, а не метода класса): ваш пример мне подходит; повторно использовать исходную функцию IsAdmin (int) внутри нового IsAdmin (строка). оригинал не меняется, новый функционал расширяет его функциональность.

Суть в том, что если ваш код использует класс 'cUserInfo', в котором находится IsAdmin (int), вы по сути нарушаете правило и меняете класс. К сожалению, правило будет сохранено только в том случае, если вы создадите новый класс cUserWithNameInfo: public cUserInfo и поместите туда переопределение IsAdmin (string). Если бы я владел кодовой базой, я бы никогда не повиновался правилу. Я бы сказал чепухи и просто сделал бы изменения, которые вы предлагаете.

Sassafras_wot
источник
3

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

public bool IsAdmin(int userid)
{
    User user = UserManager.GetUser(userid);
    return user.IsAdmin();
}

public bool IsAdmin(string username)
{
    int userId = UserManager.GetUser(username).Id;
    return IsAdmin(userId);
}

Вы должны действительно расширить этот класс.

public bool IsAdmin(int userid)
{
    User user = UserManager.GetUserById(userid);
    return user.IsAdmin();
}

public bool IsUsernameAdmin(string username)
{
    User userId = UserManager.GetUserByName(username);
    return user.IsAdmin();
}

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

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

Как говорит дядя Боб (выделение мое):

Поскольку закрытие не может быть полным, оно должно быть стратегическим. То есть дизайнер должен выбрать виды изменений, по которым следует закрыть свой дизайн. Это требует определенного количества предвидения, полученного из опыта . Опытный дизайнер знает пользователей и отрасль достаточно хорошо, чтобы судить о вероятности различных изменений. Затем он следит за тем, чтобы принцип «открыто-закрыто» использовался для наиболее вероятных изменений.

прецизионный самописец
источник
Спасибо за хорошее объяснение. Но наличие 1000 поездок туда-обратно или одного туда-обратно не главное. Итак, давайте забудем о производительности на данный момент. Однако я также расширил класс, потому что я добавил к нему другой метод, хотя и с тем же именем, но с другими входными параметрами. Но я очень ценю твою цитату от дяди Боба . +1. ;)
Саид Нимати
@SaeedNeamati: Я хотел подчеркнуть, что не имеет значения, добавляете ли вы метод, использующий существующий метод, или добавляете метод, который является полностью независимым, в любом случае ваш класс был открыт для расширения. Но если вам пришлось изменить существующий интерфейс метода, чтобы достичь этого, вы не закрыты для модификации.
фунтовые
2

Модули, которые соответствуют принципу открытого-закрытого, имеют два основных атрибута.

  1. Они «открыты для расширения». Это означает, что поведение модуля может быть расширено. То, что мы можем заставить модуль вести себя по-новому и по-разному при изменении требований приложения или для удовлетворения потребностей новых приложений.
  2. Они «закрыты для модификации». Исходный код такого модуля нерушим. Никто не может вносить изменения в исходный код.
  1. Перегружая ваш метод, вы расширяете функциональность существующего модуля, таким образом отвечая новым потребностям вашего приложения.

  2. Вы не вносите никаких изменений в существующий метод, поэтому не нарушаете второе правило.

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

Ссылка: http://www.objectmentor.com/resources/articles/ocp.pdf

CodeART
источник
1

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

Но скажем, у вас была вызвана функция BigContrivedMethod(int1, int2, string1). BigContrivedMethodделает три вещи: вещь1, вещь2 и вещь3. На данный момент, повторное использование BCM, вероятно, сложно, потому что это делает слишком много. Рефакторинг этого (если возможно) в ContrivedFunction1(int), ContrivedFunction2(int)и ContrivedFunction3(string)дает вам три меньших, более сфокусированных метода, которые вы можете легко комбинировать.

И это ключ к OCP в отношении методов / функций: состав. Вы «расширяете» функции, вызывая их из других функций.

Помните, что OCP является частью 5 других принципов, руководящих принципов SOLID. Первый из них является ключевым, Единой Ответственности. Если бы все в вашей кодовой базе делало только одну конкретную вещь, которую вам приходилось делать, вам никогда бы не пришлось изменять код. Вам нужно всего лишь добавить новый код или объединить старый код вместе новыми способами. Поскольку реальный код редко соответствует этому руководству, вам часто приходится изменять его, чтобы получить SRP, прежде чем вы сможете получить OCP.

CodexArcanum
источник
Я думаю, что вы говорите здесь о принципале единой ответственности, а не об OCP.
Саид Нямати
1
Я говорю, что вы не можете реально иметь OCP без SRP. Код, который делает слишком много, не может быть расширен, его нельзя использовать повторно. SOLID почти написан в порядке важности, причем каждый принцип полагается на те, которые прежде должны быть осуществимыми.
CodexArcanum
Этот ответ должен быть одобрен больше.
Ашиш Гупта
0

Вы следуете принципу открытого-закрытого, если вы изменяете поведение своей программы, записывая новый код, а не изменяя старый код.

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

Когда вы сталкиваетесь с чем-то, что вам нужно изменить, вместо того, чтобы просто что-то изменить, чтобы разрешить новое поведение, вы выясните, в чем суть изменения, реструктурируйте свою программу, не изменяя ее поведение, чтобы затем вы могли изменить это поведение, написав НОВЫЙ код ,

Итак, как это относится к вашему делу:

Если вы добавляете новые функции в ваш класс, тогда ваш класс не открывается / не закрывается, а клиенты перегружаемой функции.

Если вы просто добавляете новые функции, которые работают НА вашем классе, то и он, и клиенты вашей функции открыты / закрыты.

Эдвард Стрендж
источник