在接口中使用默认方法是否违反接口隔离原则?

2024-06-20

我正在学习 SOLID 原则,ISP 指出:

客户端不应被迫依赖于他们所使用的接口 不使用。

在接口中使用默认方法是否违反了这个原则?

我见过类似的问题,但我在这里发布了一个示例,以便更清楚地了解我的示例是否违反了 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() 方法,而 Admin 类没有提供该方法的实现,因此我将其实现为 IUser Interface 中的默认方法,以尽量减少代码重复(DRY 原则)。

由于 Admin 类不使用 read 方法,因此在上面的代码中使用 IUser 中的默认方法是否违反了接口隔离原则?

有人可以解释一下吗,因为我认为我不会强迫管理类使用他们不使用的方法/接口。


在接口中使用默认方法是否违反原则?

不,如果正确使用它们就不会。事实上,它们可以帮助避免违反 ISP(见下文)。


您使用默认方法的示例是否违反了 ISP?

是的!我们可能会。我们可以就它到底违反 ISP 的严重程度进行辩论,但它肯定违反了许多其他原则,并且不是 Java 编程的良好实践。

问题是您使用默认方法作为实现类调用的方法。那不是他们的意图。

应使用默认方法来定义以下方法:

  1. users接口的成员可能希望调用(即不是实现者)
  2. provide 总计的功能性
  3. 对于接口的大多数(如果不是全部)实现者来说,有一个可能相同的实现

您的示例似乎违反了几个条件。

第一个条件的存在有一个简单的原因:Java 接口上的所有可继承方法都是公共的,因此它们总是can由接口的用户调用。举一个具体的例子,下面的代码可以正常工作:

Admin admin = new Admin();
admin.closeSession();
admin.readRecords();

想必,您不希望这种情况成为可能,不仅仅是为了Admin, 但对于Editor and Viewer也?我认为这是对 ISP 的违反,因为你依赖于你的类的用户不打电话那些方法。为了Admin类,你可以做readRecords()通过覆盖它并给它一个无操作实现来“安全”,但这只是凸显了对 ISP 的更直接的侵犯。对于所有其他方法/实现,包括do利用readRecords(), 你完蛋了。我不会从 ISP 的角度来考虑这一点,而是将其称为 API 或实现泄漏:它允许您的类以您不希望的方式使用(并且可能希望在将来打破)。

我所说的第二个条件可能需要进一步解释。经过总计的功能,我的意思是这些方法可能应该调用(直接或间接)接口上的一个或多个抽象方法。如果他们不这样做,那么这些方法的行为不可能依赖于实现类的状态,因此可能是静态的,或者完全移动到不同的类中(即参见单一职责原则 https://en.wikipedia.org/wiki/Single-responsibility_principle)。有一些示例和用例可以放宽此条件,但应非常仔细地考虑它们。在您给出的示例中,默认方法不是聚合的,但它看起来像是为了堆栈溢出而清理的代码,所以也许您的“真实”代码没问题。

对于我的第三个条件,2/3 的实施者是否算作“大多数”是有争议的。然而,另一种思考方式是你应该知道提前编写实现类,无论它们是否应该具有具有该功能的方法。您如何确定将来是否需要创建一个新的 User 类,它们将需要以下功能readRecords()?无论哪种方式,这都是一个有争议的问题,因为只有在您没有违反前两个条件的情况下才真正需要考虑这个条件。

充分利用默认方法

标准库中有一些很好用的例子default方法。一个是java.util.function.Function https://docs.oracle.com/javase/8/docs/api/java/util/function/Function.html以其andThen(...) and compose(...)方法。这些是有用的功能users函数,它们(间接)利用函数的抽象apply(...)方法,重要的是,实现类不太可能希望覆盖它们,除非是为了某些高度专业化场景中的效率。

这些默认方法的作用是not违反 ISP,因为类实现Function无需调用或覆盖它们。可能有许多用例中 Function 的具体实例永远不会有它们的andThen(...)方法被调用,但这很好——你不会通过提供有用但非必要的功能来破坏 ISP,只要你不妨碍所有这些用例forcing他们用它做点什么。对于 Function,将这些方法作为抽象方法而不是默认方法提供would违反 ISP,因为所有实现类都必须添加自己的实现,即使它们知道它不太可能被调用。

如何在不违反“规则”的情况下实现 DRY?

使用抽象类!

在有关 Java 良好实践的讨论中,抽象类遭到了很多批评,因为它们经常被误解、误用和滥用。如果至少有一些编程最佳实践指南(例如 SOLID)针对这种滥用而出版,我不会感到惊讶。我见过的一个非常常见的问题是,让一个抽象类为大量方法提供“默认”实现,然后几乎在所有地方都可以重写这些方法,通常是通过复制粘贴基本实现并更改 1 或 2 行来实现。本质上,这打破了我在上面的默认方法上的第三个条件(这也适用于预期子类化类型上的任何方法),并且这种情况经常发生。

然而,在这种情况下,抽象类可能正是您所需要的。

像这样的东西:

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();
    }
}

