нарушает ли использование методов по умолчанию в интерфейсах принцип разделения интерфейсов?
Я изучаю принципы SOLID, и интернет-провайдер заявляет, что:
Clients should not be forced to depend upon interfaces that they do not use.
нарушает ли принцип использование методов по умолчанию в интерфейсах?
Я видел аналогичный вопрос, но я публикую здесь с примером, чтобы получить более ясную картину, если мой пример нарушает ISP. Скажем, у меня есть этот пример:
public interface IUser{
void UserMenu();
String getID();
default void closeSession() {
System.out.println("Client Left");
}
default void readRecords(){
System.out.println("User requested to read records...");
System.out.println("Printing records....");
System.out.println("..............");
}
}
со следующими классами, реализующими интерфейс IUser
public class Admin implements IUser{
public String getID(){
return "ADMIN";
}
public void handleUser() {
boolean sessionIsOpen = true;
while (sessionIsOpen) {
switch (Integer.parseInt(in.readLine())) {
case 1 -> addNewUser();
case 2 -> sessionIsOpen=false;
default -> System.out.println("Invalid Entry");
}
}
closeSession();
}
private void addNewUser() {
System.out.println("Adding New User..."); }
}
Класс редактора:
public class Editor implements IUser{
public String getID(){
return "EDITOR";
}
public void handleUser() {
boolean sessionIsOpen=true;
while (sessionIsOpen){
switch (Integer.parseInt(in.readLine())) {
case 1 -> addBook();
case 2 -> readRecords();
case 3 -> sessionIsOpen=false;
default ->
System.out.println("Invalid Entry");
}
}
closeSession();
}
private void addBook() {
System.out.println("Adding New Book..."); }
}
Класс зрителя
public class Viewer implements IUser{
public String getID(){
return "Viewer";
}
public void handleUser() {
boolean sessionIsOpen=true;
while (sessionIsOpen){
switch (Integer.parseInt(in.readLine())) {
case 1 -> readRecords();
case 2 -> sessionIsOpen=false;
default ->
System.out.println("Invalid Entry");
}
}
closeSession();
}
}
поскольку класс редактора и средства просмотра, использующий метод readRecords() и класс администратора, не обеспечивает реализацию этого метода, я реализовал его как метод по умолчанию в интерфейсе IUser, чтобы минимизировать повторение кода (принцип DRY). Нарушаю ли я принцип разделения интерфейса в приведенном выше коде, используя методы по умолчанию в IUser, потому что класс Admin не использует метод чтения?
2 ответа
нарушает ли принцип использование методов по умолчанию в интерфейсах?
Нет, если они используются правильно. Фактически, они могут помочь избежать нарушения ISP (см. Ниже).
Ваш пример использования методов по умолчанию нарушает ISP?
Да! Ну, наверное. Мы могли бы спорить о том, насколько серьезно это нарушает ISP, но это определенно нарушает множество других принципов и не является хорошей практикой в программировании на Java.
Проблема в том, что вы используете метод по умолчанию как нечто для вызова реализующего класса. Это не их намерения.
Методы по умолчанию следует использовать для определения методов, которые:
- пользователи интерфейса, скорее всего, захотят позвонить (т. е. не разработчики)
- обеспечивать совокупную функциональность
- иметь реализацию, которая, вероятно, будет одинаковой для большинства (если не для всех) разработчиков интерфейса
Ваш пример, похоже, нарушает несколько условий.
Первое условие существует по простой причине: все наследуемые методы в интерфейсах Java являются общедоступными, поэтому они всегда могут быть вызваны пользователями интерфейса. Чтобы привести конкретный пример, приведенный ниже код отлично работает:
Admin admin = new Admin();
admin.closeSession();
admin.readRecords();
По-видимому, вы не хотите, чтобы это стало возможным не только для, но и для
Второе условие, которое я сформулировал, может потребовать дальнейшего объяснения. Под совокупной функциональностью я имею в виду, что методы, вероятно, должны вызывать (прямо или косвенно) один или несколько абстрактных методов интерфейса. Если они этого не сделают, то поведение этих методов не может зависеть от состояния реализующего класса, и поэтому, вероятно, может быть статическим или полностью перемещенным в другой класс (т. Е. См. Принцип единой ответственности ) . Существуют примеры и варианты использования, когда можно ослабить это состояние, но о них следует подумать очень внимательно. В приведенном вами примере методы по умолчанию не являются агрегированными, но они выглядят как очищенный код для переполнения стека, так что, возможно, ваш «настоящий» код в порядке.
Спорный вопрос, считаются ли 2/3 исполнителей «большинством» в отношении моего третьего условия. Тем не менее, еще один способ думать о том, что вы должны знать заранее написание , реализующие классов , должны ли они этот метод с этой функциональностью. Как с уверенностью можно сказать, потребуются ли в будущем, если вам потребуется создать новый класс User, функциональность? В любом случае, это спорный вопрос, это условие действительно нужно учитывать только в том случае, если вы не нарушили первые 2.
Хорошее использование методов по умолчанию
В стандартной библиотеке есть примеры хороших применений.
java.util.function.Function
с его и
Эти методы по умолчанию не нарушают ISP, поскольку классы, которые их реализуют, не нуждаются в их вызове или реализации. Может быть много случаев использования, когда конкретные экземпляры Function никогда не имеют своих
Как добиться СУХОЙ, не нарушая «правил»?
Используйте абстрактный класс!
Абстрактные классы часто путались в дискуссиях о хорошей практике Java, потому что их часто неправильно понимали, неправильно использовали и злоупотребляли. Меня не удивило бы, если бы в ответ на это злоупотребление были опубликованы хотя бы некоторые руководства по передовому опыту программирования, такие как SOLID. Очень частая проблема, с которой я сталкивался, заключается в том, что абстрактный класс предоставляет реализацию "по умолчанию" для множества методов, которая затем переопределяется почти везде, часто путем копирования и вставки базовой реализации и изменения 1 или 2 строк. По сути, это нарушает мое третье условие для методов по умолчанию, описанных выше (которое также относится к любому методу для предполагаемого подкласса), и это происходит ОЧЕНЬ МНОГО.
Однако в этом сценарии абстрактные классы, вероятно, именно то, что вам нужно.
Что-то вроде этого:
interface IUser {
// Add all methods here intended to be CALLED by code that hods instances of IUser
// e.g.:
void handleUser();
String getID();
// If some methods only make sense for particular types of user,
// they shouldn't be added.
// e.g.:
// NOT void addBook();
// NOT void addNewUser();
}
abstract class AbstractUser implements IUser {
// Add methods and fields here that will be USEFUL to most or
// all implementations of IUser.
//
// Nothing should be public, unless it's an implementation of
// one of the abstract methods defined on IUser.
//
// e.g.:
protected void closeSession() { /* etc... */ }
}
abstract class AbstractRecordReadingUser extends AbstractUser {
// Add methods here that are only USEFUL to a subset of
// implementations of IUser.
//
// e.g.:
protected void readRecords(){ /* etc... */ }
}
final class Admin extends AbstractUser {
@Override
public void handleUser() {
// etc...
closeSession();
}
public void addNewUser() { /* etc... */ }
}
final class Editor extends AbstractRecordReadingUser {
@Override
public void handleUser() {
// etc...
readRecords();
// etc...
closeSession();
}
public void addBook() { /* etc... */ }
}
final class Viewer extends AbstractRecordReadingUser {
@Override
public void handleUser() {
// etc...
readRecords();
// etc...
closeSession();
}
}
Примечание: в зависимости от вашей ситуации могут быть лучшие альтернативы абстрактным классам, которые по-прежнему достигают DRY:
Если ваши общие вспомогательные методы не имеют состояния (т.е. не зависят от полей в классе), вы можете вместо этого использовать вспомогательный класс статических вспомогательных методов (см. Здесь пример).
Возможно, вы захотите использовать композицию вместо наследования абстрактных классов. Например, вместо создания
как указано выше, у вас может быть: final class RecordReader { // Fields relevant to the readRecords() method public void readRecords() { /* etc... */ } } final class Editor { final RecordReader r = new RecordReader(); @Override void handleUser() { // etc... r.readRecords(); // etc... } } // Similar for Viewer
Это позволяет избежать проблемы, заключающейся в том, что Java не допускает множественное наследование, что может стать проблемой, если вы попытаетесь создать несколько абстрактных классов, содержащих различные части дополнительных функций, и некоторые конечные классы должны использовать несколько из них. Однако, в зависимости от того, в каком состоянии (т.е. в полях)
метод должен взаимодействовать, возможно, не удастся чисто выделить его в отдельный класс. Вы можете просто вставить свой метод
и избегайте наличия дополнительного абстрактного класса. Класс не обязан его вызывать, и пока метод , нет риска, что кто-то еще вызовет его (при условии, что ваши пакеты правильно разделены). Это не нарушает ISP, хотя может взаимодействовать с , Он не вынужден к. Он может делать вид, что метода не существует, и все в порядке!
Я считаю, что это нарушение принципа интернет-провайдера. Но не обязательно строго следовать всем твердым принципам, так как это усложнит разработку.