接口隔离原则 - 对接口进行编程

发布于 2025-01-04 23:54:47 字数 84 浏览 4 评论 0原文

我正在阅读有关 SOLID 和其他设计原则的内容。我认为 ISP 与“对接口编程,而不是实现”相同。但看起来这些是不同的原理?

有区别吗?

I was reading about SOLID and other design principles. I thought ISP was the same as "Program to an interface, not an implementation". But it looks like these are different principles?

Is there a difference?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

庆幸我还是我 2025-01-11 23:54:47

Robert Martin 在他的《UML for Java Programmers》一书中对接口隔离原则(ISP)有很好的解释。基于此,我不认为 ISP 是一个“专注于”逻辑上、连贯的一组事物的接口。因为,这是不言而喻的;或者,至少应该不言而喻。每个类、接口或抽象类都应该这样设计。

那么,什么是ISP?让我用一个例子来解释一下。假设,你有一个类A和一个类B,类B是类A的客户端。假设,类A有十个方法,其中只有两个被B使用。现在,B是否需要知道A的所有十个方法?可能不是——信息隐藏的原理。你暴露的越多,你创造的耦合机会就越多。因此,您可以在两个类之间插入一个接口(称为 C)(隔离)。该接口只会声明 B 使用的两个方法,并且 B 将依赖于该接口,而不是直接依赖于 A。

所以现在,

class A {
  method1()
  method2()
  // more methods
  method10()
}

class B {
   A a = new A()
}

将变成

interface C {
  method1()
  method2()
}

class A implements C{
  method1()
  method2()
  // more methods
  method10()
}

class B {
  C c = new A()      
}   

This,阻止 B 知道更多的信息。

Robert Martin has a very good explanation of Interface segregation principle (ISP), in his book "UML for Java Programmers". Based on that, I don't think ISP is about an interface being "focused" on one logical, coherent group of things. Because, that goes without saying; or, at least it should go without saying. Each class, interface or abstract class should be designed that way.

So, what is ISP? Let me explain it with an example. Say, you have a class A and a class B, which is the client of class A. Suppose, class A has ten methods, of which only two are used by B. Now, does B need to know about all ten methods of A? Probably not - the principle of Information hiding. The more you expose, the more you create the chance for coupling. For that reason, you may insert an interface, call it C, between the two classes (segregation). That interface will only declare the two methods that are used by B, and B will depend on that Interface, instead of directly on A.

So now,

class A {
  method1()
  method2()
  // more methods
  method10()
}

class B {
   A a = new A()
}

will become

interface C {
  method1()
  method2()
}

class A implements C{
  method1()
  method2()
  // more methods
  method10()
}

class B {
  C c = new A()      
}   

This, prevents B from knowing more than it should.

梦里兽 2025-01-11 23:54:47

ISP 专注于每个接口代表一种离散且内聚的行为的理念。

也就是说,对象应该执行的每个逻辑事物组都将映射到单个特定接口。一个类可能想要做几件事,但每件事都会映射到代表该行为的特定接口。这个想法是每个界面都非常集中。

ISP is focused on the idea of each interface representing one discrete and cohesive behavior.

That is, each logical group of things an object should do would map to a single specific interface. A class might want to do several things, but each thing would map to a specific interface representing that behavior. The idea is each interface is very focused.

温柔少女心 2025-01-11 23:54:47

假设您有一个胖接口,其中包含许多要实现的方法。

任何实现该胖接口的类都必须提供所有这些方法的实现。有些方法可能不适用于该具体类。但在没有接口隔离​​原则的情况下,它仍然必须提供实现。

让我们看一下没有接口隔离​​的示例代码。

interface Shape{
    public int getLength();
    public int getWidth();
    public int getRadius();
    public double getArea();
}

class Rectangle implements Shape{
    int length;
    int width;
    public Rectangle(int length, int width){
        this.length = length;
        this.width = width;
    }
    public int getLength(){
        return length;
    }
    public int getWidth(){
        return width;
    }
    public int getRadius(){
        // Not applicable
        return 0;
    }
    public double getArea(){
        return width * length;
    }
}
class Square implements Shape{
    int length;
    
    public Square(int length){
        this.length = length;
    }
    public int getLength(){
        return length;
    }
    public int getWidth(){
        // Not applicable
        return 0;
    }
    public int getRadius(){
        // Not applicable
        return 0;
    }
    public double getArea(){
        return length * length;
    }
}

