接口如何打破类的依赖关系?

我正在阅读鲍勃·马丁的《清洁建筑》。他谈到了用接口打破依赖关系。例如。B 类使用 A 类。因此,B 类依赖于类 A(B → A)。我们可以添加一个接口,让 B 依赖于接口,也让 A 依赖于接口(A → I ← B)。


我不明白的是,如果类A具有在B需要的函数中使用的私有成员变量,那么我是否不必重写B中与A相同的代码?另外,这不就是重复的代码吗?


下面是一个示例。


class Car {

    private String color;

    private Integer numberOfTires;

    [...]


    public void printCar()

    {

        System.out.print("Color: " + color);

        System.out.print("Number of tires: " + numberOfTires);

    }

}


class Inventory{

    private Car car;

    private Truck truck; // Left out for brevity


    public void printCar()

    {

        car.printCar();

    }


    public void printTruck()

    {

        truck.printTruck();

    }

}

我不明白接口如何帮助解决这种依赖关系。


幕布斯7119047
浏览 177回答 4
4回答

素胚勾勒不出你

考埃·西尔维拉提供了一个很好的例子,但没有提到另一个重要方面。通常,接口的要点不是要有更少的类。我想你熟悉耦合和内聚这两个术语。您总是希望拥有松散耦合且高度内聚的代码。这意味着,您不希望类相互依赖(耦合),而是以继承,多态性等形式共享一些逻辑。这些概念是质量对象导向设计的一些基本支柱。如果您不熟悉这些主题,绝对值得一读。回到这个问题,如果你正在处理一个有很多特殊情况的复杂逻辑,你通常会有一些重复。但是,这些问题与其他设计模式和原则更相关,这些模式和原则仅用于遵守&nbsp;DRY&nbsp;原则,并以推广解决方案方法的方式解决情况。接口背后的主要思想是为类设置一个逻辑结构,这有助于对象操作的统一性。想象一个证券交易所系统。此接口具有一个名为 execute 的方法,该方法将执行一些应用程序逻辑。public&nbsp;interface&nbsp;Order{&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;void&nbsp;execute(); }可能实现此接口的其他类可能是买入和卖出。它看起来像这样:public&nbsp;class&nbsp;Buy&nbsp;implement&nbsp;Order{&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@Override &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;public&nbsp;void&nbsp;execute(){&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//TODO:&nbsp;Some&nbsp;logic &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;} }现在,买入和卖出都有类似的代码,甚至可能有一些重复的代码,但更重要的是,当它们实现相同的接口时,你可以以统一的方式处理它们。您可以有一些股票管理器类,该类会在某些 .&nbsp;由此可以得出结论,在使用此类队列时,您将能够在&nbsp;Order&nbsp;接口的任何实现上调用该方法。Queue<Order>execute()在前面的论点之上,通过使用接口和一些框架(如Spring),您可以进行自动布线。这大大减少了对较低级别实现类的依赖,使您可以更改低级类而不会影响顶级处理程序。这种类型的应用程序设计是面向服务的体系结构&nbsp;(SOA) 中的常见做法。

蝴蝶不菲

Robert 谈到了打破依赖关系,当然你甚至可以打破库存和汽车之间的依赖关系,但我认为这不会给你带来太多好处,因为你不会跨越架构边界。最好将静态依赖关系中断为 ,因为 I/O 是一个架构边界,然后您可以替换汽车或库存的打印方式,这对于测试非常有用。System.out打破静态依赖关系class Car {&nbsp; &nbsp; private String color;&nbsp; &nbsp; private Integer numberOfTires;&nbsp; &nbsp; private PrintStream output = System.out;&nbsp; &nbsp; void setOutput(PrintStream output){&nbsp; &nbsp; &nbsp; &nbsp; this.output = Objects.requireNotNull(output);&nbsp; &nbsp; }&nbsp; &nbsp; public void printCar() {&nbsp; &nbsp; &nbsp; &nbsp; output.print("Color: " + color);&nbsp; &nbsp; &nbsp; &nbsp; output.print("Number of tires: " + numberOfTires);&nbsp; &nbsp; }}现在,您可以在测试中替换输出以捕获输出结果。应用接口隔离原则public interface Output {&nbsp; &nbsp; &nbsp;public void print(String msg);}class Car {&nbsp; &nbsp; private String color;&nbsp; &nbsp; private Integer numberOfTires;&nbsp; &nbsp; private Output output = (msg) -> System.out.println(msg);&nbsp; &nbsp; void setOutput(Output output){&nbsp; &nbsp; &nbsp; &nbsp; this.output = Objects.requireNotNull(output);&nbsp; &nbsp; }&nbsp; &nbsp; public void printCar() {&nbsp; &nbsp; &nbsp; &nbsp; output.print("Color: " + color);&nbsp; &nbsp; &nbsp; &nbsp; output.print("Number of tires: " + numberOfTires);&nbsp; &nbsp; }}现在,您唯一的依赖项是输出接口,该接口在测试中更容易替换或模拟。这个小小的变化使您的汽车独立于具体的输出系统,或者正如罗伯特所说,一个细节。我还可以想象实现一个 JTextArea输出,以便输出可以显示在 GUI 中。干净的体系结构告诉我们,I/O 是一个细节,我们的业务代码不应该依赖于它。似乎汽车和库存是您的业务代码,因此我向您展示了如何将其与具体的输出系统分离 - 一个细节。&nbsp; &nbsp; +-----+&nbsp; &nbsp; uses&nbsp; &nbsp; &nbsp;+--------+&nbsp; &nbsp; &nbsp;implements&nbsp; &nbsp;+--------------+&nbsp; &nbsp; | Car |&nbsp; -------->&nbsp; | Output |&nbsp; <-------------&nbsp; | SystemOutput |&nbsp; &nbsp; +-----+&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;+--------+&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; +--------------+&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;---------> control flow&nbsp; ------------->我们还应用了依赖关系反转原则,因为源代码依赖关系指向控制流。

红颜莎娜

据推测,您的是“车辆库存”,而不是“一辆汽车和一辆卡车的库存”。Inventory考虑到这一点,也许这会有所帮助:Car&nbsp;是一个&nbsp;VehicleTruck&nbsp;是一个&nbsp;VehicleInventory&nbsp;取决于&nbsp;--&nbsp;而不是&nbsp;or ,它对这些类型的一无所知VehicleCarTruckVehicle::printDetails由 和 实现CarTruck.public class Scratch4 {&nbsp; &nbsp; public static void main(String args[]) throws Exception {&nbsp; &nbsp; &nbsp; &nbsp; Car car = new Car("Blue", 4);&nbsp; &nbsp; &nbsp; &nbsp; Truck truck = new Truck();&nbsp; &nbsp; &nbsp; &nbsp; Inventory inventory = new Inventory();&nbsp; &nbsp; &nbsp; &nbsp; inventory.addVehicle(car);&nbsp; &nbsp; &nbsp; &nbsp; inventory.addVehicle(truck);&nbsp; &nbsp; &nbsp; &nbsp; inventory.printVehicleDetails();&nbsp; &nbsp; }}interface Vehicle {&nbsp; &nbsp; void printDetails();}class Car implements Vehicle {&nbsp; &nbsp; private String color;&nbsp; &nbsp; private Integer numberOfTires;&nbsp; &nbsp; public Car(String color, Integer numberOfTires) {&nbsp; &nbsp; &nbsp; &nbsp; this.color = color;&nbsp; &nbsp; &nbsp; &nbsp; this.numberOfTires = numberOfTires;&nbsp; &nbsp; }&nbsp; &nbsp; public void printDetails() {&nbsp; &nbsp; &nbsp; &nbsp; System.out.println("Color: " + color);&nbsp; &nbsp; &nbsp; &nbsp; System.out.println("Number of tires: " + numberOfTires);&nbsp; &nbsp; &nbsp; &nbsp; System.out.println();&nbsp; &nbsp; }}class Truck implements Vehicle {&nbsp; &nbsp; @Override&nbsp; &nbsp; public void printDetails() {&nbsp; &nbsp; &nbsp; &nbsp; System.out.println("Some kind of truck");&nbsp; &nbsp; &nbsp; &nbsp; System.out.println();&nbsp; &nbsp; }}class Inventory {&nbsp; &nbsp; private List<Vehicle> vehicles = new ArrayList<>();;&nbsp; &nbsp; public void addVehicle(Vehicle vehicle) {&nbsp; &nbsp; &nbsp; &nbsp; vehicles.add(vehicle);&nbsp; &nbsp; }&nbsp; &nbsp; public void printVehicleDetails() {&nbsp; &nbsp; &nbsp; &nbsp; vehicles.forEach(Vehicle::printDetails);&nbsp; &nbsp; }}收益 率Color: BlueNumber of tires: 4Some kind of truck

呼啦一阵风

在生产环境中的运行时,B 将使用真正的类 A(或实现相同接口的另一个类),因此无需复制代码。使用接口可以将另一个实现用于其他用途,例如,使用内存中的轻量级假类 A 进行 B 类的单元测试,从而避免重磅实类 A。您(只能)打破“静态,构建时”的依赖关系。“动态,运行时”依赖项(始终)保留。在代码中:public interface AInterface {}public class A implements AInterface {}public class B {&nbsp; AInterface a;&nbsp; public B(AInterface a) {&nbsp; &nbsp; this.a = a;&nbsp; }}public class Main {&nbsp; B b = B(A());}public class AFake implements AInterface {}public class BTest {&nbsp; B b = B(AFake())}此示例使用构造函数依赖关系注入。
打开App,查看更多内容
随时随地看视频慕课网APP

相关分类

Java