Note:根据您的情况,可能有更好的抽象类替代方案,但仍然实现 DRY:

  • 如果您的常用辅助方法是无状态的(即不依赖于类中的字段),您可以使用静态辅助方法的辅助类来代替(请参阅here https://www.geeksforgeeks.org/java-helperclass/举个例子)。

  • 您可能希望使用作品而不是抽象类继承。例如,不是创建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 不允许多重继承的问题,如果您尝试让多个抽象类包含不同的可选功能,并且某些最终类需要使用其中的多个抽象类,那么多重继承就会成为一个问题。然而,取决于什么state(即字段)readRecord()方法需要交互,因此可能无法将其干净地分离到单独的类中。

  • 你可以把你的readRecords()中的方法AbstractUser并避免使用额外的抽象类。这Admin类没有义务调用它,只要该方法是protected,不存在其他人调用它的风险(假设您已正确分离包裹)。这并不违反 ISP,即使Admin can与。。。相互作用readRecords(),这不是forced到。它可以假装该方法不存在,并且每个人都很好!

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

在接口中使用默认方法是否违反接口隔离原则? 的相关文章

  • Java 字符串哈希码缓存

    字符串不变性的优点之一是哈希码缓存以实现更快的访问 在这种情况下 如何处理具有相同哈希码的字符串的缓存 在这种情况下它真的能提高性能吗 在这种情况下 如何处理具有相同哈希码的字符串的缓存 被缓存的是字符串的哈希码 它被缓存在私有的int字符
  • Java中RandomAccessFile的并发

    我正在创建一个RandomAccessFile对象通过多个线程写入文件 在 SSD 上 每个线程都尝试在文件中的特定位置写入直接字节缓冲区 并且我确保线程写入的位置不会与另一个线程重叠 file getChannel write buffe
  • RxJava + Retrofit 2 的正确使用方法

    我有这样的 JSON success true data id 29 name u0420 u0435 u0441 u0442 u043e u0440 u0430 u0446 u0456 u044f u0411 u0430 u0447 u0
  • 如何实现具有LinkedHashMap类似功能的ConcurrentHashMap?

    我用过LinkedHashMap with accessOrdertrue 并同时允许最多 500 个条目作为数据的 LRU 缓存 但由于可扩展性问题 我想转向一些线程安全的替代方案 ConcurrentHashMap在这方面似乎不错 但缺
  • jpa2 CriteriaBuilder order by “ORDER BY 表达式必须出现在选择列表中”

    我正在写一个查询标准生成器 但无法添加order by子句 因为它随消息一起抛出错误ORDER BY 表达式必须出现在选择列表中这是我的实体 public class A Integer aId ManyToOne JoinColumn n
  • Java“空白最终字段可能尚未初始化”方法中抛出异常

    我有一些代码 例如 final int var1 if isSomethingTrue var1 123 else throwErrorMethod int var2 var1 throwErrorMethod 的定义如下 private
  • Glassfish:在部署期间修改 EAR 的部署描述符

    经过几天的搜索 尝试和摇头 我将这个问题发布到 SO 尽管它seems已经得到答复 这是场景 我有一个 EAR 应用程序 目前 包含一个 WAR 和一个 EJB 模块 EJB 模块使用 JPA persistence xml 并且一些无状态
  • 如何将抽象工厂与单例模式结合起来? [关闭]

    Closed 这个问题不符合堆栈溢出指南 help closed questions 目前不接受答案 我正在用 java 编码 并且对这些模式很陌生 谁能给我一个也使用单例的工厂抽象的例子 这是一个实现类的示例单例模式 这个实现也是线程安全
  • Active MQ - HelloWorld 示例异常

    我正在尝试运行 hello world 示例在这里找到 http activemq apache org hello world html I added activemq all 5 5 1 jar已经到图书馆了 它构建成功 但出现以下警
  • 如何为java注释处理器编写自动化单元测试?

    我正在尝试使用 java 注释处理器 我可以使用 JavaCompiler 编写集成测试 事实上我现在正在使用 hickory 我可以运行编译过程并分析输出 问题 即使我的注释处理器中没有任何代码 单个测试也会运行大约半秒 对于以 TDD
  • SwingWorker 在另一个 SwingWorker 的 did 方法中

    首先 我需要通知您 我正在尽最大努力学习如何用 Java 编写代码 虽然有点困难 但我相信我能做到 我过去提交了几个有关 SwingWorkers 等的问题 每一个我都以为我已经做到了 但后来发现我仍然需要学习 希望这一次不是那样的一次 话
  • 如何在Java中打印保留2位小数的浮点数?

    我可以用System out print 您可以使用printf http java sun com j2se 1 5 0 docs api java io PrintStream html printf 28java lang Strin
  • 如何在 JdbcTemplate 中创建 mySQL 存储过程

    背景 为了解决 MySql 中某些语句只允许在存储过程中出现的问题 我尝试在 JdbcTemplate 提交的 sql 中创建 运行然后删除存储过程 一个简单的例子是 这恰好是在 Spring Boot 中 Service public c
  • 使用 Box2d(适用于 Android)进行碰撞检测?

    有人可以解释一下使用 box2d for android 进行碰撞检测的工作原理吗 我无法理解 BBContactListener 以什么方式工作 BBContactListener listener new BBContactListen
  • JFrame Glasspane 也优于 JDialog,但不应该

    我有一个带有 Glasspane 的 JFrame 未装饰 该框架打开一个 JDialog 也未装饰 也有一个 glassPane 并隐藏自身 setVisible false Glasspanes 通过 setGlassPane 设置 对
  • 如何在Java中模拟引用传递?

    我是一个十足的 Java 菜鸟 我知道 Java 将所有参数视为按值传递 并且还有其他几个线程人们对此进行了解释 例如 在 C 中我可以这样做 void makeAThree int n n 3 int main int myInt 4 m
  • 更新分页。是否可以?

    他们是否存在一些方法来处理更新分页 例如我有 100 行类型 Id private Integer id Column private boolean flag Column private Date last 一开始它们看起来像 id f
  • while 之后无法访问的语句[重复]

    这个问题在这里已经有答案了 我只是修改代码 在以下代码中出现错误 int x 1 System out println x x while true x System out println x x 错误在最后一行 我可以知道错误 错误 无
  • 如何使 JScrollPane 与嵌套 JPanel 一起正常工作?

    我正在使用 NetBeans 在 Java 中构建 Swing 应用程序 但我遇到布局问题 我的主框架包含一个JScrollPane其中包含一个JPanel called contentPanel其中又包含一个JPanel called l
  • 如何创建具有同等时间元素的 JavaFX 转换?

    我正在尝试 JavaFX 和动画 尤其是PathTransition 我正在创建一个简单的程序 使球 弹跳 而不使用QuadCurveTo班级 到目前为止 这是我的代码 Ellipse ball new Ellipse 375 250 10

随机推荐

  • React Native this.'function' 不是一个函数

    我正在学习 React Native 和 Redux 这里有很多与我类似的问题 但我很难与我的问题联系起来 当我在另一个方法中调用一个方法时 它不断地返回给我 this some function 不是一个函数 我真的不知道该怎么做 这是我
  • NLTK 2.0分类器批量分类器方法

    当我运行此代码时 它会抛出一个错误 我认为这是由于 NLTK 3 0 中不存在batch classify 方法 我很好奇如何解决旧版本中的某些内容在新版本中消失的此类问题 def accuracy classifier gold resu
  • 限制文本日志文件的好方法

    我有一个 winForm 应用程序 其中有一个用于记录目的的文本文件 我想将其大小限制为 10 MB 这样如果超过此限制并写入新数据 则删除文本文件中最旧的数据以为新数据腾出空间 有什么有用的建议吗 使用一些日志框架 我建议NLog htt
  • 为什么我的 CloudFormation 脚本无法下载文件?

    我需要在 Windows 实例初始化期间下载文件 为了测试这一点 我使用以下脚本来下载 Google 徽标 使用简化版本的Windows 角色和功能模板 https s3 amazonaws com cloudformation templ
  • 如何将行从一个 DataGridView 移动到另一个 DataGridView?

    我有两个具有相同列架构的 DataGridView 尽管两个不同的 DataView 作为数据源 如果这很重要 将行从一个数据网格视图移动到另一个数据网格视图的最佳 最快方法是什么 我相信如果您在 DataTable table1 中有一行
  • 勾选或取消勾选复选框时输入时间戳

    我有一个 3 行 7 列的工作表 A1 G3 A 和 B 列有 6 个复选框 A1 B3 A 列和 B 列中的框分别链接到 C 列和 D 列 E 列和 F 列中的单元格只是分别复制 C 列和 D 列 实时E1细胞是 C1 and F3细胞是
  • 使用多个 io_service 对象

    我有我的应用程序 其中侦听和处理来自互联网套接字和 unix 域套接字的消息 现在我需要将 SSL 添加到互联网套接字 我使用的是单个io service应用程序中所有套接字的对象 看来现在我需要添加单独的io service网络套接字和
  • 使用 jQuery/JS 打开时使
    标签的内容具有动画效果

    我只想要 HTML5 的内容details标记为 滑行 动画打开 而不是仅仅弹出打开 立即出现 这可以用 jQuery Javascript 实现吗 Fiddle http jsfiddle net 9h4Hq HTML
  • VSCode Settings.json 丢失

    我正在遵循教程 并尝试将 vscode 指向我为 Scrapy 设置的虚拟工作区 但是当我在 VSCode 中打开设置时 工作区设置 选项卡不在 用户设置 选项卡旁边 我还尝试通过以下方式手动转到文件 APPDATA Code User s
  • 如何在 Access 2010 中创建自定义 ID

    这个问题的标题可能不准确 因为我不确定如何提出这个问题 有没有办法让 AC 2010 中的 ID 字段具有常量部分 然后是用户将输入的 ID 的一部分 示例 EMP9066 我希望 ID 的 EMP 部分始终保持不变 用户不应更改它 并且
  • 使用 SSL 证书验证 Web 浏览器

    是否可以使用 ssl 证书对 Web 浏览器进行身份验证 假设我在应用程序中存储私钥 有什么方法可以从浏览器读取密钥并尝试基于该私钥进行身份验证 您可以使用 SSL TLS 客户端证书身份验证来对浏览器 用户进行身份验证 服务器必须请求客户
  • Swift - 元类型 .Type 和 .self 之间有什么区别?

    元类型有什么区别 Type and self在斯威夫特 Do self and Type返回一个struct 我明白那个 self可以用来检查dynamicType 你如何使用 Type 首先也是最重要的是查看 Apple 文档type o
  • 我应该使用 Python 双端队列还是列表作为堆栈? [复制]

    这个问题在这里已经有答案了 我想要一个可以用作堆栈的 Python 对象 使用双端队列还是列表更好 元素数量较少还是数量较多有什么区别 您的情况可能会根据您的应用程序和具体用例而有所不同 但在一般情况下 列表非常适合堆栈 append is
  • Scrollable Control中的Scroll/Scroll有什么用?

    ScrollableControl 类有 2 个受保护的布尔属性 HScroll 和 VScroll As the document https msdn microsoft com en us library system windows
  • 在 Bash 中监控 tomcat,直到它完成部署 war 或应用程序

    怎么可能Tomcat在 bash 脚本中进行监控以检测它是否完成了战争或应用程序的部署 应用场景 Tomcat 开始于systemd Tomcat 开始于catalina sh 使用 Tomcat 管理器 Tomcat从Eclipse启动
  • 在 Play2 和 Scala 中解析没有数据类型的 JSON

    people name Jack age 15 name Tony age 23 name Mike age 19 这是我试图解析的 json 示例 我希望能够对每个人进行 foreach 操作并打印他们的姓名和年龄 我知道当 json 数
  • Socket.io - “套接字 ID”是否被视为敏感信息?

    我正在使用 Node js 和 socket io 实现一个简单的聊天应用程序 想知道在整个对象中共享所有客户端的套接字 ID 是否被认为是一个好的做法 为了解释一下 我的每个用户都是这样表示的 nick John Doe dateJoin
  • 确定哪个进程锁定了文件

    我有一个在本地运行良好的单元测试 但上传到 TeamCity 构建服务器时失败 并显示 该进程无法访问该文件 因为它正在被另一个进程使用 在我在测试中做任何事情之前 我检查设置是否有文件 存在 如果存在尝试删除它 这会失败并出现相同的错误
  • 有什么办法可以加快这个 VBA 算法的速度吗?

    我正在寻找实现 VBAtrie http en wikipedia org wiki Trie 构建能够在相对较短的时间内 少于 15 20 秒 处理大量英语词典 约 50 000 个单词 的算法 由于我实际上是一名 C 程序员 这是我第一
  • 在接口中使用默认方法是否违反接口隔离原则?

    我正在学习 SOLID 原则 ISP 指出 客户端不应被迫依赖于他们所使用的接口 不使用 在接口中使用默认方法是否违反了这个原则 我见过类似的问题 但我在这里发布了一个示例 以便更清楚地了解我的示例是否违反了 ISP 假设我有这个例子 pu