class Circle implements Shape{
    int radius;
    public Circle(int radius){
        this.radius = radius;
    }
    public int getLength(){
        // Not applicable
        return 0;
    }
    public int getWidth(){
        // Not applicable
        return 0;
    }
    public int getRadius(){
        return radius;
    }
    public double getArea(){
        return 3.14* radius * radius;
    }
}

public class InterfaceNoSeggration{
    public static void main(String args[]){
        Rectangle r = new Rectangle(10,20);
        Square s = new Square(15);
        Circle c = new Circle(2);
        System.out.println("Rectangle area:"+r.getArea());
        System.out.println("Square area:"+s.getArea());
        System.out.println("Circle area:"+c.getArea());
        
    }
}

输出:

java InterfaceNoSeggration
Rectangle area:200.0
Square area:225.0
Circle area:12.56

注释:

  1. Shape 是一个通用胖接口,其中包含所有 Shape 实现(如 Rectangle)所需的方法圆形方形。但在各自的 Shape 子项中只需要一些方法

     矩形:getLength()、getWidth()、getArea()
      正方形:getLength() 和 getArea()
      圆:getRadius() 和 getArea()
    
  2. 在没有隔离的情况下,所有 Shape 都实现了整个 fat 接口:Shape。

如果我们将代码更改如下,我们可以按照接口隔离原则实现相同的输出。

interface Length{
    public int getLength();
}
interface Width{
    public int getWidth();
}
interface Radius{
    public int getRadius();
}
interface Area {
    public double getArea();
}


class Rectangle implements Length,Width,Area{
    int length;
    int width;
    public Rectangle(int length, int width){
        this.length = length;
        this.width = width;
    }
    public int getLength(){
        return length;
    }
    public int getWidth(){
        return width;
    }
    
    public double getArea(){
        return width * length;
    }
}
class Square implements Length,Area{
    int length;
    
    public Square(int length){
        this.length = length;
    }
    public int getLength(){
        return length;
    }
    
    public double getArea(){
        return length * length;
    }
}

class Circle implements Radius,Area{
    int radius;
    public Circle(int radius){
        this.radius = radius;
    }
    
    public int getRadius(){
        return radius;
    }
    public double getArea(){
        return 3.14* radius * radius;
    }
}

public class InterfaceSeggration{
    public static void main(String args[]){
        Rectangle r = new Rectangle(10,20);
        Square s = new Square(15);
        Circle c = new Circle(2);
        System.out.println("Rectangle area:"+r.getArea());
        System.out.println("Square area:"+s.getArea());
        System.out.println("Circle area:"+c.getArea());
        
    }
}

注意:

现在诸如 RectangleSquareCircle 之类的单个 Shape 只实现了必需的接口,并摆脱了未使用的方法。

Assume that you have one fat interface with many methods to be implemented.

Any class, that implements that fat interface has to provide implementation for all these methods. Some of the methods may not be applicable to that concrete class. But still it has to provide implementation in absence of interface segregation principle.

Let's have a look at example code in absence of Interface segregation.

interface Shape{
    public int getLength();
    public int getWidth();
    public int getRadius();
    public double getArea();
}

class Rectangle implements Shape{
    int length;
    int width;
    public Rectangle(int length, int width){
        this.length = length;
        this.width = width;
    }
    public int getLength(){
        return length;
    }
    public int getWidth(){
        return width;
    }
    public int getRadius(){
        // Not applicable
        return 0;
    }
    public double getArea(){
        return width * length;
    }
}
class Square implements Shape{
    int length;
    
    public Square(int length){
        this.length = length;
    }
    public int getLength(){
        return length;
    }
    public int getWidth(){
        // Not applicable
        return 0;
    }
    public int getRadius(){
        // Not applicable
        return 0;
    }
    public double getArea(){
        return length * length;
    }
}

class Circle implements Shape{
    int radius;
    public Circle(int radius){
        this.radius = radius;
    }
    public int getLength(){
        // Not applicable
        return 0;
    }
    public int getWidth(){
        // Not applicable
        return 0;
    }
    public int getRadius(){
        return radius;
    }
    public double getArea(){
        return 3.14* radius * radius;
    }
}

