为了账号安全,请及时绑定邮箱和手机立即绑定

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

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

幕布斯7119047 2022-09-14 10:37:37
我正在阅读鲍勃·马丁的《清洁建筑》。他谈到了用接口打破依赖关系。例如。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();    }}我不明白接口如何帮助解决这种依赖关系。
查看完整描述

4 回答

?
素胚勾勒不出你

TA贡献1827条经验 获得超9个赞

考埃·西尔维拉提供了一个很好的例子,但没有提到另一个重要方面。

通常,接口的要点不是要有更少的类。我想你熟悉耦合内聚这两个术语。您总是希望拥有松散耦合且高度内聚的代码。

这意味着,您不希望类相互依赖(耦合),而是以继承,多态性等形式共享一些逻辑。这些概念是质量对象导向设计的一些基本支柱。如果您不熟悉这些主题,绝对值得一读。

回到这个问题,如果你正在处理一个有很多特殊情况的复杂逻辑,你通常会有一些重复。但是,这些问题与其他设计模式和原则更相关,这些模式和原则仅用于遵守 DRY 原则,并以推广解决方案方法的方式解决情况。

接口背后的主要思想是为类设置一个逻辑结构,这有助于对象操作的统一性。

想象一个证券交易所系统。

此接口具有一个名为 execute 的方法,该方法将执行一些应用程序逻辑。

public interface Order{     void execute();
}

可能实现此接口的其他类可能是买入卖出。它看起来像这样:

public class Buy implement Order{     @Override
     public void execute(){          //TODO: Some logic
     }
}

现在,买入卖出都有类似的代码,甚至可能有一些重复的代码,但更重要的是,当它们实现相同的接口时,你可以以统一的方式处理它们。您可以有一些股票管理器类,该类会在某些 . 由此可以得出结论,在使用此类队列时,您将能够在 Order 接口的任何实现上调用该方法。Queue<Order>execute()

在前面的论点之上,通过使用接口和一些框架(如Spring),您可以进行自动布线。这大大减少了对较低级别实现类的依赖,使您可以更改低级类而不会影响顶级处理程序。这种类型的应用程序设计是面向服务的体系结构 (SOA) 中的常见做法。


查看完整回答
反对 回复 2022-09-14
?
蝴蝶不菲

TA贡献1810条经验 获得超4个赞

Robert 谈到了打破依赖关系,当然你甚至可以打破库存和汽车之间的依赖关系,但我认为这不会给你带来太多好处,因为你不会跨越架构边界。


最好将静态依赖关系中断为 ,因为 I/O 是一个架构边界,然后您可以替换汽车或库存的打印方式,这对于测试非常有用。System.out


打破静态依赖关系

class Car {

    private String color;

    private Integer numberOfTires;


    private PrintStream output = System.out;


    void setOutput(PrintStream output){

        this.output = Objects.requireNotNull(output);

    }


    public void printCar() {

        output.print("Color: " + color);

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

    }

}

现在,您可以在测试中替换输出以捕获输出结果。


应用接口隔离原则

public interface Output {

     public void print(String msg);

}



class Car {

    private String color;

    private Integer numberOfTires;


    private Output output = (msg) -> System.out.println(msg);


    void setOutput(Output output){

        this.output = Objects.requireNotNull(output);

    }


    public void printCar() {

        output.print("Color: " + color);

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

    }

}

现在,您唯一的依赖项是输出接口,该接口在测试中更容易替换或模拟。


这个小小的变化使您的汽车独立于具体的输出系统,或者正如罗伯特所说,一个细节。我还可以想象实现一个 JTextArea输出,以便输出可以显示在 GUI 中。


干净的体系结构告诉我们,I/O 是一个细节,我们的业务代码不应该依赖于它。似乎汽车和库存是您的业务代码,因此我向您展示了如何将其与具体的输出系统分离 - 一个细节。


    +-----+    uses     +--------+     implements   +--------------+

    | Car |  -------->  | Output |  <-------------  | SystemOutput |

    +-----+             +--------+                  +--------------+


             ---------> control flow  ------------->

我们还应用了依赖关系反转原则,因为源代码依赖关系指向控制流。


查看完整回答
反对 回复 2022-09-14
?
红颜莎娜

TA贡献1842条经验 获得超12个赞

据推测,您的是“车辆库存”,而不是“一辆汽车和一辆卡车的库存”。Inventory

考虑到这一点,也许这会有所帮助:

  • Car 是一个 Vehicle

  • Truck 是一个 Vehicle

  • Inventory 取决于 -- 而不是 or ,它对这些类型的一无所知VehicleCarTruck

  • Vehicle::printDetails由 和 实现CarTruck

.

public class Scratch4 {

    public static void main(String args[]) throws Exception {

        Car car = new Car("Blue", 4);

        Truck truck = new Truck();


        Inventory inventory = new Inventory();

        inventory.addVehicle(car);

        inventory.addVehicle(truck);


        inventory.printVehicleDetails();

    }

}


interface Vehicle {

    void printDetails();

}


class Car implements Vehicle {

    private String color;

    private Integer numberOfTires;


    public Car(String color, Integer numberOfTires) {

        this.color = color;

        this.numberOfTires = numberOfTires;

    }


    public void printDetails() {

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

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

        System.out.println();

    }

}


class Truck implements Vehicle {


    @Override

    public void printDetails() {

        System.out.println("Some kind of truck");

        System.out.println();

    }


}


class Inventory {

    private List<Vehicle> vehicles = new ArrayList<>();;


    public void addVehicle(Vehicle vehicle) {

        vehicles.add(vehicle);

    }


    public void printVehicleDetails() {

        vehicles.forEach(Vehicle::printDetails);

    }

}

收益 率


Color: Blue

Number of tires: 4


Some kind of truck


查看完整回答
反对 回复 2022-09-14
?
呼啦一阵风

TA贡献1802条经验 获得超6个赞

在生产环境中的运行时,B 将使用真正的类 A(或实现相同接口的另一个类),因此无需复制代码。


使用接口可以将另一个实现用于其他用途,例如,使用内存中的轻量级假类 A 进行 B 类的单元测试,从而避免重磅实类 A。


您(只能)打破“静态,构建时”的依赖关系。“动态,运行时”依赖项(始终)保留。


在代码中:


public interface AInterface {}

public class A implements AInterface {}

public class B {

  AInterface a;

  public B(AInterface a) {

    this.a = a;

  }

}

public class Main {

  B b = B(A());

}

public class AFake implements AInterface {}

public class BTest {

  B b = B(AFake())

}

此示例使用构造函数依赖关系注入。


查看完整回答
反对 回复 2022-09-14
  • 4 回答
  • 0 关注
  • 102 浏览

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信