在接口中使用默认方法是否违反了接口隔离原则?
does using default methods in interfaces violates Interface segregation principle?
我正在学习 SOLID 原则,ISP 指出:
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("..............");
}
}
使用以下 classes 实现 IUser Interface
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..."); }
}
}
编辑器Class:
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..."); }
}
}
观看者Class
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();
}
}
由于编辑器和查看器 class 使用 readRecords() 方法并且 Admin class 没有提供该方法的实现,我将其实现为 IUser 界面中的默认方法以最大程度地减少代码重复(DRY 原则)。
我上面的代码是不是因为Adminclass没有使用read方法,所以在IUser中使用默认方法违反了接口隔离原则?
有人可以解释一下吗,因为我认为我没有强迫管理员 class 使用他们不使用的 methods/interfaces。
我认为这违反了 ISP 的原则。但是你不必严格遵循所有坚实的原则,因为这会使开发复杂化。
does using default methods in interfaces violate the principle?
不,如果使用得当则不会。事实上,它们可以帮助避免违反 ISP(见下文)。
您使用默认方法的示例是否违反了 ISP?
是的!我们很可能会。我们可以就它到底有多严重地违反 ISP 进行辩论,但它肯定违反了一系列其他原则,并且不是 Java 编程的好习惯。
问题是您正在使用默认方法作为实现 class 调用的方法。那不是他们的意图。
应使用默认方法来定义以下方法:
- 接口的用户 可能希望调用(即不是实现者)
- 提供聚合功能
- 对于接口的大多数(如果不是全部)实现者来说,有一个可能相同的实现
您的示例似乎违反了几个条件。
存在第一个条件的原因很简单:Java 接口上的所有可继承方法都是 public,因此它们总是 可以 被用户调用的界面。举一个具体的例子,下面的代码工作正常:
Admin admin = new Admin();
admin.closeSession();
admin.readRecords();
据推测,您不希望这成为可能,不仅是 Admin
,还有 Editor
和 Viewer
?我认为这是对 ISP 的一种违反,因为您依赖于 classes 的用户而不是调用 这些方法。对于 Admin
class,您可以通过覆盖它并为其提供空操作实现来使 readRecords()
'safe',但这只会突出更直接的 ISP 违规行为。对于所有其他 methods/implementations,包括 做 使用 readRecords()
的 class,你完蛋了。与其从 ISP 的角度考虑这一点,不如将其称为 API 或实现泄漏:它允许您的 classes 以您不希望的方式使用(并且可能希望破坏将来)。
我说的第二个条件可能需要进一步解释。通过 聚合 功能,我的意思是这些方法可能应该调用(直接或间接)接口上的一个或多个抽象方法。如果他们不这样做,那么这些方法的行为就不可能依赖于实现 class 的状态,因此可能是静态的,或者完全移入不同的 class(即见 Single-responsibility principle)。有一些示例和用例可以放宽此条件,但应该非常仔细地考虑它们。在您给出的示例中,默认方法不是聚合的,但它看起来像是为了 Stack Overflow 而经过清理的代码,所以也许您的“真实”代码没问题。
关于我的第三个条件,2/3 的实施者是否算作“最多”是值得商榷的。但是,另一种思考方式是您应该提前知道编写实现classes 是否应该具有具有该功能的方法。您怎么能确定将来如果您需要创建一个新的 class 用户,他们将需要 readRecords()
的功能?无论哪种方式,这都是一个有争议的问题,因为只有在您没有违反前 2 条的情况下才真正需要考虑这种情况。
很好地使用默认方法
标准库中有很好使用 default
方法的示例。一种是 java.util.function.Function
及其 andThen(...)
和 compose(...)
方法。这些对于 users 的函数来说是有用的功能,它们(间接地)利用函数的抽象 apply(...)
方法,重要的是,实现 class 永远希望覆盖它们,除非在某些高度专业化的场景中可能为了提高效率。
这些默认方法不违反ISP,因为实现Function
的classes不需要调用或覆盖它们。可能有许多用例,其中 Function 的具体实例永远不会调用其 andThen(...)
方法,但这很好 - 只要您不妨碍,您就不会通过提供有用但非必要的功能来破坏 ISP 强迫他们用它做一些事情。在 Function 的情况下,将这些方法提供为抽象而不是默认 会 违反 ISP,因为所有实现 classes 都必须添加自己的实现,即使他们知道它是不太可能被调用。
如何在不破坏 'the rules' 的情况下实现 DRY?
使用摘要 class!
摘要 classes 在讨论良好 Java 实践时经常被大便,因为它们经常被误解、误用和滥用。如果至少有一些像 SOLID 这样的编程最佳实践指南针对这种滥用而发布,我也不会感到惊讶。我见过的一个非常常见的问题是抽象 class 为大量方法提供“默认”实现,然后几乎在所有地方都被覆盖,通常是通过复制粘贴基本实现并更改 1 或 2 行。从本质上讲,这打破了我在上面的默认方法上的第三个条件(这也适用于 intended-to-be-subclassed 类型的任何方法),并且它经常发生。
但是,在这种情况下,抽象 classes 可能正是您所需要的。
像这样:
interface IUser {
// Add all methods here intended to be CALLED by code that holds
// 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();
}
}
注意: 根据您的情况,可能有更好的抽象 classes 替代品,但仍然实现 DRY:
如果您的常用辅助方法是无状态的(即不依赖于 class 中的字段),您可以使用辅助 class 静态辅助方法(有关示例,请参见 here。
您可能希望使用 组合 而不是抽象 class 继承。例如,不是像上面那样创建 AbstractRecordReadingUser
,而是可以:
final class RecordReader {
// Fields relevant to the readRecords() method
public void readRecords() { /* etc... */ }
}
final class Editor extends AbstractUser {
private final RecordReader r = new RecordReader();
@Override
void handleUser() {
// etc...
r.readRecords();
// etc...
}
}
// Similar for Viewer
这避免了 Java 不允许多重继承的问题,如果你试图让多个抽象 classes 包含不同的可选功能,以及一些最终的,这将成为一个问题classes 需要使用其中的几个。但是,根据 state(即字段),readRecord()
方法需要与之交互,可能无法将其完全分离到单独的 class 中.
您可以将 readRecords()
方法放在 AbstractUser
中,避免使用额外的摘要 class。 Admin
class 没有义务调用它,只要该方法是 protected
,就没有其他人调用它的风险(假设您的包裹已正确分离) .这并不违反 ISP,因为即使 Admin
可以 与 readRecords()
交互,也不会 强制 交互。它可以假装方法不存在,大家没事!
我正在学习 SOLID 原则,ISP 指出:
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("..............");
}
}
使用以下 classes 实现 IUser Interface
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..."); }
}
}
编辑器Class:
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..."); }
}
}
观看者Class
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();
}
}
由于编辑器和查看器 class 使用 readRecords() 方法并且 Admin class 没有提供该方法的实现,我将其实现为 IUser 界面中的默认方法以最大程度地减少代码重复(DRY 原则)。
我上面的代码是不是因为Adminclass没有使用read方法,所以在IUser中使用默认方法违反了接口隔离原则?
有人可以解释一下吗,因为我认为我没有强迫管理员 class 使用他们不使用的 methods/interfaces。
我认为这违反了 ISP 的原则。但是你不必严格遵循所有坚实的原则,因为这会使开发复杂化。
does using default methods in interfaces violate the principle?
不,如果使用得当则不会。事实上,它们可以帮助避免违反 ISP(见下文)。
您使用默认方法的示例是否违反了 ISP?
是的!我们很可能会。我们可以就它到底有多严重地违反 ISP 进行辩论,但它肯定违反了一系列其他原则,并且不是 Java 编程的好习惯。
问题是您正在使用默认方法作为实现 class 调用的方法。那不是他们的意图。
应使用默认方法来定义以下方法:
- 接口的用户 可能希望调用(即不是实现者)
- 提供聚合功能
- 对于接口的大多数(如果不是全部)实现者来说,有一个可能相同的实现
您的示例似乎违反了几个条件。
存在第一个条件的原因很简单:Java 接口上的所有可继承方法都是 public,因此它们总是 可以 被用户调用的界面。举一个具体的例子,下面的代码工作正常:
Admin admin = new Admin();
admin.closeSession();
admin.readRecords();
据推测,您不希望这成为可能,不仅是 Admin
,还有 Editor
和 Viewer
?我认为这是对 ISP 的一种违反,因为您依赖于 classes 的用户而不是调用 这些方法。对于 Admin
class,您可以通过覆盖它并为其提供空操作实现来使 readRecords()
'safe',但这只会突出更直接的 ISP 违规行为。对于所有其他 methods/implementations,包括 做 使用 readRecords()
的 class,你完蛋了。与其从 ISP 的角度考虑这一点,不如将其称为 API 或实现泄漏:它允许您的 classes 以您不希望的方式使用(并且可能希望破坏将来)。
我说的第二个条件可能需要进一步解释。通过 聚合 功能,我的意思是这些方法可能应该调用(直接或间接)接口上的一个或多个抽象方法。如果他们不这样做,那么这些方法的行为就不可能依赖于实现 class 的状态,因此可能是静态的,或者完全移入不同的 class(即见 Single-responsibility principle)。有一些示例和用例可以放宽此条件,但应该非常仔细地考虑它们。在您给出的示例中,默认方法不是聚合的,但它看起来像是为了 Stack Overflow 而经过清理的代码,所以也许您的“真实”代码没问题。
关于我的第三个条件,2/3 的实施者是否算作“最多”是值得商榷的。但是,另一种思考方式是您应该提前知道编写实现classes 是否应该具有具有该功能的方法。您怎么能确定将来如果您需要创建一个新的 class 用户,他们将需要 readRecords()
的功能?无论哪种方式,这都是一个有争议的问题,因为只有在您没有违反前 2 条的情况下才真正需要考虑这种情况。
很好地使用默认方法
标准库中有很好使用 default
方法的示例。一种是 java.util.function.Function
及其 andThen(...)
和 compose(...)
方法。这些对于 users 的函数来说是有用的功能,它们(间接地)利用函数的抽象 apply(...)
方法,重要的是,实现 class 永远希望覆盖它们,除非在某些高度专业化的场景中可能为了提高效率。
这些默认方法不违反ISP,因为实现Function
的classes不需要调用或覆盖它们。可能有许多用例,其中 Function 的具体实例永远不会调用其 andThen(...)
方法,但这很好 - 只要您不妨碍,您就不会通过提供有用但非必要的功能来破坏 ISP 强迫他们用它做一些事情。在 Function 的情况下,将这些方法提供为抽象而不是默认 会 违反 ISP,因为所有实现 classes 都必须添加自己的实现,即使他们知道它是不太可能被调用。
如何在不破坏 'the rules' 的情况下实现 DRY?
使用摘要 class!
摘要 classes 在讨论良好 Java 实践时经常被大便,因为它们经常被误解、误用和滥用。如果至少有一些像 SOLID 这样的编程最佳实践指南针对这种滥用而发布,我也不会感到惊讶。我见过的一个非常常见的问题是抽象 class 为大量方法提供“默认”实现,然后几乎在所有地方都被覆盖,通常是通过复制粘贴基本实现并更改 1 或 2 行。从本质上讲,这打破了我在上面的默认方法上的第三个条件(这也适用于 intended-to-be-subclassed 类型的任何方法),并且它经常发生。
但是,在这种情况下,抽象 classes 可能正是您所需要的。
像这样:
interface IUser {
// Add all methods here intended to be CALLED by code that holds
// 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();
}
}
注意: 根据您的情况,可能有更好的抽象 classes 替代品,但仍然实现 DRY:
如果您的常用辅助方法是无状态的(即不依赖于 class 中的字段),您可以使用辅助 class 静态辅助方法(有关示例,请参见 here。
您可能希望使用 组合 而不是抽象 class 继承。例如,不是像上面那样创建
AbstractRecordReadingUser
,而是可以:final class RecordReader { // Fields relevant to the readRecords() method public void readRecords() { /* etc... */ } } final class Editor extends AbstractUser { private final RecordReader r = new RecordReader(); @Override void handleUser() { // etc... r.readRecords(); // etc... } } // Similar for Viewer
这避免了 Java 不允许多重继承的问题,如果你试图让多个抽象 classes 包含不同的可选功能,以及一些最终的,这将成为一个问题classes 需要使用其中的几个。但是,根据 state(即字段),
readRecord()
方法需要与之交互,可能无法将其完全分离到单独的 class 中.您可以将
readRecords()
方法放在AbstractUser
中,避免使用额外的摘要 class。Admin
class 没有义务调用它,只要该方法是protected
,就没有其他人调用它的风险(假设您的包裹已正确分离) .这并不违反 ISP,因为即使Admin
可以 与readRecords()
交互,也不会 强制 交互。它可以假装方法不存在,大家没事!