public class InterfaceNoSeggration{
    public static void main(String args[]){
        Rectangle r = new Rectangle(10,20);
        Square s = new Square(15);
        Circle c = new Circle(2);
        System.out.println("Rectangle area:"+r.getArea());
        System.out.println("Square area:"+s.getArea());
        System.out.println("Circle area:"+c.getArea());
        
    }
}

output:

java InterfaceNoSeggration
Rectangle area:200.0
Square area:225.0
Circle area:12.56

Notes:

  1. Shape is a general purpose fat interface, which contains methods required for all Shape implementations like Rectangle, Circle and Square. But only some methods are needed in respective Shape childs

      Rectangle : getLength(), getWidth(), getArea()
      Square    : getLength() and getArea()
      Circle    : getRadius() and getArea()
    
  2. In absence of segregation, all Shapes have implemented entire fat interface : Shape.

We can achieve same output with interface segregation principle if we change the code as follows.

interface Length{
    public int getLength();
}
interface Width{
    public int getWidth();
}
interface Radius{
    public int getRadius();
}
interface Area {
    public double getArea();
}


class Rectangle implements Length,Width,Area{
    int length;
    int width;
    public Rectangle(int length, int width){
        this.length = length;
        this.width = width;
    }
    public int getLength(){
        return length;
    }
    public int getWidth(){
        return width;
    }
    
    public double getArea(){
        return width * length;
    }
}
class Square implements Length,Area{
    int length;
    
    public Square(int length){
        this.length = length;
    }
    public int getLength(){
        return length;
    }
    
    public double getArea(){
        return length * length;
    }
}

class Circle implements Radius,Area{
    int radius;
    public Circle(int radius){
        this.radius = radius;
    }
    
    public int getRadius(){
        return radius;
    }
    public double getArea(){
        return 3.14* radius * radius;
    }
}

public class InterfaceSeggration{
    public static void main(String args[]){
        Rectangle r = new Rectangle(10,20);
        Square s = new Square(15);
        Circle c = new Circle(2);
        System.out.println("Rectangle area:"+r.getArea());
        System.out.println("Square area:"+s.getArea());
        System.out.println("Circle area:"+c.getArea());
        
    }
}

Notes:

Now individual Shapes like Rectangle, Square and Circle have implemented only required interfaces and got rid of un-used methods.

安穩 2025-01-11 23:54:47

同意上面两个答案。举个上面 TrueWill 代码味道的例子,你不应该发现自己这样做:

@Override
public void foo() {
    //Not used: just needed to implement interface
}

Agree with both the answers above. Just to give an example of TrueWill's code smell above, you shouldn't find yourself doing this:

@Override
public void foo() {
    //Not used: just needed to implement interface
}
〆一缕阳光ご 2025-01-11 23:54:47
  1. IWorker接口:

    公共接口 IWorker {
        公共无效工作();
        公共无效吃();
    
    }
    
  2. 开发人员类:

    公共类 Developer 实现 IWorker {
    
         @覆盖
         公共无效工作(){
               // TODO 自动生成的方法存根
               System.out.println("开发人员正在工作");
    
         }
    
         @覆盖
         公共无效吃(){
               // TODO 自动生成的方法存根
               System.out.println("开发者吃饭");
    
         }
    
    }
    
  3. 机器人类别:

    public class Robot 实现 IWorker {
    
         @覆盖
         公共无效工作(){
               // TODO 自动生成的方法存根
               System.out.println("机器人正在工作");
    
         }
    
         @覆盖
         公共无效吃(){
               // TODO 自动生成的方法存根
               throw new UnsupportedOperationException("不能吃");
    
         }
    
    }
    

有关更完整的示例,请访问 这里

  1. IWorker Interface:

    public interface IWorker {
        public void work();
        public void eat();
    
    }
    
  2. Developer Class :

    public class Developer implements IWorker {
    
         @Override
         public void work() {
               // TODO Auto-generated method stub
               System.out.println("Developer working");
    
         }
    
         @Override
         public void eat() {
               // TODO Auto-generated method stub
               System.out.println("developer eating");
    
         }
    
    }
    
  3. Robot Class:

    public class Robot implements IWorker {
    
         @Override
         public void work() {
               // TODO Auto-generated method stub
               System.out.println("robot is working");
    
         }
    
         @Override
         public void eat() {
               // TODO Auto-generated method stub
               throw new UnsupportedOperationException("cannot eat");
    
         }
    
    }
    

