11 设计原则SOLID之 : I 接口隔离原则

69 阅读3分钟

写代码太菜了,被女朋友我分手第一天,来学习接口隔离原则。在B站看了一个视频,该死:b23.tv/UetYdY8

1. 什么是接口隔离原则?

1. 定义

接口隔离原则: 强调一个类不应该强制实现它用不到的接口。具体来说,一个类应该对它的客户端提供尽可能小的接口,而不强迫客户端依赖于它们不使用的方法。

2. 举例说明

public interface Animal {
    void eat();
    void sleep();
    void fly();
}

我们有两个具体的动物类:BirdSnake

public class Bird implements Animal {
    public void eat() {
        // 实现吃的行为
    }

    public void sleep() {
        // 实现睡觉的行为
    }

    public void fly() {
        // 实现飞的行为
    }
}
public class Snake implements Animal {
    public void eat() {
        // 实现吃的行为
    }

    public void sleep() {
        // 实现睡觉的行为
    }

    public void fly() {
        // 啊,蛇不能飞!这里不应该有这个方法
    }
}

在这个例子中,Snake 类实现了 fly 方法,但是这并不符合蛇的行为,因为蛇是不会飞的。这违反了接口隔离原则。

修改:为了符合接口隔离原则:将 Animal 接口分解为更小的接口,每个接口代表一个行为:

public interface Eater {
    void eat();
}

public interface Sleeper {
    void sleep();
}

public interface Flyer {
    void fly();
}

public class Bird implements Eater, Sleeper, Flyer {
    // 实现相应的方法
}
public class Snake implements Eater, Sleeper {
    // 实现相应的方法
}

这就是接口隔离原则的核心思想:将庞大的接口分解为更小的、更具体的接口,以提高类的灵活性,降低耦合度。

2.接口隔离原则在工程中的体现

第一个例子是完完全全的接口,现在我们再换一种理解方式,把接口理解为单个接口或函数。那接口隔离原则就可以理解为:函数的设计要功能单一,不要将多个不同的功能逻辑在一个函数中实现。

考虑一个需求:从一组数字中筛选出偶数并计算它们的平均值。

import java.util.ArrayList;
import java.util.List;

public class Main {

    public static void main(String[] args) {
        List<Integer> numbers = List.of(1, 2, 3, 4, 5, 6, 7, 8, 9);
        filterEvenNumbersAndCalculateAverage(numbers);
    }

    // 不是纯函数,有副作用
    private static void filterEvenNumbersAndCalculateAverage(List<Integer> numbers) {
        List<Integer> evens = new ArrayList<>();
        for (int num : numbers) {
            if (num % 2 == 0) {
                evens.add(num);
            }
        }

        double average = evens.isEmpty() ? 0 : calculateAverage(evens);

        System.out.println("Filtered even numbers: " + evens);
        System.out.println("Average of even numbers: " + average);
    }

    // 纯函数,只关注计算平均值
    private static double calculateAverage(List<Integer> numbers) {
        double sum = 0;
        for (int num : numbers) {
            sum += num;
        }
        return numbers.isEmpty() ? 0 : sum / numbers.size();
    }
}

import java.util.ArrayList;
import java.util.List;

public class Main {

    public static void main(String[] args) {
        List<Integer> numbers = List.of(1, 2, 3, 4, 5, 6, 7, 8, 9);

        List<Integer> evens = filterEvenNumbers(numbers);
        double average = calculateAverage(evens);

        System.out.println("Filtered even numbers: " + evens);
        System.out.println("Average of even numbers: " + average);
    }

    // 纯函数,只关注筛选偶数
    private static List<Integer> filterEvenNumbers(List<Integer> numbers) {
        List<Integer> evens = new ArrayList<>();
        for (int num : numbers) {
            if (num % 2 == 0) {
                evens.add(num);
            }
        }
        return evens;
    }

    // 纯函数,只关注计算平均值
    private static double calculateAverage(List<Integer> numbers) {
        double sum = 0;
        for (int num : numbers) {
            sum += num;
        }
        return numbers.isEmpty() ? 0 : sum / numbers.size();
    }
}

这个代码有点长,但是表达的思想很简单,就是保持方法的纯度,其中一个是求奇数,一个是求平均值,可能A需求就既求奇偶数,也求平均值,可能是B需求只是求平均值。这个时候我们就应该对代码提纯,提高代码的复用性,而用不到求奇偶数的类就不需要提供改方法,虽然后面可以写成工具类,但是工具类里也要保持方法之间的隔离。

你应该已经发现,接口隔离原则跟单一职责原则有点类似,不过还是有点区别的:

  • 焦点不同: ISP主要关注接口设计,强调接口的精简和高内聚性;SRP主要关注模块接口的设计,强调类的单一职责。
  • 适用范围: ISP更侧重于接口的设计层面,而SRP更侧重于类的设计层面。

ISP提供了一种判断接口是否职责单一的标准:通过调用者如何使用接口来间接地判定。如果调用者只使用部分接口或接口的部分功能,那接口的设计就不够职责单一。

今天就先到这里了,晚安。