接口隔离原理 - 编程到接口

时间:2012-02-12 15:09:37

标签: oop design-patterns solid-principles interface-segregation-principle

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

有区别吗?

6 个答案:

答案 0 :(得分:38)

Robert Martin在他的“UML for Java Programmers”一书中对接口隔离原则(ISP)进行了很好的解释。基于此,我不认为ISP是关于一个界面“集中”在一个逻辑的,连贯的事物上的界面。因为,这是不言而喻的;或者,至少应该不言而喻。应该以这种方式设计每个类,接口或抽象类。

那么,什么是ISP?让我用一个例子解释一下。比如,你有一个A类和一个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()

 }   

这可以防止B了解更多信息。

答案 1 :(得分:30)

ISP专注于每个界面代表一种离散和凝聚行为的想法。

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

答案 2 :(得分:8)

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

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

让我们看看缺少接口隔离中的示例代码。

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是一个通用的fat接口,其中包含ShapeRectangleCircle等所有Square实现所需的方法。但是在各个Shape子项中只需要一些方法

     Rectangle : getLength(), getWidth(), getArea()
     Square    : getLength() and getArea()
     Circle    : getRadius() and getArea()
    
  2. 在没有隔离的情况下,所有Shapes都实现了整个胖接口:Shape。

  3. 如果我们按如下方式更改代码,我们可以通过接口隔离原则实现相同的输出。

    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 int getRadius(){
            // Not applicable
            return 0;
        }
        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 int getWidth(){
            // Not applicable
            return 0;
        }
        public int getRadius(){
            // Not applicable
            return 0;
        }
        public double getArea(){
            return length * length;
        }
    }
    
    class Circle implements Radius,Area{
        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 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之类的单个形状只实现了必需的接口,并且摆脱了未使用的方法。

答案 3 :(得分:1)

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

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

答案 4 :(得分:1)

  1. IWorker界面:

    public interface IWorker {
        public void work();
        public void eat();
    
    }
    
  2. 开发者类:

    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. 机器人类:

    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");
    
         }
    
    }
    
  4. 有关更完整的示例请转到here

答案 5 :(得分:0)

以下是此原则的实际示例(在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并不关心它使用的各种内容类型的任何不相关行为。它只关心它需要的行为,无论这些行为与之相关联。因此,通过为Discussable接口提供您想要讨论的每种内容类型,您将使用接口隔离原则。

这也是抽象基类不是一个好主意的一个很好的例子。论坛主题,用户个人资料和新闻文章在概念上甚至不是同一个东西,因此试图让他们继承讨论行为导致与不相关的父母的奇怪耦合。使用代表讨论的特定界面,您可以确保要进行讨论的实体与将要管理这些讨论的客户端代码兼容。

这个例子也可能是在PHP中使用Traits的一个很好的选择,因为它值得。