For a more complete example go here.

青春有你 2025-01-11 23:54:47

这是该原则的一个真实示例(使用 PHP)

问题陈述

我希望各种形式的内容都具有与之相关的评论/讨论。该内容可能是从论坛主题、新闻文章、用户个人资料到对话式私人消息的任何内容。

架构

我们需要一个可重用的DiscussionManager类,它将Discussion附加到给定的内容实体。然而,上述四个例子(以及更多)在概念上都是不同的。如果我们希望 DiscussionManager 使用它们,那么所有四个以上都需要有一个共享的通用接口。 DiscussionManager 没有其他方法可以使用它们,除非您希望您的参数裸露(例如不进行类型检查)。

解决方案:使用以下方法的 Discussable 接口:

  • attachDiscussion($topic_id)
  • detachDiscussion()
  • getDiscussionID()

然后 DiscussionManager 可能如下所示:

class DiscussionManager
{
    public function addDiscussionToContent(Discussable $Content)
    {
        $Discussion = $this->DiscussionFactory->make( ...some data...);
        $Discussion->save() // Or $this->DiscussionRepository->save($Discussion);
        $Content->attachDiscussion($Discussion->getID()); // Maybe saves itself, or you can save through a repository
    }

    public function deleteDiscussion(Discussable $Content)
    {
        $id = $Content->getDiscussionID();
        $Content->detatchDiscussion();
        $this->DiscussionRepository->delete($id);
    }

    public function closeDiscussion($discussion_id) { ... }
}

这样,DiscussionManager 就不会关心它使用的各种内容类型的任何不相关行为。它只关心它需要的行为,无论这些行为与什么相关。因此,通过为您想要讨论的每种内容类型提供一个可讨论的接口,您正在使用接口隔离原则。

这也是抽象基类不是一个好主意的情况的一个很好的例子。论坛主题、用户个人资料和新闻文章在概念上根本不是同一件事,因此试图让它们继承讨论行为会导致与不相关的父级的奇怪耦合。使用代表讨论的特定接口,您可以确保您想要讨论的实体与将管理这些讨论的客户端代码兼容。

就其价值而言,这个示例也可能是在 PHP 中使用 Traits 的一个很好的候选者。

Here's a real-world example of this principle (in PHP)

Problem Statement:

I want various forms of content to have comments/discussion associated with them. That content might be anything from a forum topic, to a news article, to a user's profile, to a conversation-style private message.

Architecture

We will want a re-usable DiscussionManager class which attaches a Discussion to a given content entity. However, the above four examples (and many more) are all conceptually different. If we want the DiscussionManager to use them, then all four+ need to have one common interface that they all share. There is no other way for DiscussionManager to use them unless you want to your arguments to go naked (e.g. no type checking).

Solution: Discussable interface with these methods:

  • attachDiscussion($topic_id)
  • detachDiscussion()
  • getDiscussionID()

Then DiscussionManager might look like this:

class DiscussionManager
{
    public function addDiscussionToContent(Discussable $Content)
    {
        $Discussion = $this->DiscussionFactory->make( ...some data...);
        $Discussion->save() // Or $this->DiscussionRepository->save($Discussion);
        $Content->attachDiscussion($Discussion->getID()); // Maybe saves itself, or you can save through a repository
    }

    public function deleteDiscussion(Discussable $Content)
    {
        $id = $Content->getDiscussionID();
        $Content->detatchDiscussion();
        $this->DiscussionRepository->delete($id);
    }

    public function closeDiscussion($discussion_id) { ... }
}

This way, DiscussionManager does not care about any of the unrelated behaviors of the various content types that it uses. It ONLY cares about the behaviors it needs, regardless of what those behaviors are associated with. So by giving each content type that you want to have discussions for, a Discussable interface, you are using the interface segregation principle.

This is also a good example of a situation where an abstract base class is not a good idea. A forum topic, user profile, and news article aren't even remotely conceptually the same thing, thus trying to get them to inherit the discussion behaviors leads to strange coupling to an unrelated parent. Using a specific interface that represents discussions, you can makes sure that the entities you want to have discussions, are compatible with the client code that will be managing those discussions.

This example might also be a good candidate for usage of Traits in PHP, for what it's worth.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文